Ofensteuerung v1.0: Wemos-Platine 100x100 mit Pumpenrelais 230 V, Thermostat parallel, Strommessung ZMCT103C/ADS1115, MAX6675 Abgas, 3x DS18B20, Servo MG996R, PWM-Pumpe; 2-stufiges Freerouting, ESPHome-Konfig

This commit is contained in:
OpenClaw committed 2026-11-07 04:01:44 +01:00
commit 1301212f13
32 files changed
+33133

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
/.esphome/
/esphome/.esphome/
secrets.yaml
*.bin
__pycache__/
pcb/en/
pcb/*_unrouted.kicad_p*
pcb/*-backups/
pcb/fp-info-cache
*.kicad_prl
pcb/fab/gerber/
+88
View File
@@ -0,0 +1,88 @@
# Ofensteuerung (Wemos D1 mini)
Trägerplatine 100 × 100 mm für den Ofen, gleicher Aufbau wie die Umwälzpumpen-Platine:
Wemos D1 mini auf Buchsenleisten, alle Außenanschlüsse als **Steckklemmen 5,08 mm** (Stiftleiste + abziehbarer Schraubstecker).
![Bestückung](pcb/docs_3d.png)
## Funktionen
| Klemme | Pins | Funktion | ESP |
|---|---|---|---|
| J2 NETZ 230V | PE / N / L | Netzzuleitung (vorher LS-Schalter) | – |
| J3 PUMPE | L / N / PE | Pumpe 230 V über Relais K1 (Schließer), abgesichert mit F1 T2A | D0 |
| J4 THERMOSTAT | L / Ls | **Externer Thermostat parallel zum Relaiskontakt**: potentialfreier Schließer, schaltet die Pumpe auch ohne ESP | – |
| T1 | Drahtbrücke | Stromwandler ZMCT103C misst den Pumpenstrom, egal ob Relais oder Thermostat schaltet → ADS1115 A0-A1 | I²C |
| A3 (Modul) | Typ K | Abgastemperatur: MAX6675-Modul, Thermoelement direkt in die Klemme des Moduls am Plattenrand | D5/D6/D8 |
| J5, J6, J7 | GND / DQ / 3V3 | 3× DS18B20 am selben 1-Wire-Bus (4k7) | D4 |
| J8 KLAPPE | GND / 5V / SIG | Luftklappe: Servo **MG996R** (braun/rot/orange), 470 µF direkt an der Klemme | D3 |
| J9 PWM-PUMPE | GND / FB / PWM | Vorbereitung PWM-Pumpe, gleiche Stufe wie Umwälzpumpe (BC546B, 470 Ω, JP1 Pull-up) + Rückmeldung | D7, RX |
| J1 5V 3A | + / − | Versorgung 5 V, **mindestens 3 A** (MG996R zieht beim Anlaufen/Blockieren bis 2,5 A) | – |
| J10 | 5V 3V3 GND SDA SCL A0 TX AD2 AD3 | Erweiterung (Stiftleiste 2,54) | – |
Pinbelegung jeder Klemme steht auf dem Bestückungsdruck, Pin 1 ist mit ▽ markiert.
## 230-V-Teil
- Der Netzspannungsbereich (oben links) ist auf dem Bestückungsdruck umrandet, dort gibt es keine GND-Fläche.
- Abstand 230 V ↔ Kleinspannung auf der Platine **≥ 4 mm** (eigene DRC-Regel). Ausnahme ist das Relais selbst: Spule ↔ Kontakt ≈ 3,3 mm (SRD-Bauform, Basisisolierung).
Die Kleinspannungsseite ist damit **nicht berührsicher getrennt** → Platine nur in geschlossenem Gehäuse betreiben, vor dem Öffnen Netz trennen.
Über die Fühler (Thermoelement am Abgasrohr, DS18B20 an Rohren) liegt GND in der Praxis ohnehin auf Schutzleiterpotential.
- **Stromwandler T1:** Die Pumpen-Phase läuft als isolierte Drahtbrücke (H07V-K 1,5 mm², ca. 4 cm, vorgebogen) vom Pad links durch das Loch des ZMCT103C zum Pad rechts.
Ohne diese Brücke läuft die Pumpe nicht.
- Leiterbahnen 230 V: 2 mm breit, für die Pumpe (< 1 A) reichlich. F1 = Feinsicherung 5×20 träge (T2A, je nach Pumpe auch T1A).
- **Ausfallverhalten:** ESP aus/bootet → Relais aus (10k am Basiswiderstand). Die Pumpe läuft dann nur über den Thermostat an J4.
Deshalb den Thermostat (z.B. Anlegethermostat am Ofenvorlauf, ca. 60–70 °C, Schließer) unbedingt anschließen.
## Strommessung
ZMCT103C (1000:1) mit 100 Ω Bürde: 1 A Pumpenstrom = 100 mV eff. Die Bürde liegt auf 1,65 V (Teiler 2×10k), der ADS1115 misst differenziell A0-A1 (±256 mV → bis ca. 1,8 A).
ESPHome (`ct_clamp`) rechnet daraus den Strom und meldet zusätzlich:
- **Pumpe läuft** (Strom > 30 mA)
- **Pumpe läuft über Thermostat** (Strom, aber Relais aus)
- **Pumpe Störung** (Relais an, 20 s kein Strom → Sicherung, Pumpe oder Kabel)
Nach dem Aufbau einmal mit Zangenamperemeter oder Shelly vergleichen und ggf. den Faktor `multiply: 10.0` anpassen.
## Teile
`pcb/fab/ofensteuerung_bom.csv` enthält die Stückliste mit Fach aus deinem Inventar (`Parts_Inventory.xlsx`).
**Aus dem Inventar:** Widerstände 100/220/330/470/1k/2k2/4k7/10k (Schrank E), 100n (C-07-1), 10 µ (C-10-2), 470 µ (C-04-3), 1000 µ (C-05-3),
BC546B (B-01-4), 1N4007 (B-02-1), LEDs (B-07-5, B-08-5), Stift-/Buchsenleisten (A-07-2, A-06-5), Jumper (A-11-3), Sicherungshalter/-sicherung (B-12-2, B-11-2).
Relais: im Fach A-07-1 nachsehen, ob ein SRD-05VDC-SL-C (oder pinkompatibel: Songle SRD, HKE HRS4H, Ningbo …) dabei ist.
**Zukaufen:**
- Steckklemmen 5,08 mm, jeweils Stiftleiste abgewinkelt + Schraubstecker: **7× 3-polig, 2× 2-polig** (J2, J3, J4 sind 230 V: Phoenix MSTB 2,5 oder 2EDG mit 300 V Nennspannung)
- ZMCT103C Stromwandler (Einzelbauteil, nicht das Modul mit OPV)
- MAX6675-Modul + Thermoelement Typ K als Abgasfühler (Edelstahl-Hülse, bis ca. 600 °C)
- ADS1115-Modul (HW-ADS1115, Pinfolge VDD GND SCL SDA ADDR ALRT A0 A1 A2 A3 prüfen)
- Servo MG996R, 3× DS18B20 (wasserdicht), Wemos D1 mini
- 5-V-Netzteil ≥ 3 A (Hutschiene z.B. Mean Well HDR-30-5)
- Anlegethermostat mit Schließer
- ggf. Relais SRD-05VDC-SL-C, 2 Sicherungsclips 5×20 + T2A
Hinweise:
- MAX6675-Modul: Pinfolge GND VCC SCK CS SO ist die übliche (rote/blaue Platine). Vor dem Einlöten der Buchsenleiste am Modul prüfen.
- Wemos, ADS1115 und MAX6675 auf Buchsenleisten stecken. Der USB-Stecker des Wemos ist auf der Platine zugebaut: erstes Flashen vor dem Einstecken, danach OTA.
- D3/GPIO0 (Servo) und D8/GPIO15 (MAX CS, 10k nach GND) sind Boot-Pins und passend beschaltet.
## Firmware
`esphome/ofensteuerung.yaml` (geprüft mit `esphome config`): Relais, Strom, Abgas, 3× DS18B20, Luftklappe als Zahl 0–100 %, PWM-Pumpe.
MQTT unter `gunter/ofensteuerung/…`, Secrets wie bei der Umwälzpumpe. Die Servo-Endlagen (`min_level`/`max_level`) an die Klappe anpassen.
## Dateien neu erzeugen
Alles kommt aus `pcb/design.py` (Netzliste, Footprints, Positionen) und `pcb/sch_layout.py` (Schaltplan-Anordnung).
```
cd pcb
python3 gen_sch.py # Schaltplan + Projekt-Libs (kicad-cli sch erc ofensteuerung.kicad_sch)
python3 gen_pcb.py # platzierte Platine
python3 route.py 30 # Freerouting in 2 Stufen: erst 230 V, dann Kleinspannung (230-V-Bereich gesperrt)
kicad-cli pcb drc --schematic-parity ofensteuerung.kicad_pcb
python3 fab.py && ./docs.sh # Gerber-zip, Stückliste, PDFs, Bilder
```
Bestellen: `pcb/fab/ofensteuerung_gerber.zip` (2 Lagen, 1,6 mm, HASL, 100 × 100 mm).
+216
View File
@@ -0,0 +1,216 @@
# Ofensteuerung auf der Platine pcb/ (v1.0)
# D0/GPIO16 Relais K1 (230-V-Pumpe, Schließer) - Thermostat an J4 liegt parallel zum Kontakt
# D1/GPIO5 SCL, D2/GPIO4 SDA -> ADS1115 (0x48): A0-A1 = Stromwandler ZMCT103C (Bürde 100 R, Mitte 1,65 V)
# D5/GPIO14 SCK, D6/GPIO12 SO, D8/GPIO15 CS -> MAX6675 (Abgas, Thermoelement Typ K)
# D4/GPIO2 1-Wire, 3x DS18B20 (J5-J7, 4k7 Pull-up)
# D3/GPIO0 Servo MG996R (Luftklappe, 330 R in Serie)
# D7/GPIO13 PWM-Pumpe (Transistorstufe wie Umwälzpumpe), RX/GPIO3 Rückmeldung
# frei auf J10: A0, TX/GPIO1, ADS1115 A2/A3
substitutions:
name: ofensteuerung
esphome:
name: ${name}
friendly_name: Ofensteuerung
esp8266:
board: d1_mini
restore_from_flash: true
logger:
level: INFO
baud_rate: 0 # RX/GPIO3 = Rückmeldung PWM-Pumpe
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
reboot_timeout: 15min
ap:
ssid: "Ofensteuerung Fallback"
captive_portal:
api:
reboot_timeout: 0s
encryption:
key: !secret api_key
mqtt:
broker: 192.168.179.24
port: 1883
username: ramiro
password: !secret mqtt_password
topic_prefix: gunter/ofensteuerung
reboot_timeout: 0s
discovery: true
log_topic: null
birth_message:
topic: gunter/ofensteuerung/status
payload: online
will_message:
topic: gunter/ofensteuerung/status
payload: offline
ota:
- platform: esphome
password: !secret ota_password
web_server:
port: 80
i2c:
sda: GPIO4 # D2
scl: GPIO5 # D1
scan: true
frequency: 400kHz
spi:
clk_pin: GPIO14 # D5
miso_pin: GPIO12 # D6
one_wire:
- platform: gpio
pin: GPIO2 # D4
ads1115:
- address: 0x48
continuous_mode: true
# ---------------- Pumpe 230 V ----------------
switch:
- platform: gpio
pin: GPIO16 # D0 -> BC546B -> K1
name: "Pumpe Relais"
id: pumpe_relais
restore_mode: RESTORE_DEFAULT_OFF
icon: mdi:pump
# ---------------- Luftklappe (Servo MG996R) ----------------
output:
- platform: esp8266_pwm
id: klappe_pwm
pin: GPIO0 # D3 (Boot-Pin, Servo-Eingang ist hochohmig)
frequency: 50 Hz
- platform: esp8266_pwm
id: pump_pwm
pin: GPIO13 # D7
frequency: 1000 Hz
inverted: false # Stufe invertiert: Klemmen-Duty = 1 - power (VTS/AMG Profil H)
min_power: 0.16
max_power: 0.93
zero_means_zero: true
servo:
- id: klappe_servo
output: klappe_pwm
min_level: 3% # ~0,6 ms -> ggf. an Klappenanschlag anpassen
max_level: 12% # ~2,4 ms
transition_length: 4s
auto_detach_time: 3s # danach kein Haltestrom/Brummen; Klappe hält per Getriebe
number:
- platform: template
name: "Luftklappe"
id: luftklappe
unit_of_measurement: "%"
min_value: 0
max_value: 100
step: 1
optimistic: true
restore_value: true
initial_value: 50
icon: mdi:valve
set_action:
- servo.write:
id: klappe_servo
level: !lambda 'return x / 50.0f - 1.0f;' # 0..100 % -> -1..+1
# ---------------- PWM-Pumpe (optional) ----------------
fan:
- platform: speed
output: pump_pwm
name: "PWM-Pumpe"
id: pwm_pumpe
restore_mode: RESTORE_DEFAULT_ON
sensor:
# Pumpenstrom über ZMCT103C: 1 A primär = 1 mA sekundär = 100 mV an der Bürde
- platform: ads1115
multiplexer: A0_A1
gain: 0.256 # +-256 mV -> bis ca. 1,8 A eff.
id: ct_spannung
sample_rate: 860
update_interval: never
internal: true
- platform: ct_clamp
sensor: ct_spannung
name: "Pumpe Strom"
id: pumpe_strom
sample_duration: 400ms
update_interval: 5s
unit_of_measurement: A
accuracy_decimals: 3
filters:
- multiply: 10.0 # V an 100 R -> A primär
- lambda: 'return x < 0.015f ? 0.0f : x;'
- platform: template
name: "Pumpe Leistung (ca.)"
unit_of_measurement: W
device_class: power
accuracy_decimals: 0
update_interval: 5s
lambda: 'return id(pumpe_strom).state * 230.0f * 0.9f;' # Schein -> grob Wirkleistung (cos phi ~0,9 schätzen)
- platform: max6675
cs_pin: GPIO15 # D8 (10k Pull-down auf der Platine)
name: "Abgastemperatur"
id: abgas
update_interval: 5s
filters:
- sliding_window_moving_average: {window_size: 3, send_every: 1}
# DS18B20 an J5/J6/J7 (ein Bus). Nach dem ersten Start die Adressen aus dem Log eintragen
# und index durch address: 0x... ersetzen, sonst können die Fühler die Reihenfolge tauschen.
- platform: dallas_temp
name: "Temperatur 1"
index: 0
update_interval: 10s
- platform: dallas_temp
name: "Temperatur 2"
index: 1
update_interval: 10s
- platform: dallas_temp
name: "Temperatur 3"
index: 2
update_interval: 10s
- platform: duty_cycle
pin:
number: GPIO3 # RX
mode: INPUT
name: "PWM-Pumpe Rückmeldung"
update_interval: 5s
accuracy_decimals: 0
- platform: wifi_signal
name: "WLAN Signal"
update_interval: 60s
- platform: uptime
name: "Uptime"
binary_sensor:
- platform: template
name: "Pumpe läuft"
device_class: running
lambda: 'return id(pumpe_strom).state > 0.03f;'
- platform: template
name: "Pumpe läuft über Thermostat"
lambda: 'return id(pumpe_strom).state > 0.03f && !id(pumpe_relais).state;'
- platform: template
name: "Pumpe Störung" # Relais an, aber kein Strom (Sicherung, Pumpe, Kabel)
device_class: problem
lambda: 'return id(pumpe_relais).state && id(pumpe_strom).state < 0.03f;'
filters:
- delayed_on: 20s
- platform: status
name: "Status"
+5
View File
@@ -0,0 +1,5 @@
wifi_ssid: "IOT"
wifi_password: "..."
api_key: "..." # 32 Byte base64: head -c32 /dev/urandom | base64
ota_password: "..."
mqtt_password: "..."
+178
View File
@@ -0,0 +1,178 @@
"""Ofensteuerung (Wemos D1 mini) - einzige Quelle für Schaltplan + Platine.
Pins: Symbol-Pinnummer == Footprint-Padnummer.
pcb: (x, y, rot) in mm, Platine 100 x 100, Ursprung oben links.
230-V-Bereich oben links (Rahmen auf dem Bestückungsdruck), Kleinspannung rechts und unten.
"""
PROJECT = 'ofensteuerung'
W, H = 100.0, 100.0
FP_R = 'Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal'
FP_C = 'Capacitor_THT:C_Disc_D5.0mm_W2.5mm_P5.00mm'
FP_CP10 = 'Capacitor_THT:CP_Radial_D10.0mm_P5.00mm'
FP_CP8 = 'Capacitor_THT:CP_Radial_D8.0mm_P3.50mm'
FP_CP5 = 'Capacitor_THT:CP_Radial_D5.0mm_P2.00mm'
FP_Q = 'Package_TO_SOT_THT:TO-92_Inline_Wide'
FP_LED = 'LED_THT:LED_D3.0mm'
FP_D41 = 'Diode_THT:D_DO-41_SOD81_P10.16mm_Horizontal'
FP_T3 = 'Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal'
FP_T2 = 'Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_2-G-5,08_1x02_P5.08mm_Horizontal'
FP_H2 = 'Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical'
TERM = 'Steckklemme 5,08 mm (Phoenix MSTBA/MSTB oder 2EDGR/2EDGK kompatibel)'
TERM230 = 'Steckklemme 5,08 mm, 250 V / 12 A (Phoenix MSTBA 2,5/x-G-5,08 + MSTB 2,5/x-ST-5,08)'
MAINS = ('L_IN', 'L_F', 'L_SW', 'L_PUMP', 'N', 'PE')
PARTS = []
def P(ref, sym, val, fp, pins, pcb, **kw):
d = dict(ref=ref, sym=sym, val=val, fp=fp, pins=pins, pcb=pcb); d.update(kw); PARTS.append(d)
def R(ref, val, a, b, pcb, **kw):
P(ref, 'Device:R', val, FP_R, {'1': a, '2': b}, pcb, **kw)
def C(ref, val, a, b, pcb, fp=FP_C, sym='Device:C', **kw):
P(ref, sym, val, fp, {'1': a, '2': b}, pcb, **kw)
# ================= 230 V =================
P('J2', 'Connector:Screw_Terminal_01x03', 'NETZ 230V (PE/N/L)', FP_T3, {'1': 'L_IN', '2': 'N', '3': 'PE'}, (21.16, 10.5, 180), mpn=TERM230,
desc='Netzzuleitung, vor der Platine abgesichert (LS 10 A)')
P('J4', 'Connector:Screw_Terminal_01x02', 'THERMOSTAT (L/Ls)', FP_T2, {'1': 'L_SW', '2': 'L_F'}, (34.44, 10.5, 180), mpn=TERM230,
desc='Potentialfreier Schließer (Anlegethermostat), parallel zum Relaiskontakt - schaltet die Pumpe auch ohne ESP')
P('J3', 'Connector:Screw_Terminal_01x03', 'PUMPE 230V (L/N/PE)', FP_T3, {'1': 'PE', '2': 'N', '3': 'L_PUMP'}, (52.8, 10.5, 180), mpn=TERM230)
P('F1', 'Device:Fuse', 'T 2A', 'Fuse:Fuseholder_Clip-5x20mm_Littelfuse_111_Inline_P20.00x5.00mm_D1.05mm_Horizontal',
{'1': 'L_IN', '2': 'L_F'}, (9.36, 25.0, 0), desc='Feinsicherung 5x20 träge, Pumpe + Thermostatkreis', mpn='2x Sicherungsclip 5x20 (oder Halter aus B-12-2)')
P('K1', 'Relay:SANYOU_SRD_Form_C', 'SRD-05VDC-SL-C', 'Relay_THT:Relay_SPDT_SANYOU_SRD_Series_Form_C',
{'1': 'L_F', '2': 'RELAY_C', '3': 'L_SW', '4': None, '5': '+5V'}, (29.36, 48.0, 90),
desc='Songle SRD-05VDC-SL-C 10 A/250 V, Schließer (NO) schaltet Pumpe', ncname={'4': ''})
P('T1', PROJECT + ':ZMCT103C', 'ZMCT103C', PROJECT + ':ZMCT103C_Drahtbruecke',
{'1': 'CT_A', '2': 'VMID', '3': 'L_SW', '4': 'L_PUMP'}, (50.0, 38.0, 90),
desc='Stromwandler 5A/5mA (1000:1). Pumpen-L als isolierte Drahtbrücke (H07V-K 1,5 mm²) durchs Loch: Pad L_SW -> Loch -> Pad L_PUMP')
# ================= Versorgung 5 V =================
P('J1', 'Connector:Screw_Terminal_01x02', 'VIN 5V/3A (+/-)', FP_T2, {'1': '+5V', '2': 'GND'}, (84.44, 89.5, 0), mpn=TERM,
desc='5-V-Netzteil >= 3 A (Servo MG996R zieht bis 2,5 A)')
C('C1', '1000u/10V', '+5V', 'GND', (90.0, 79.0, 0), fp=FP_CP10, sym='Device:C_Polarized', desc='Puffer für Servo-Anlaufstrom')
C('C2', '100n', '+5V', 'GND', (85.5, 40.5, 0))
R('R4', '2k2', '+5V', 'LED5_A', (77.0, 47.0, 0))
P('D3', 'Device:LED', 'LED grün', FP_LED, {'1': 'GND', '2': 'LED5_A'}, (91.5, 47.0, 180))
# ================= Wemos =================
P('A1', PROJECT + ':WEMOS_D1_mini', 'WEMOS D1 mini', PROJECT + ':WEMOS_D1_mini_Socket', {
'1': None, '2': 'A0', '3': 'RELAY_G', '4': 'SCK', '5': 'MISO', '6': 'PWM_G', '7': 'TC_CS', '8': '+3V3',
'9': 'TX', '10': 'FB', '11': 'SCL', '12': 'SDA', '13': 'SERVO_G', '14': 'OW', '15': 'GND', '16': '+5V'},
(70.0, 16.0, 0), desc='auf 2x 1x8 Buchsenleiste 2,54 mm', ncname={'1': 'RST'})
C('C3', '10u', '+3V3', 'GND', (77.0, 40.5, 0), fp=FP_CP5, sym='Device:C_Polarized')
C('C4', '100n', '+3V3', 'GND', (69.0, 40.5, 0))
# ================= Relaistreiber =================
R('R1', '1k', 'RELAY_G', 'RELAY_B', (15.0, 61.0, 0))
R('R2', '10k', 'RELAY_B', 'GND', (15.0, 64.5, 0), desc='hält das Relais beim Booten aus')
P('Q1', 'Transistor_BJT:Q_NPN_CBE', 'BC546B', FP_Q, {'1': 'RELAY_C', '2': 'RELAY_B', '3': 'GND'}, (26.5, 63.0, 0))
P('D1', 'Device:D', '1N4007', FP_D41, {'1': '+5V', '2': 'RELAY_C'}, (23.0, 57.0, 0), desc='Freilaufdiode Relaisspule')
R('R3', '1k', '+5V', 'LEDK_A', (34.0, 66.0, 0))
P('D2', 'Device:LED', 'LED rot', FP_LED, {'1': 'RELAY_C', '2': 'LEDK_A'}, (37.0, 61.5, 0), desc='leuchtet, wenn das Relais anzieht')
# ================= Strommessung (ZMCT103C -> ADS1115 A0-A1 differenziell) =================
R('R5', '100', 'CT_A', 'VMID', (44.0, 58.5, 0), desc='Bürde: 1 A Pumpenstrom = 100 mV eff.')
R('R6', '10k', '+3V3', 'VMID', (44.0, 62.0, 0))
R('R7', '10k', 'VMID', 'GND', (44.0, 65.5, 0))
C('C5', '10u', 'VMID', 'GND', (56.0, 60.0, 0), fp=FP_CP5, sym='Device:C_Polarized')
C('C6', '100n', 'VMID', 'GND', (55.0, 65.5, 0))
P('A2', PROJECT + ':ADS1115_Modul', 'ADS1115 Modul', PROJECT + ':ADS1115_Module_1x10', {
'1': '+3V3', '2': 'GND', '3': 'SCL', '4': 'SDA', '5': 'GND', '6': None, '7': 'CT_A', '8': 'VMID', '9': 'ADS_A2', '10': 'ADS_A3'},
(16.0, 81.5, 0), desc='I2C 0x48 (ADDR=GND), auf 1x10 Buchsenleiste', ncname={'6': 'ALRT'})
# ================= Abgastemperatur (MAX6675-Modul, Typ K) =================
P('A3', PROJECT + ':MAX6675_Modul', 'MAX6675 Modul', PROJECT + ':MAX6675_Module_1x5',
{'1': 'GND', '2': '+3V3', '3': 'SCK', '4': 'TC_CS', '5': 'MISO'}, (76.5, 58.5, 0),
desc='Thermoelement Typ K direkt an die Schraubklemme des Moduls (Plattenrand). Pinfolge GND VCC SCK CS SO vor dem Einlöten prüfen')
R('R9', '10k', 'TC_CS', 'GND', (72.5, 59.0, 90), desc='GPIO15 muss beim Booten low sein')
C('C8', '100n', '+3V3', 'GND', (68.5, 64.0, 90))
# ================= 1-Wire DS18B20 (3 Klemmen, ein Bus) =================
for i, xyr in enumerate(((11.0, 89.5, 0), (29.36, 89.5, 0), (10.5, 58.0, 270))):
P(f'J{5+i}', 'Connector:Screw_Terminal_01x03', 'DS18B20 (GND/DQ/3V3)', FP_T3, {'1': 'GND', '2': 'OW', '3': '+3V3'}, xyr, mpn=TERM,
desc='DS18B20: schwarz GND, gelb DQ, rot VDD')
R('R8', '4k7', '+3V3', 'OW', (3.5, 76.0, 0))
C('C7', '100n', '+3V3', 'GND', (3.5, 80.0, 0))
# ================= Luftklappe: Servo MG996R =================
P('J8', 'Connector:Screw_Terminal_01x03', 'KLAPPE SERVO (GND/5V/SIG)', FP_T3, {'1': 'GND', '2': '+5V', '3': 'SERVO'}, (47.72, 89.5, 0), mpn=TERM,
desc='MG996R: braun GND, rot +5V, orange Signal')
R('R10', '330', 'SERVO_G', 'SERVO', (47.0, 83.0, 0), desc='Schutzwiderstand Signal (3,3-V-Pegel reicht für MG996R)')
C('C9', '470u/10V', '+5V', 'GND', (51.0, 76.5, 0), fp=FP_CP8, sym='Device:C_Polarized', desc='Puffer direkt an der Servoklemme')
C('C10', '100n', '+5V', 'GND', (44.0, 76.5, 90))
# ================= PWM-Pumpe (optional, wie Umwälzpumpe) =================
P('J9', 'Connector:Screw_Terminal_01x03', 'PWM-PUMPE (GND/FB/PWM)', FP_T3, {'1': 'GND', '2': 'FB_IN', '3': 'PWM_OUT'}, (66.08, 89.5, 0), mpn=TERM,
desc='Signalkabel PWM-Hocheffizienzpumpe (Profil H/S), wie Umwälzpumpe')
R('R11', '1k', 'PWM_G', 'PWM_B', (73.0, 84.0, 90))
R('R12', '10k', 'PWM_B', 'GND', (76.5, 84.0, 90))
P('Q2', 'Transistor_BJT:Q_NPN_CBE', 'BC546B', FP_Q, {'1': 'PWM_OUT', '2': 'PWM_B', '3': 'GND'}, (80.0, 82.0, 0))
R('R13', '470', 'PWM_PU', 'PWM_OUT', (69.5, 84.0, 90))
P('JP1', 'Jumper:Jumper_2_Open', 'Pull-up 5V', FP_H2, {'1': '+5V', '2': 'PWM_PU'}, (66.0, 78.5, 0),
desc='gesteckt: Ausgang 0/5 V (VTS/AMG), offen: Open-Collector -> ESP aus = Pumpe nach Bedienfeld')
R('R14', '220', 'FB_IN', 'FB', (62.5, 84.0, 90))
R('R15', '4k7', '+3V3', 'FB', (59.0, 84.0, 90))
# ================= Erweiterung =================
P('J10', 'Connector_Generic:Conn_01x09', 'ERWEITERUNG', 'Connector_PinHeader_2.54mm:PinHeader_1x09_P2.54mm_Vertical', {
'1': '+5V', '2': '+3V3', '3': 'GND', '4': 'SDA', '5': 'SCL', '6': 'A0', '7': 'TX', '8': 'ADS_A2', '9': 'ADS_A3'},
(97.5, 32.0, 0), desc='I2C, ESP A0, TX (GPIO1), ADS1115 A2/A3 für spätere Sensoren')
for i, (x, y) in enumerate([(3.5, 3.5), (96.5, 3.5), (3.5, 96.5), (97.1, 97.1)]):
P(f'H{i+1}', 'Mechanical:MountingHole', 'M3', 'MountingHole:MountingHole_3.2mm_M3', {}, (x, y, 0))
# PWR_FLAGs (nur Schaltplan)
for i, net in enumerate(('+5V', 'GND')):
P(f'#FLG0{i+1}', 'power:PWR_FLAG', 'PWR_FLAG', '', {'1': net}, None, board=False)
NOCONNECT = {('A1', '1'), ('A2', '6'), ('K1', '4')}
# ---------- 230-V-Bereich ----------
# Polygon des Netzspannungsbereichs (keine GND-Fläche; beim Routen der Kleinspannung gesperrt)
MAINS_AREA = [(0.6, 0.6), (66.3, 0.6), (66.3, 53.5), (0.6, 53.5)]
# Kleinspannungs-Stummel, die aus dem 230-V-Bereich herausführen (vorverlegt): (net, [(x,y),...])
LV_STUBS = [] # in gen_pcb aus den Padpositionen berechnet
# ---------- Bestückungsdruck ----------
TERMLBL = {
'J1': ('5V 3A', ['+', '-']), 'J2': ('NETZ 230V', ['L', 'N', 'PE']), 'J3': ('PUMPE', ['PE', 'N', 'L']),
'J4': ('THERMOSTAT', ['Ls', 'L']),
'J5': ('DS18B20', ['GND', 'DQ', '3V3']), 'J6': ('DS18B20', ['GND', 'DQ', '3V3']), 'J7': ('DS18B20', ['GND', 'DQ', '3V3']),
'J8': ('KLAPPE', ['GND', '5V', 'SIG']), 'J9': ('PWM-PUMPE', ['GND', 'FB', 'PWM']),
}
SILK = []
for p in PARTS:
if p['ref'] in TERMLBL:
name, pins = TERMLBL[p['ref']]
x0, y0, rot = p['pcb']
if rot == 270: # linke Kante, Pins nach unten
ys = [y0 + 5.08 * i for i in range(len(pins))]
for y, t in zip(ys, pins):
SILK.append((t, x0 + 3.6, y, 0.8))
SILK.append((name, x0 - 5.5, sum(ys) / len(ys), 1.1, 'F.SilkS', 90, True))
continue
if rot == 90: # rechte Kante, Pin 1 unten, Pins nach oben
ys = [y0 - 5.08 * i for i in range(len(pins))]
for y, t in zip(ys, pins):
SILK.append((t, x0 - 6.0, y, 0.9))
SILK.append((name, x0 - 1.0, ys[-1] - 4.8, 1.1, 'F.SilkS', 0, True))
continue
sgn = 1 if rot == 0 else -1
xs = [x0 + sgn * 5.08 * i for i in range(len(pins))]
ly = y0 + (4.05 if rot == 180 else -3.0)
gy = y0 + (-6.0 if rot == 180 else -5.0)
for x, t in zip(xs, pins):
SILK.append((t, x, ly, 0.9))
if rot == 180:
SILK.append((name, sum(xs) / len(xs), ly + 2.0, 1.1, 'F.SilkS', 0, True))
else:
SILK.append((name, sum(xs) / len(xs), gy, 1.1, 'F.SilkS', 0, True))
for i, t in enumerate(['5V', '3V3', 'GND', 'SDA', 'SCL', 'A0', 'TX', 'AD2', 'AD3']):
SILK.append((t, 94.4, 32.0 + 2.54 * i, 0.8, 'F.SilkS', 0))
SILK += [
('OFENSTEUERUNG v1.0', 50, 97.0, 1.2, 'B.SilkS', 0, True),
('!! 230 V !!', 9.0, 44.0, 1.5, 'F.SilkS', 0, True),
('Draht L durchs Loch', 49.5, 27.5, 0.8),
('T2A', 19.2, 28.6, 0.9),
('PU', 66.0, 77.4, 0.8),
('Typ K', 98.6, 63.5, 0.9, 'F.SilkS', 90),
]
Executable
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# Doku-Bilder: Bestückung, Platine (Kupfer), Schaltplan
cd "$(dirname "$0")"; t=/tmp/docs_$$; mkdir -p $t
kicad-cli pcb export svg --mode-single -l F.Silkscreen,F.Fab,Edge.Cuts --page-size-mode 2 --exclude-drawing-sheet -o $t/b.svg ofensteuerung.kicad_pcb >/dev/null 2>&1
rsvg-convert -w 1400 -b white $t/b.svg -o docs_bestueckung.png
kicad-cli pcb export svg --mode-single -l F.Cu,B.Cu,Edge.Cuts,F.Silkscreen --page-size-mode 2 --exclude-drawing-sheet -o $t/c.svg ofensteuerung.kicad_pcb >/dev/null 2>&1
rsvg-convert -w 1400 -b white $t/c.svg -o docs_platine.png
kicad-cli sch export svg -o $t ofensteuerung.kicad_sch >/dev/null 2>&1
rsvg-convert -w 2400 -b white $t/ofensteuerung.svg -o docs_schaltplan.png
kicad-cli pcb render --side top --width 1400 --height 1400 --quality basic -o docs_3d.png ofensteuerung.kicad_pcb >/dev/null 2>&1 || true
rm -rf $t; ls -la docs_*.png
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""Fertigungsdaten: Gerber+Bohrdaten (zip), Stückliste mit Inventar-Fächern, Schaltplan-PDF, 1:1-Druck, Doku-Bilder."""
import os, sys, csv, shutil, subprocess, zipfile, collections
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from design import PARTS, PROJECT
FAB = os.path.join(HERE, 'fab'); G = os.path.join(FAB, 'gerber')
shutil.rmtree(G, ignore_errors=True); os.makedirs(G)
pcb = os.path.join(HERE, PROJECT + '.kicad_pcb'); sch = os.path.join(HERE, PROJECT + '.kicad_sch')
def run(*a):
subprocess.run(a, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
run('kicad-cli', 'pcb', 'export', 'gerbers', '-o', G + '/', '--layers',
'F.Cu,B.Cu,F.Silkscreen,B.Silkscreen,F.Mask,B.Mask,Edge.Cuts', '--subtract-soldermask', pcb)
run('kicad-cli', 'pcb', 'export', 'drill', '-o', G + '/', '--format', 'excellon', '--excellon-separate-th', pcb)
zp = os.path.join(FAB, PROJECT + '_gerber.zip')
with zipfile.ZipFile(zp, 'w', zipfile.ZIP_DEFLATED) as z:
for f in sorted(os.listdir(G)):
z.write(os.path.join(G, f), f)
run('kicad-cli', 'sch', 'export', 'pdf', '-o', os.path.join(FAB, PROJECT + '_schaltplan.pdf'), sch)
run('kicad-cli', 'pcb', 'export', 'pdf', '--mode-single', '-l', 'F.Silkscreen,F.Fab,Edge.Cuts,F.Courtyard',
'--include-border-title', '-o', os.path.join(FAB, PROJECT + '_1zu1_druck.pdf'), pcb)
# ---------- Stückliste ----------
INV = { # (Wert, Footprint-Teil) -> Inventar-Fach (Parts_Inventory.xlsx)
'100': 'E-01-3', '220': 'E-05-3', '330': 'E-07-3', '470': 'E-09-3', '1k': 'E-01-4', '2k2': 'E-05-4',
'4k7': 'E-09-4', '10k': 'E-01-5', '100n': 'C-07-1', '10u': 'C-10-2 / D-04-3', '470u/10V': 'C-04-3',
'1000u/10V': 'C-05-3', 'BC546B': 'B-01-4', '1N4007': 'B-02-1', 'LED grün': 'B-07-5', 'LED rot': 'B-08-5',
'Pull-up 5V': 'A-07-2 + Jumper A-11-3', 'ERWEITERUNG': 'A-07-2',
'SRD-05VDC-SL-C': 'A-07-1 prüfen (sonst kaufen)', 'T 2A': 'Clips/Halter B-12-2, Sicherung B-11-2',
'WEMOS D1 mini': 'Buchsenleisten A-06-5 / A-07-3', 'ADS1115 Modul': 'Buchsenleiste A-06-5; Modul ggf. B-11-5',
'MAX6675 Modul': 'Buchsenleiste A-06-5; Modul ggf. B-11-5',
}
BUY = {'ZMCT103C': 'kaufen (Stromwandler 5A/5mA)', 'MAX6675 Modul': 'kaufen inkl. Thermoelement Typ K (Abgasfühler)',
'ADS1115 Modul': 'kaufen, falls kein Rest von der Umwälzpumpe', 'WEMOS D1 mini': 'vorhanden/kaufen'}
rows = collections.OrderedDict()
for p in PARTS:
if p.get('board') is False or p['ref'].startswith('H'):
continue
key = (p['val'], p['fp'].split(':')[-1])
rows.setdefault(key, []).append(p)
with open(os.path.join(FAB, PROJECT + '_bom.csv'), 'w', newline='') as f:
w = csv.writer(f, delimiter=';')
w.writerow(['Anzahl', 'Referenzen', 'Wert', 'Footprint', 'Inventar-Fach', 'Beschaffung / Hinweis'])
for (val, fp), ps in rows.items():
refs = ' '.join(sorted((q['ref'] for q in ps), key=lambda r: (r.rstrip('0123456789'), int(''.join(c for c in r if c.isdigit()) or 0))))
term = ps[0]['fp'].startswith('Connector_Phoenix')
inv = '' if term else INV.get(val, '')
hint = ps[0].get('mpn') or BUY.get(val, '')
w.writerow([len(ps), refs, val, fp, inv, hint])
print('fab ok:', sorted(os.listdir(FAB)))
Binary file not shown.
+32
View File
@@ -0,0 +1,32 @@
Anzahl;Referenzen;Wert;Footprint;Inventar-Fach;Beschaffung / Hinweis
1;J2;NETZ 230V (PE/N/L);PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal;;Steckklemme 5,08 mm, 250 V / 12 A (Phoenix MSTBA 2,5/x-G-5,08 + MSTB 2,5/x-ST-5,08)
1;J4;THERMOSTAT (L/Ls);PhoenixContact_MSTBA_2,5_2-G-5,08_1x02_P5.08mm_Horizontal;;Steckklemme 5,08 mm, 250 V / 12 A (Phoenix MSTBA 2,5/x-G-5,08 + MSTB 2,5/x-ST-5,08)
1;J3;PUMPE 230V (L/N/PE);PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal;;Steckklemme 5,08 mm, 250 V / 12 A (Phoenix MSTBA 2,5/x-G-5,08 + MSTB 2,5/x-ST-5,08)
1;F1;T 2A;Fuseholder_Clip-5x20mm_Littelfuse_111_Inline_P20.00x5.00mm_D1.05mm_Horizontal;Clips/Halter B-12-2, Sicherung B-11-2;2x Sicherungsclip 5x20 (oder Halter aus B-12-2)
1;K1;SRD-05VDC-SL-C;Relay_SPDT_SANYOU_SRD_Series_Form_C;A-07-1 prüfen (sonst kaufen);
1;T1;ZMCT103C;ZMCT103C_Drahtbruecke;;kaufen (Stromwandler 5A/5mA)
1;J1;VIN 5V/3A (+/-);PhoenixContact_MSTBA_2,5_2-G-5,08_1x02_P5.08mm_Horizontal;;Steckklemme 5,08 mm (Phoenix MSTBA/MSTB oder 2EDGR/2EDGK kompatibel)
1;C1;1000u/10V;CP_Radial_D10.0mm_P5.00mm;C-05-3;
6;C2 C4 C6 C7 C8 C10;100n;C_Disc_D5.0mm_W2.5mm_P5.00mm;C-07-1;
1;R4;2k2;R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal;E-05-4;
1;D3;LED grün;LED_D3.0mm;B-07-5;
1;A1;WEMOS D1 mini;WEMOS_D1_mini_Socket;Buchsenleisten A-06-5 / A-07-3;vorhanden/kaufen
2;C3 C5;10u;CP_Radial_D5.0mm_P2.00mm;C-10-2 / D-04-3;
3;R1 R3 R11;1k;R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal;E-01-4;
5;R2 R6 R7 R9 R12;10k;R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal;E-01-5;
2;Q1 Q2;BC546B;TO-92_Inline_Wide;B-01-4;
1;D1;1N4007;D_DO-41_SOD81_P10.16mm_Horizontal;B-02-1;
1;D2;LED rot;LED_D3.0mm;B-08-5;
1;R5;100;R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal;E-01-3;
1;A2;ADS1115 Modul;ADS1115_Module_1x10;"Buchsenleiste A-06-5; Modul ggf. B-11-5";kaufen, falls kein Rest von der Umwälzpumpe
1;A3;MAX6675 Modul;MAX6675_Module_1x5;"Buchsenleiste A-06-5; Modul ggf. B-11-5";kaufen inkl. Thermoelement Typ K (Abgasfühler)
3;J5 J6 J7;DS18B20 (GND/DQ/3V3);PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal;;Steckklemme 5,08 mm (Phoenix MSTBA/MSTB oder 2EDGR/2EDGK kompatibel)
2;R8 R15;4k7;R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal;E-09-4;
1;J8;KLAPPE SERVO (GND/5V/SIG);PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal;;Steckklemme 5,08 mm (Phoenix MSTBA/MSTB oder 2EDGR/2EDGK kompatibel)
1;R10;330;R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal;E-07-3;
1;C9;470u/10V;CP_Radial_D8.0mm_P3.50mm;C-04-3;
1;J9;PWM-PUMPE (GND/FB/PWM);PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal;;Steckklemme 5,08 mm (Phoenix MSTBA/MSTB oder 2EDGR/2EDGK kompatibel)
1;R13;470;R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal;E-09-3;
1;JP1;Pull-up 5V;PinHeader_1x02_P2.54mm_Vertical;A-07-2 + Jumper A-11-3;
1;R14;220;R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal;E-05-3;
1;J10;ERWEITERUNG;PinHeader_1x09_P2.54mm_Vertical;A-07-2;
1 Anzahl Referenzen Wert Footprint Inventar-Fach Beschaffung / Hinweis
2 1 J2 NETZ 230V (PE/N/L) PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal Steckklemme 5,08 mm, 250 V / 12 A (Phoenix MSTBA 2,5/x-G-5,08 + MSTB 2,5/x-ST-5,08)
3 1 J4 THERMOSTAT (L/Ls) PhoenixContact_MSTBA_2,5_2-G-5,08_1x02_P5.08mm_Horizontal Steckklemme 5,08 mm, 250 V / 12 A (Phoenix MSTBA 2,5/x-G-5,08 + MSTB 2,5/x-ST-5,08)
4 1 J3 PUMPE 230V (L/N/PE) PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal Steckklemme 5,08 mm, 250 V / 12 A (Phoenix MSTBA 2,5/x-G-5,08 + MSTB 2,5/x-ST-5,08)
5 1 F1 T 2A Fuseholder_Clip-5x20mm_Littelfuse_111_Inline_P20.00x5.00mm_D1.05mm_Horizontal Clips/Halter B-12-2, Sicherung B-11-2 2x Sicherungsclip 5x20 (oder Halter aus B-12-2)
6 1 K1 SRD-05VDC-SL-C Relay_SPDT_SANYOU_SRD_Series_Form_C A-07-1 prüfen (sonst kaufen)
7 1 T1 ZMCT103C ZMCT103C_Drahtbruecke kaufen (Stromwandler 5A/5mA)
8 1 J1 VIN 5V/3A (+/-) PhoenixContact_MSTBA_2,5_2-G-5,08_1x02_P5.08mm_Horizontal Steckklemme 5,08 mm (Phoenix MSTBA/MSTB oder 2EDGR/2EDGK kompatibel)
9 1 C1 1000u/10V CP_Radial_D10.0mm_P5.00mm C-05-3
10 6 C2 C4 C6 C7 C8 C10 100n C_Disc_D5.0mm_W2.5mm_P5.00mm C-07-1
11 1 R4 2k2 R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal E-05-4
12 1 D3 LED grün LED_D3.0mm B-07-5
13 1 A1 WEMOS D1 mini WEMOS_D1_mini_Socket Buchsenleisten A-06-5 / A-07-3 vorhanden/kaufen
14 2 C3 C5 10u CP_Radial_D5.0mm_P2.00mm C-10-2 / D-04-3
15 3 R1 R3 R11 1k R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal E-01-4
16 5 R2 R6 R7 R9 R12 10k R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal E-01-5
17 2 Q1 Q2 BC546B TO-92_Inline_Wide B-01-4
18 1 D1 1N4007 D_DO-41_SOD81_P10.16mm_Horizontal B-02-1
19 1 D2 LED rot LED_D3.0mm B-08-5
20 1 R5 100 R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal E-01-3
21 1 A2 ADS1115 Modul ADS1115_Module_1x10 Buchsenleiste A-06-5; Modul ggf. B-11-5 kaufen, falls kein Rest von der Umwälzpumpe
22 1 A3 MAX6675 Modul MAX6675_Module_1x5 Buchsenleiste A-06-5; Modul ggf. B-11-5 kaufen inkl. Thermoelement Typ K (Abgasfühler)
23 3 J5 J6 J7 DS18B20 (GND/DQ/3V3) PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal Steckklemme 5,08 mm (Phoenix MSTBA/MSTB oder 2EDGR/2EDGK kompatibel)
24 2 R8 R15 4k7 R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal E-09-4
25 1 J8 KLAPPE SERVO (GND/5V/SIG) PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal Steckklemme 5,08 mm (Phoenix MSTBA/MSTB oder 2EDGR/2EDGK kompatibel)
26 1 R10 330 R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal E-07-3
27 1 C9 470u/10V CP_Radial_D8.0mm_P3.50mm C-04-3
28 1 J9 PWM-PUMPE (GND/FB/PWM) PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal Steckklemme 5,08 mm (Phoenix MSTBA/MSTB oder 2EDGR/2EDGK kompatibel)
29 1 R13 470 R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal E-09-3
30 1 JP1 Pull-up 5V PinHeader_1x02_P2.54mm_Vertical A-07-2 + Jumper A-11-3
31 1 R14 220 R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal E-05-3
32 1 J10 ERWEITERUNG PinHeader_1x09_P2.54mm_Vertical A-07-2
Binary file not shown.
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
(fp_lib_table (version 7)
(lib (name "ofensteuerung")(type "KiCad")(uri "${KIPRJMOD}/ofensteuerung.pretty")(options "")(descr ""))
)
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""Platzierte Platine (ohne Leiterbahnen) aus design.py erzeugen -> ofensteuerung_unrouted.kicad_pcb"""
import os, sys, json
import pcbnew
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from design import PARTS, PROJECT, W, H, MAINS, MAINS_AREA
REFPOS = {'A1': (81.4, 20.0)}
FPDIR = '/usr/share/kicad/footprints'
mm = pcbnew.FromMM
def V(x, y): return pcbnew.VECTOR2I(mm(x), mm(y))
symuuids = json.load(open(os.path.join(HERE, 'symuuids.json')))
board = pcbnew.LoadBoard(os.path.join(HERE, PROJECT + '_template.kicad_pcb')) if False else pcbnew.CreateEmptyBoard()
board.SetFileName(os.path.join(HERE, PROJECT + '.kicad_pcb'))
ds = board.GetDesignSettings()
ds.SetCopperLayerCount(2)
ns = ds.m_NetSettings
dnc = ns.GetDefaultNetclass()
dnc.SetTrackWidth(mm(0.4)); dnc.SetClearance(mm(0.25)); dnc.SetViaDiameter(mm(0.8)); dnc.SetViaDrill(mm(0.4))
pnc = pcbnew.NETCLASS('Power')
pnc.SetTrackWidth(mm(0.8)); pnc.SetClearance(mm(0.3)); pnc.SetViaDiameter(mm(1.0)); pnc.SetViaDrill(mm(0.5)); pnc.SetPriority(0)
ns.SetNetclass('Power', pnc)
pnc.SetTrackWidth(mm(1.0))
mnc = pcbnew.NETCLASS('Mains')
mnc.SetTrackWidth(mm(2.0)); mnc.SetClearance(mm(2.5)); mnc.SetViaDiameter(mm(2.2)); mnc.SetViaDrill(mm(1.0)); mnc.SetPriority(0)
ns.SetNetclass('Mains', mnc)
for pat in ('GND', '+5V', '+3V3'):
ns.SetNetclassPatternAssignment(pat, 'Power')
for n in MAINS:
ns.SetNetclassPatternAssignment('/' + n, 'Mains')
ns.SetNetclassPatternAssignment('unconnected-(K1-*', 'Mains')
ds.m_TrackMinWidth = mm(0.25); ds.m_MinClearance = mm(0.2); ds.m_CopperEdgeClearance = mm(0.4)
nets = {}
POWER = ('GND', '+5V', '+3V3')
def net(name):
name = name if (name in POWER or name.startswith('unconnected-')) else '/' + name
if name not in nets:
n = pcbnew.NETINFO_ITEM(board, name); board.Add(n); nets[name] = n
return nets[name]
for p in PARTS:
if p.get('board') is False or not p['fp']:
continue
lib, name = p['fp'].split(':')
libpath = os.path.join(HERE, lib + '.pretty') if lib == PROJECT else os.path.join(FPDIR, lib + '.pretty')
fp = pcbnew.FootprintLoad(libpath, name)
if fp is None:
raise SystemExit('fp missing ' + p['fp'])
fp.SetFPID(pcbnew.LIB_ID(lib, name))
fp.SetReference(p['ref']); fp.SetValue(p['val'])
fp.SetPath(pcbnew.KIID_PATH('/' + symuuids[p['ref']]))
x, y, rot = p['pcb']
fp.SetPosition(V(x, y))
fp.SetOrientationDegrees(rot)
board.Add(fp)
for pad in fp.Pads():
n = p['pins'].get(pad.GetNumber())
if n:
pad.SetNet(net(n))
elif pad.GetNumber() and pad.GetNumber() in p['pins']:
nm = p.get('ncname', {}).get(pad.GetNumber(), '')
pad.SetNet(net(f"unconnected-({p['ref']}-{nm + '-' if nm else ''}Pad{pad.GetNumber()})"))
for k in ('Description', 'Bezugsquelle'):
v = p.get({'Description': 'desc', 'Bezugsquelle': 'mpn'}[k], '')
if k == 'Description' or v:
try:
fld = fp.GetField(k) if fp.HasField(k) else None
if fld is None:
fld = pcbnew.PCB_FIELD(fp, fp.GetNextFieldId() if hasattr(fp,'GetNextFieldId') else -1, k); fp.Add(fld)
fld.SetText(v); fld.SetVisible(False); fld.SetLayer(pcbnew.F_Fab)
except Exception as e:
print('field', k, e)
# Referenz klein, Value auf Fab
fp.Reference().SetTextSize(V(0.9, 0.9)); fp.Reference().SetTextThickness(mm(0.14))
fp.Value().SetLayer(pcbnew.F_Fab)
if p['fp'].split(':')[0] in ('Resistor_THT', 'Diode_THT') or p['fp'].startswith('Capacitor_THT:C_Disc'):
pads = list(fp.Pads())
a, b2 = pads[0].GetPosition(), pads[1].GetPosition()
fp.Reference().SetPosition(pcbnew.VECTOR2I((a.x + b2.x) // 2, (a.y + b2.y) // 2))
fp.Reference().SetTextAngleDegrees(rot if rot in (0, 90) else rot - 180)
fp.Reference().SetTextSize(V(0.8, 0.8)); fp.Reference().SetTextThickness(mm(0.13))
if p['ref'] in REFPOS:
fp.Reference().SetPosition(V(*REFPOS[p['ref']])); fp.Reference().SetTextAngleDegrees(0)
if p['ref'] in ('J10',):
fp.Reference().SetVisible(False)
if p['fp'].startswith('Connector_Phoenix'):
npins = len(p['pins']); sgn = 1 if rot == 0 else -1
fp.Reference().SetPosition(V(x + sgn * 5.08 * (npins - 1) + sgn * 2.6, y + (-2.0 if rot == 180 else 2.0)))
fp.Reference().SetTextAngleDegrees(0)
# Platinenumriss mit abgerundeten Ecken
def seg(a, b):
s = pcbnew.PCB_SHAPE(board); s.SetShape(pcbnew.SHAPE_T_SEGMENT); s.SetStart(V(*a)); s.SetEnd(V(*b))
s.SetLayer(pcbnew.Edge_Cuts); s.SetWidth(mm(0.1)); board.Add(s)
def arc(c, start, ang):
s = pcbnew.PCB_SHAPE(board); s.SetShape(pcbnew.SHAPE_T_ARC); s.SetCenter(V(*c)); s.SetStart(V(*start))
s.SetArcAngleAndEnd(pcbnew.EDA_ANGLE(ang, pcbnew.DEGREES_T)); s.SetLayer(pcbnew.Edge_Cuts); s.SetWidth(mm(0.1)); board.Add(s)
r = 2.0
seg((r, 0), (W - r, 0)); seg((W, r), (W, H - r)); seg((W - r, H), (r, H)); seg((0, H - r), (0, r))
arc((W - r, r), (W - r, 0), 90); arc((W - r, H - r), (W, H - r), 90); arc((r, H - r), (r, H), 90); arc((r, r), (0, r), 90)
# GND-Flächen beidseitig
def zone(layer, netname, pts, keepout=False):
z = pcbnew.ZONE(board)
z.SetLayer(layer)
if keepout:
z.SetIsRuleArea(True); z.SetDoNotAllowTracks(True); z.SetDoNotAllowVias(True); z.SetDoNotAllowZoneFills(True)
z.SetDoNotAllowPads(False); z.SetDoNotAllowFootprints(False)
ls = pcbnew.LSET(); ls.AddLayer(pcbnew.F_Cu); ls.AddLayer(pcbnew.B_Cu); z.SetLayerSet(ls)
else:
z.SetNet(net(netname)); z.SetMinThickness(mm(0.25)); z.SetLocalClearance(mm(0.4))
z.SetPadConnection(pcbnew.ZONE_CONNECTION_THERMAL); z.SetThermalReliefGap(mm(0.5)); z.SetThermalReliefSpokeWidth(mm(0.6))
z.SetAssignedPriority(0)
ol = z.Outline(); ol.NewOutline()
for (x, y) in pts:
ol.Append(mm(x), mm(y))
board.Add(z)
return z
for L in (pcbnew.F_Cu, pcbnew.B_Cu):
zone(L, 'GND', [(0.5, 0.5), (W - 0.5, 0.5), (W - 0.5, H - 0.5), (0.5, H - 0.5)])
# Antennen-Freifläche unter dem ESP8266-Modul (Antenne zeigt zum oberen Rand)
zone(pcbnew.F_Cu, None, [(68.6, 1.0), (94.2, 1.0), (94.2, 13.0), (68.6, 13.0)], keepout=True)
# 230-V-Bereich: keine GND-Fläche (beide Lagen)
z = pcbnew.ZONE(board); z.SetIsRuleArea(True); z.SetDoNotAllowZoneFills(True); z.SetDoNotAllowTracks(False); z.SetDoNotAllowVias(False)
z.SetDoNotAllowPads(False); z.SetDoNotAllowFootprints(False); z.SetZoneName('NETZ_230V')
ls = pcbnew.LSET(); ls.AddLayer(pcbnew.F_Cu); ls.AddLayer(pcbnew.B_Cu); z.SetLayerSet(ls)
ol = z.Outline(); ol.NewOutline()
for (x, y) in MAINS_AREA:
ol.Append(mm(x), mm(y))
board.Add(z)
# Kleinspannungs-Stummel aus dem 230-V-Bereich (CT-Sekundärseite, Relaisspule), gesperrt
def padpos(ref, num):
for fp in board.GetFootprints():
if fp.GetReference() == ref:
for pd in fp.Pads():
if pd.GetNumber() == num:
p = pd.GetPosition(); return (pcbnew.ToMM(p.x), pcbnew.ToMM(p.y))
raise KeyError((ref, num))
def track(netname, pts, width):
for a, b in zip(pts, pts[1:]):
t = pcbnew.PCB_TRACK(board); t.SetStart(V(*a)); t.SetEnd(V(*b)); t.SetWidth(mm(width)); t.SetLayer(pcbnew.F_Cu)
t.SetNet(net(netname)); t.SetLocked(True); board.Add(t)
YO = MAINS_AREA[2][1] + 1.0
vm = padpos('T1', '2'); ca = padpos('T1', '1'); k5 = padpos('K1', '5'); k2 = padpos('K1', '2')
track('VMID', [vm, (vm[0] - 2.0, vm[1] + 2.0), (vm[0] - 2.0, YO)], 0.4)
track('CT_A', [ca, (ca[0] + 2.0, ca[1] + 2.0), (ca[0] + 2.0, YO)], 0.4)
track('+5V', [k5, (k5[0] - 1.5, k5[1] + 1.5), (k5[0] - 1.5, YO)], 1.0)
track('RELAY_C', [k2, (k2[0] + 1.5, k2[1] + 1.5), (k2[0] + 1.5, YO)], 0.6)
# Trennlinie 230 V auf dem Bestückungsdruck
for a, b in (((MAINS_AREA[1][0], 1.5), MAINS_AREA[2]), (MAINS_AREA[2], (1.5, MAINS_AREA[2][1]))):
s = pcbnew.PCB_SHAPE(board); s.SetShape(pcbnew.SHAPE_T_SEGMENT); s.SetStart(V(*a)); s.SetEnd(V(*b))
s.SetLayer(pcbnew.F_SilkS); s.SetWidth(mm(0.3)); board.Add(s)
# Randstreifen: keine Leiterbahnen näher als 0,8 mm am Rand
for pts in ([(0, 0), (W, 0), (W, 0.8), (0, 0.8)], [(0, H - 0.8), (W, H - 0.8), (W, H), (0, H)],
[(0, 0), (0.8, 0), (0.8, H), (0, H)], [(W - 0.8, 0), (W, 0), (W, H), (W - 0.8, H)]):
z = zone(pcbnew.F_Cu, None, pts, keepout=True); z.SetDoNotAllowZoneFills(False)
# Beschriftung
def text(t, x, y, size=1.2, layer='F.SilkS', rot=0, bold=False):
layer = {'F.SilkS': pcbnew.F_SilkS, 'B.SilkS': pcbnew.B_SilkS}[layer]
s = pcbnew.PCB_TEXT(board); s.SetText(t)
if layer == pcbnew.B_SilkS: s.SetMirrored(True)
s.SetPosition(V(x, y)); s.SetLayer(layer)
s.SetTextSize(V(size, size)); s.SetTextThickness(mm(size * (0.2 if bold else 0.15))); s.SetTextAngleDegrees(rot)
board.Add(s)
from design import SILK
for args in SILK:
text(*args)
ns.RecomputeEffectiveNetclasses() if hasattr(ns,'RecomputeEffectiveNetclasses') else None
board.SynchronizeNetsAndNetClasses(True)
pcbnew.SaveBoard(os.path.join(HERE, PROJECT + '_unrouted.kicad_pcb'), board)
print('saved', len(board.GetFootprints()), 'footprints')
+581
View File
@@ -0,0 +1,581 @@
#!/usr/bin/env python3
"""Erzeugt Schaltplan (.kicad_sch) + Projekt-Libs aus design.py (eine Quelle für Schaltplan und Platine)."""
import re, uuid, os, sys, json
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from design import PARTS, PROJECT, NOCONNECT
SYMDIR = '/usr/share/kicad/symbols'
HERE = os.path.dirname(os.path.abspath(__file__))
U = lambda: str(uuid.uuid5(uuid.NAMESPACE_URL, PROJECT + ':' + '|'.join(map(str, _ctr.__next__()))))
def _gen():
i = 0
while True:
i += 1; yield (i,)
_ctr = _gen()
# ---------- minimal s-expression helpers ----------
def tokenize(s):
return re.findall(r'\(|\)|"(?:\\.|[^"\\])*"|[^\s()"]+', s)
def parse(tokens, i=0):
out = []
while i < len(tokens):
t = tokens[i]
if t == '(':
sub, i = parse(tokens, i + 1); out.append(sub)
elif t == ')':
return out, i + 1
else:
out.append(t); i += 1
return out, i
def dump(x, ind=0):
if not isinstance(x, list):
return x
if all(not isinstance(e, list) for e in x):
return '(' + ' '.join(x) + ')'
s = '(' + ' '.join(e for e in x[:2] if not isinstance(e, list))
rest = x[2:] if len(x) > 1 and not isinstance(x[1], list) else x[1:]
if isinstance(x[1], list):
s = '(' + x[0]; rest = x[1:]
for e in rest:
s += '\n' + ' ' * (ind + 1) + (dump(e, ind + 1) if isinstance(e, list) else e)
return s + ')'
def q(s):
return '"' + str(s).replace('\\', '\\\\').replace('"', '\\"') + '"'
_libcache = {}
def lib_symbol(libid):
lib, name = libid.split(':')
if lib == PROJECT:
path = os.path.join(HERE, PROJECT + '.kicad_sym')
else:
path = os.path.join(SYMDIR, lib + '.kicad_sym')
if path not in _libcache:
_libcache[path] = parse(tokenize(open(path).read()))[0][0]
for e in _libcache[path]:
if isinstance(e, list) and e[0] == 'symbol' and e[1] == q(name):
if any(isinstance(k, list) and k[0] == 'extends' for k in e):
raise SystemExit('extends not supported: ' + libid)
import copy
s = copy.deepcopy(e); s[1] = q(libid)
return s
raise SystemExit('symbol not found ' + libid)
def pins_of(sym):
res = {}
def walk(x):
for e in x:
if isinstance(e, list):
if e[0] == 'pin':
at = next(k for k in e if isinstance(k, list) and k[0] == 'at')
num = next(k for k in e if isinstance(k, list) and k[0] == 'number')[1].strip('"')
res[num] = (float(at[1]), float(at[2]), int(float(at[3])) if len(at) > 3 else 0)
else:
walk(e)
walk(sym)
return res
# ---------- project symbol library (Wemos, ADS1115 module) ----------
def rect_symbol(name, ref, left, right, w=15.24, desc=''):
"""left/right: list of (number, name, type). pins 2.54 apart."""
n = max(len(left), len(right))
h = (n + 1) * 2.54
top = h / 2
pins = []
for i, (num, nm, tp) in enumerate(left):
y = top - 2.54 * (i + 1)
pins.append(f'(pin {tp} line (at {-w/2-2.54:.2f} {y:.2f} 0) (length 2.54) (name {q(nm)} (effects (font (size 1.27 1.27)))) (number {q(num)} (effects (font (size 1.27 1.27)))))')
for i, (num, nm, tp) in enumerate(right):
y = top - 2.54 * (i + 1)
pins.append(f'(pin {tp} line (at {w/2+2.54:.2f} {y:.2f} 180) (length 2.54) (name {q(nm)} (effects (font (size 1.27 1.27)))) (number {q(num)} (effects (font (size 1.27 1.27)))))')
return f'''(symbol {q(name)} (pin_names (offset 1.016)) (exclude_from_sim no) (in_bom yes) (on_board yes)
(property "Reference" {q(ref)} (at 0 {top+1.27:.2f} 0) (effects (font (size 1.27 1.27))))
(property "Value" {q(name)} (at 0 {-top-1.27:.2f} 0) (effects (font (size 1.27 1.27))))
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Description" {q(desc)} (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(symbol {q(name + '_0_1')} (rectangle (start {-w/2:.2f} {top:.2f}) (end {w/2:.2f} {-top:.2f}) (stroke (width 0.254) (type default)) (fill (type background))))
(symbol {q(name + '_1_1')} {' '.join(pins)}))'''
def write_projlib():
wemos = rect_symbol('WEMOS_D1_mini', 'A',
[('1', 'RST', 'input'), ('2', 'A0', 'input'), ('3', 'D0/GPIO16', 'bidirectional'), ('4', 'D5/GPIO14', 'bidirectional'),
('5', 'D6/GPIO12', 'bidirectional'), ('6', 'D7/GPIO13', 'bidirectional'), ('7', 'D8/GPIO15', 'bidirectional'), ('8', '3V3', 'power_out')],
[('9', 'TX/GPIO1', 'bidirectional'), ('10', 'RX/GPIO3', 'bidirectional'), ('11', 'D1/GPIO5', 'bidirectional'), ('12', 'D2/GPIO4', 'bidirectional'),
('13', 'D3/GPIO0', 'bidirectional'), ('14', 'D4/GPIO2', 'bidirectional'), ('15', 'GND', 'power_in'), ('16', '5V', 'power_in')],
w=30.48, desc='Wemos D1 mini (ESP8266) auf 2x 1x8 Buchsenleisten')
ads = rect_symbol('ADS1115_Modul', 'A',
[('1', 'VDD', 'power_in'), ('2', 'GND', 'power_in'), ('3', 'SCL', 'input'), ('4', 'SDA', 'bidirectional'), ('5', 'ADDR', 'input')],
[('6', 'ALRT', 'output'), ('7', 'A0', 'input'), ('8', 'A1', 'input'), ('9', 'A2', 'input'), ('10', 'A3', 'input')],
w=12.7, desc='ADS1115 16-bit ADC Breakout (HW-ADS1115/CJMCU, 1x10 Pin)')
ct = rect_symbol('ZMCT103C', 'T',
[('3', 'P1 (L_SW)', 'passive'), ('4', 'P2 (L_PUMP)', 'passive')],
[('1', 'S1', 'passive'), ('2', 'S2', 'passive')],
w=15.24, desc='Stromwandler ZMCT103C 1000:1, Primär = Drahtbrücke durchs Loch')
mx = rect_symbol('MAX6675_Modul', 'A',
[('1', 'GND', 'power_in'), ('2', 'VCC', 'power_in'), ('3', 'SCK', 'input'), ('4', 'CS', 'input'), ('5', 'SO', 'output')],
[], w=12.7, desc='MAX6675 Thermoelement-Modul (Typ K), 1x5 Pin')
open(os.path.join(HERE, PROJECT + '.kicad_sym'), 'w').write(
'(kicad_symbol_lib (version 20231120) (generator "gen_sch.py")\n' + wemos + '\n' + ads + '\n' + ct + '\n' + mx + '\n)\n')
# ---------- geometry ----------
import math, heapq
from sch_layout import BLOCKS, LABELS, PSYM, NOTES as LNOTES
GRID = 1.27
POWERNETS = ('GND', '+5V', '+3V3')
def pin_lengths(sym):
res = {}
def walk(x):
for e in x:
if isinstance(e, list):
if e[0] == 'pin':
num = next(k for k in e if isinstance(k, list) and k[0] == 'number')[1].strip('"')
ln = next((k for k in e if isinstance(k, list) and k[0] == 'length'), ['length', '2.54'])
res[num] = float(ln[1])
else:
walk(e)
walk(sym)
return res
def graphic_points(sym):
pts = []
def walk(x):
for e in x:
if isinstance(e, list):
if e[0] in ('rectangle',):
for k in e:
if isinstance(k, list) and k[0] in ('start', 'end'):
pts.append((float(k[1]), float(k[2])))
elif e[0] in ('polyline', 'bezier'):
for k in e:
if isinstance(k, list) and k[0] == 'pts':
pts.extend((float(p[1]), float(p[2])) for p in k[1:])
elif e[0] == 'circle':
c = next(k for k in e if isinstance(k, list) and k[0] == 'center')
r = float(next(k for k in e if isinstance(k, list) and k[0] == 'radius')[1])
pts.extend([(float(c[1]) - r, float(c[2]) - r), (float(c[1]) + r, float(c[2]) + r)])
elif e[0] == 'arc':
for k in e:
if isinstance(k, list) and k[0] in ('start', 'mid', 'end'):
pts.append((float(k[1]), float(k[2])))
elif e[0] in ('pin', 'property'):
pass
else:
walk(e)
walk(sym)
return pts
def xf(px, py, rot, mirror):
"""lib point (y up) -> screen offset (y down)"""
x, y = px, -py
if mirror == 'y':
x = -x
elif mirror == 'x':
y = -y
r = math.radians(rot)
c, s = round(math.cos(r)), round(math.sin(r))
return (x * c + y * s, -x * s + y * c)
DIRV = {0: (1, 0), 90: (0, -1), 180: (-1, 0), 270: (0, 1)} # screen
def vdir(v):
return {(1, 0): 0, (0, -1): 90, (-1, 0): 180, (0, 1): 270}[(int(round(v[0])), int(round(v[1])))]
def G(v):
return int(round(v / GRID))
# ---------- router ----------
class Grid:
def __init__(self):
self.block = {} # cell -> 'hard'
self.soft = set()
self.occ = {} # cell -> {net: set(dirs 'h'/'v')}
self.nodes = {} # cell -> net (pins, label anchors, wire corners/ends: no foreign wire may pass)
self.edges = {} # net -> set of (cellA, cellB)
def add_edge(self, net, a, b):
e = (min(a, b), max(a, b))
self.edges.setdefault(net, set()).add(e)
d = 'h' if a[1] == b[1] else 'v'
for c in (a, b):
self.occ.setdefault(c, {}).setdefault(net, set()).add(d)
def tree(self, net):
cells = set()
for a, b in self.edges.get(net, ()):
cells.add(a); cells.add(b)
cells |= {c for c, n in self.nodes.items() if n == net}
return cells
def passable(self, c, net, d):
"""may net pass straight through c in direction d ('h'/'v')?"""
if c in self.block:
return False
n = self.nodes.get(c)
if n is not None and n != net:
return False
o = self.occ.get(c, {})
for other, dirs in o.items():
if other == net:
continue
if d in dirs or len(dirs) != 1:
return False
# other must go straight through (2 edges same axis)
deg = sum(1 for (a, b) in self.edges[other] if a == c or b == c)
if deg != 2:
return False
return True
def can_turn_or_end(self, c, net):
o = self.occ.get(c, {})
return all(other == net for other in o) and self.nodes.get(c, net) == net and c not in self.block
def route(self, net, src, targets, bounds):
"""A* from src to any target cell; returns list of cells"""
(x0, y0, x1, y1) = bounds
def h(c):
return min(abs(c[0] - t[0]) + abs(c[1] - t[1]) for t in targets) if len(targets) < 40 else 0
start = (src, None)
pq = [(h(src), 0, src, None)]
best = {start: 0}
prev = {}
while pq:
f, g, c, d = heapq.heappop(pq)
if g > best.get((c, d), 1e9):
continue
if c in targets and c != src:
if self.can_turn_or_end(c, net) or (d is not None and self._t_ok(c, net)):
path = [c]; k = (c, d)
while k in prev:
k = prev[k]; path.append(k[0])
return path[::-1]
for nd, (dx, dy) in DIRV.items():
n = (c[0] + dx, c[1] + dy)
if not (x0 <= n[0] <= x1 and y0 <= n[1] <= y1):
continue
if d is not None and nd != d and (nd - d) % 180 == 0:
continue # no reversing
turning = d is not None and nd != d
if turning and c != src and not self.can_turn_or_end(c, net):
continue
if c == src and d is None:
pass
ax = 'h' if dx else 'v'
if n in targets:
cost = 1
else:
if not self.passable(n, net, ax):
continue
cost = 1 + (6 if n in self.soft else 0) + (3 if self.occ.get(n) else 0)
cost += 4 if turning else 0
ng = g + cost
if ng < best.get((n, nd), 1e9):
best[(n, nd)] = ng; prev[(n, nd)] = (c, d)
heapq.heappush(pq, (ng + h(n), ng, n, nd))
return None
def _t_ok(self, c, net):
return self.can_turn_or_end(c, net)
# ---------- schematic ----------
def main():
write_projlib()
root = str(uuid.uuid5(uuid.NAMESPACE_URL, PROJECT + ':root'))
parts = {p['ref']: p for p in PARTS}
place = {}
blockof = {}
for b, d in BLOCKS.items():
for ref, v in d.items():
place[ref] = v; blockof[ref] = b
missing = set(parts) - set(place)
if missing:
raise SystemExit('nicht platziert: %s' % sorted(missing))
libsyms = {}
grid = Grid()
items = []
symuuids = {}
pinpos = {} # (ref, num) -> (cell, outward dir)
textboxes = []
for ref, p in parts.items():
lid = p['sym']
if lid not in libsyms:
libsyms[lid] = lib_symbol(lid)
sym = libsyms[lid]
x0, y0, rot, mir = place[ref]
pins = pins_of(sym); plen = pin_lengths(sym)
# body
gp = [xf(px, py, rot, mir) for px, py in graphic_points(sym)]
if gp:
bx0 = min(v[0] for v in gp) + x0; bx1 = max(v[0] for v in gp) + x0
by0 = min(v[1] for v in gp) + y0; by1 = max(v[1] for v in gp) + y0
for i in range(G(bx0), G(bx1) + 1):
for j in range(G(by0), G(by1) + 1):
grid.block[(i, j)] = ref
else:
bx0 = bx1 = x0; by0 = by1 = y0
for num, (px, py, ang) in pins.items():
ox, oy = xf(px, py, rot, mir)
cx, cy = x0 + ox, y0 + oy
ldx, ldy = xf(math.cos(math.radians(ang)), math.sin(math.radians(ang)), rot, mir) # toward body
out = vdir((-ldx, -ldy))
cell = (G(cx), G(cy))
pinpos[(ref, num)] = (cell, out)
# pin line cells (between endpoint and body) are blocked
L = int(round(plen.get(num, 2.54) / GRID))
for k in range(1, L + 1):
grid.block[(cell[0] + int(round(ldx)) * k, cell[1] + int(round(ldy)) * k)] = ref
grid.nodes[cell] = None # filled with net below
su = str(uuid.uuid5(uuid.NAMESPACE_URL, PROJECT + ':sym:' + ref))
symuuids[ref] = su
# fields: right of body for vertical/narrow parts, above for wide parts
pc = [xf(px, py, rot, mir) for px, py, a in pins.values()]
wide = len(pc) == 2 and abs(pc[0][1] - pc[1][1]) < 0.1 and not lid.startswith(PROJECT)
big = lid.startswith(PROJECT) or lid.startswith('Regulator') or lid.startswith('Connector') or lid.startswith('Jumper') or ref.startswith('H')
if ref.startswith('#'):
rpos = vpos = None
elif big:
rpos = (round((bx0 + bx1) / 2, 2), round(by0 - 3.81, 2), 'center'); vpos = (round((bx0 + bx1) / 2, 2), round(by0 - 1.6, 2), 'center')
if ref.startswith('H'):
rpos = (x0 + 3.81, y0 - 1.27, 'left'); vpos = (x0 + 3.81, y0 + 1.27, 'left')
elif wide:
rpos = (round((bx0 + bx1) / 2, 2), round(by0 - 1.9, 2), 'center'); vpos = (round((bx0 + bx1) / 2, 2), round(by1 + 2.0, 2), 'center')
else:
rpos = (round(bx1 + 1.6, 2), round((by0 + by1) / 2 - 0.8, 2), 'left'); vpos = (round(bx1 + 1.6, 2), round((by0 + by1) / 2 + 1.6, 2), 'left')
for pos in (rpos, vpos):
if pos:
for i in range(G(pos[0]) - (3 if pos[2] == 'center' else 0), G(pos[0]) + 5):
grid.soft.add((i, G(pos[1]))); grid.soft.add((i, G(pos[1]) - 1))
props = [('Reference', ref, rpos), ('Value', p['val'], vpos), ('Footprint', p['fp'], None), ('Datasheet', '', None),
('Description', p.get('desc', ''), None)]
if p.get('mpn'):
props.append(('Bezugsquelle', p['mpn'], None))
pr = ''
for k, v, pos in props:
if pos is None:
pr += f' (property {q(k)} {q(v)} (at {x0} {y0} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
else:
just = '' if pos[2] == 'center' else ' (justify left)'
fa = 90 if rot in (90, 270) else 0
pr += f' (property {q(k)} {q(v)} (at {pos[0]:.2f} {pos[1]:.2f} {fa}) (effects (font (size 1.27 1.27)){just}))\n'
pinstr = ''.join(f' (pin {q(n)} (uuid {U()}))\n' for n in pins)
dnp = 'yes' if p.get('dnp') else 'no'
onb = 'no' if p.get('board') is False else 'yes'
inbom = 'no' if ref.startswith('H') or ref.startswith('#') else 'yes'
mirs = f' (mirror {mir})' if mir else ''
items.append(f'''(symbol (lib_id {q(lid)}) (at {x0} {y0} {rot}){mirs} (unit 1) (exclude_from_sim no) (in_bom {inbom}) (on_board {onb}) (dnp {dnp}) (uuid {su})
{pr}{pinstr} (instances (project {q(PROJECT)} (path {q('/' + root)} (reference {q(ref)}) (unit 1)))))''')
for (ref, num), (cell, _o) in pinpos.items():
grid.block.pop(cell, None)
dx, dy = DIRV[_o]
for k in range(1, 4): # Grafik (z.B. LED-Pfeile) darf den Pinausgang nicht zustellen
c2 = (cell[0] + dx * k, cell[1] + dy * k)
if grid.block.get(c2) == ref:
grid.block.pop(c2)
if parts[ref]['sym'].startswith('power:'):
for dx in (-1, 0, 1):
grid.block.pop((cell[0] + dx, cell[1]), None)
# nets -> pins grouped per block
netpins = {}
for ref, p in parts.items():
for num, net in p['pins'].items():
cell, out = pinpos[(ref, num)]
if net is None:
grid.nodes[cell] = '__nc__'
cx, cy = cell[0] * GRID, cell[1] * GRID
items.append(f'(no_connect (at {cx:.2f} {cy:.2f}) (uuid {U()}))')
continue
grid.nodes[cell] = net
netpins.setdefault((net, blockof[ref] if net in POWERNETS else '*'), []).append((ref, num))
for ref, p in parts.items(): # pins without a net entry (shouldn't exist)
pass
def W(a, b):
items.append(f'(wire (pts (xy {a[0]*GRID:.2f} {a[1]*GRID:.2f}) (xy {b[0]*GRID:.2f} {b[1]*GRID:.2f})) (stroke (width 0) (type default)) (uuid {U()}))')
pwr_n = [0]
def power_symbol(net, cell, out=None):
"""power symbol with its pin at cell. out = direction of the stub that leads to it (None = free)."""
plid = 'power:' + net
if plid not in libsyms:
libsyms[plid] = lib_symbol(plid)
if net == 'GND':
rot = 180 if out == 90 else ((out - 270) % 360 if out in (0, 180) else 0)
else:
rot = 180 if out == 270 else ((out - 90) % 360 if out in (0, 180) else 0)
pwr_n[0] += 1
ref = '#PWR' + str(pwr_n[0]).zfill(3)
ex, ey = cell[0] * GRID, cell[1] * GRID
bodyd = ({0: 270, 90: 0, 180: 90, 270: 180} if net == 'GND' else {0: 90, 90: 180, 180: 270, 270: 0})[rot]
bdx, bdy = DIRV[bodyd]
tdx, tdy = bdx * (3.3 + 0.45 * len(net)), bdy * 4.0
items.append(f'''(symbol (lib_id {q(plid)}) (at {ex:.2f} {ey:.2f} {rot}) (unit 1) (exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no) (uuid {U()})
(property "Reference" {q(ref)} (at {ex:.2f} {ey:.2f} 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Value" {q(net)} (at {ex + tdx:.2f} {ey + tdy:.2f} {90 if rot in (90, 270) else 0}) (effects (font (size 1.0 1.0))))
(property "Footprint" "" (at {ex:.2f} {ey:.2f} 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Datasheet" "" (at {ex:.2f} {ey:.2f} 0) (effects (font (size 1.27 1.27)) (hide yes)))
(pin "1" (uuid {U()}))
(instances (project {q(PROJECT)} (path {q('/' + root)} (reference {q(ref)}) (unit 1)))))''')
for k in range(1, 3):
for w in (-1, 0, 1):
c2 = (cell[0] + bdx * k + (w if bdy else 0), cell[1] + bdy * k + (w if bdx else 0))
grid.block.setdefault(c2, 'pwr')
def label(net, cell, d):
ang, just = {'right': (0, 'left'), 'left': (180, 'right'), 'up': (90, 'left'), 'down': (270, 'right')}[d]
items.append(f'(label {q(net)} (at {cell[0]*GRID:.2f} {cell[1]*GRID:.2f} {ang}) (effects (font (size 1.27 1.27)) (justify {just} bottom)) (uuid {U()}))')
grid.nodes[cell] = net
dx, dy = DIRV[{'right': 0, 'left': 180, 'up': 90, 'down': 270}[d]]
for k in range(1, len(net) + 2):
grid.block.setdefault((cell[0] + dx * k, cell[1] + dy * k), 'lbl')
if dx:
grid.block.setdefault((cell[0] + dx * k, cell[1] - 1), 'lbl')
# explicit labels + power symbols become nodes of their (net, block)
extra = {}
for b, net, x, y, d in LABELS:
c = (G(x), G(y)); label(net, c, d); extra.setdefault((net, b), []).append(c)
for b, net, x, y in PSYM:
c = (G(x), G(y)); grid.nodes[c] = net; power_symbol(net, c, None); extra.setdefault((net, b), []).append(c)
for (nx, ny, size, text) in LNOTES: # Überschriften nicht überfahren
for i in range(G(nx) - 1, G(nx + len(text) * size * 0.95) + 2):
for j in range(G(ny - size * 1.3), G(ny) + 2):
grid.block.setdefault((i, j), 'note')
# 1) stubs for everything that is not wired inside its block
wired = []
for (net, b), pl in sorted(netpins.items()):
nodes = [pinpos[x][0] for x in pl] + extra.get((net, b), [])
if net in POWERNETS and (net, b) not in extra:
for x in pl:
cell, out = pinpos[x]
dx, dy = DIRV[out]; e = (cell[0] + 2 * dx, cell[1] + 2 * dy)
grid.add_edge(net, cell, (cell[0] + dx, cell[1] + dy)); grid.add_edge(net, (cell[0] + dx, cell[1] + dy), e)
grid.nodes[e] = net
W(cell, e); _emitted.add((net, cell, e)); power_symbol(net, e, out)
elif len(nodes) == 1:
cell, out = pinpos[pl[0]]
dx, dy = DIRV[out]; e = (cell[0] + 2 * dx, cell[1] + 2 * dy)
grid.add_edge(net, cell, (cell[0] + dx, cell[1] + dy)); grid.add_edge(net, (cell[0] + dx, cell[1] + dy), e)
W(cell, e); _emitted.add((net, cell, e))
label(net, e, {0: 'right', 180: 'left', 90: 'up', 270: 'down'}[out])
else:
wired.append((net, b, nodes, pl))
# 2) route wired nets: shortest nets first
def span(n):
xs = [c[0] for c in n[2]]; ys = [c[1] for c in n[2]]
return (max(xs) - min(xs)) + (max(ys) - min(ys))
failed = []
for net, b, nodes, pl in sorted(wired, key=span):
xs = [c[0] for c in nodes]; ys = [c[1] for c in nodes]
bounds = (min(xs) - 40, min(ys) - 40, max(xs) + 40, max(ys) + 40)
# start from the node nearest to the centroid
cx, cy = sum(xs) / len(xs), sum(ys) / len(ys)
order = sorted(nodes, key=lambda c: abs(c[0] - cx) + abs(c[1] - cy))
connected = {order[0]}
for c in order[1:]:
if c in grid.tree(net) & connected or c in connected:
continue
tgt = (grid.tree(net) & (connected | {cc for e in grid.edges.get(net, ()) for cc in e})) | connected
# pins must leave in their outward direction: start one step out if it is a pin
path = grid.route(net, c, tgt, bounds)
if path is None:
if os.environ.get('DBG'):
print('DBG', net, 'src', c, 'block', grid.block.get(c), 'node', grid.nodes.get(c), 'occ', grid.occ.get(c), 'ntgt', len(tgt))
for dx,dy in ((1,0),(-1,0),(0,1),(0,-1)):
n=(c[0]+dx,c[1]+dy); print(' nb', n, grid.block.get(n), grid.nodes.get(n), grid.occ.get(n))
failed.append((net, b, c)); continue
for a, bb in zip(path, path[1:]):
grid.add_edge(net, a, bb)
connected |= set(path)
if failed:
print('ROUTING FAILED:', failed)
# 2b) name every wired signal net with a small label on a straight wire run (keeps PCB net names)
labelled = {(net, b) for b, net, x, y, d in LABELS} | {(net, b) for b, net, x, y in PSYM}
for net, b, nodes, pl in wired:
if net in POWERNETS or (net, b) in labelled:
continue
es = grid.edges.get(net, set())
hcells = {}
for a, c in es:
if a[1] == c[1]:
hcells.setdefault(a, set()).add(c); hcells.setdefault(c, set()).add(a)
need = int(len(net) * 1.0) + 1
best = None
for c in sorted(hcells, key=lambda c: (c[1], c[0])):
run = [c]
while (run[-1][0] + 1, run[-1][1]) in hcells.get(run[-1], ()) and len(run) <= need:
run.append((run[-1][0] + 1, run[-1][1]))
if len(run) > need and all((r[0], r[1] - 1) not in grid.block and (r[0], r[1] - 1) not in grid.soft for r in run):
best = c; break
if best is None:
# fallback: first pin of the net
best = nodes[0]
print('label on pin for', net)
cx, cy = best[0] * GRID, best[1] * GRID
items.append(f'(label {q(net)} (at {cx:.2f} {cy:.2f} 0) (effects (font (size 1.0 1.0)) (justify left bottom)) (uuid {U()}))')
for k in range(need + 1):
grid.block.setdefault((best[0] + k, best[1] - 1), 'lbl')
# 3) emit wires from edge sets (split at corners, junctions, nodes)
for net, es in grid.edges.items():
adj = {}
for a, b in es:
adj.setdefault(a, set()).add(b); adj.setdefault(b, set()).add(a)
def is_break(c):
nb = adj[c]
if len(nb) != 2 or grid.nodes.get(c) == net:
return True
a, b = list(nb)
return not (a[0] == b[0] or a[1] == b[1])
done = set()
stub_set = set()
for start in adj:
if not is_break(start):
continue
for n in adj[start]:
e = (min(start, n), max(start, n))
if e in done:
continue
path = [start, n]; done.add(e)
while not is_break(path[-1]):
nxt = [m for m in adj[path[-1]] if m != path[-2]][0]
done.add((min(path[-1], nxt), max(path[-1], nxt))); path.append(nxt)
# stubs already emitted for power/single labels: skip if both ends are stub cells
if (net, path[0], path[-1]) in _emitted or (net, path[-1], path[0]) in _emitted:
continue
W(path[0], path[-1])
for c, nb in adj.items():
if len(nb) >= 3:
items.append(f'(junction (at {c[0]*GRID:.2f} {c[1]*GRID:.2f}) (diameter 0) (color 0 0 0 0) (uuid {U()}))')
for (x, y, size, text) in LNOTES:
items.append(f'(text {q(text)} (exclude_from_sim no) (at {x} {y} 0) (effects (font (size {size} {size}) (bold yes)) (justify left bottom)) (uuid {U()}))')
libtxt = '\n'.join(dump(v, 1) for v in libsyms.values())
sch = f'''(kicad_sch (version 20231120) (generator "eeschema") (generator_version "8.0") (uuid {root}) (paper "A3")
(title_block (title "Ofensteuerung (Wemos D1 mini)") (date "2026-11-07") (rev "1.0") (company "Ramiro / OpenClaw")
(comment 1 "Pumpenrelais 230 V + Thermostat parallel + Strommessung, Abgas Typ K, 3x DS18B20, Servo-Luftklappe, PWM-Pumpe")
(comment 2 "Erzeugt mit pcb/gen_sch.py aus design.py + sch_layout.py - Änderungen dort, dann neu erzeugen"))
(lib_symbols
{libtxt})
{chr(10).join(items)}
(sheet_instances (path "/" (page "1")))
)
'''
open(os.path.join(HERE, PROJECT + '.kicad_sch'), 'w').write(sch)
json.dump(symuuids, open(os.path.join(HERE, 'symuuids.json'), 'w'), indent=1)
print('ok', len(PARTS), 'parts', 'failed' if failed else '')
_emitted = set()
if __name__ == '__main__':
main()
+10
View File
@@ -0,0 +1,10 @@
(version 1)
(rule "GND-Waermefallen min. 1 Steg"
(condition "A.NetName == 'GND'")
(constraint min_resolved_spokes 1))
(rule "230 V zu Kleinspannung min. 4 mm"
(condition "A.NetClass == 'Mains' && B.NetClass != 'Mains'")
(constraint clearance (min 4mm)))
(rule "230 V zu Relaisspule (Relais-intern) min. 3 mm"
(condition "A.NetClass == 'Mains' && B.NetClass != 'Mains' && (A.memberOfFootprint('K1') || B.memberOfFootprint('K1'))")
(constraint clearance (min 3mm)))
File diff suppressed because it is too large. Load diff
+771
View File
@@ -0,0 +1,771 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.381,
"copper_line_width": 0.381,
"copper_text_italic": false,
"copper_text_size_h": 1.524,
"copper_text_size_v": 2.032,
"copper_text_thickness": 0.3048,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.381,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.762,
"height": 1.524,
"width": 1.524
},
"silk_line_width": 0.381,
"silk_text_italic": false,
"silk_text_size_h": 1.524,
"silk_text_size_v": 1.524,
"silk_text_thickness": 0.3048,
"silk_text_upright": false,
"zones": {
"45_degree_only": false,
"border_display_style": 2,
"border_hatch_pitch": 0.5,
"corner_radius": 0.0,
"corner_smoothing": 0,
"fill_mode": 0,
"hatch_gap": 1.5,
"hatch_orientation": 0.0,
"hatch_smoothing_level": 0,
"hatch_smoothing_value": 0.1,
"hatch_thickness": 1.0,
"min_clearance": 0.508,
"min_island_area": 10.0,
"min_thickness": 0.25,
"pad_connection": 1,
"remove_islands": 0,
"thermal_relief_gap": 0.5,
"thermal_relief_spoke_width": 0.5
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "warning",
"hole_clearance": "error",
"hole_near_hole": "error",
"hole_to_hole": "error",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "warning",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "warning",
"overlapping_pads": "warning",
"padstack": "error",
"pth_inside_courtyard": "warning",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "error",
"silk_overlap": "error",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"allow_blind_buried_vias": false,
"allow_microvias": false,
"max_error": 0.005,
"min_clearance": 0.2,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.4,
"min_groove_width": 0.0,
"min_hole_clearance": 0.0,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.12,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.2,
"min_via_annular_width": 0.05,
"min_via_diameter": 0.6,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_enabled": false,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_enabled": false,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_enabled": false,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0,
0.4,
0.8
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 100,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.1,
"single_sided": false,
"spacing": 0.6
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 100,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.1,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 100,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.1,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": true,
"zones_use_no_outline": true
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_label_syntax": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"conflicting_netclasses": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"legacy": {
"common": {
"NetDir": ""
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "ofensteuerung.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.25,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.178,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.4,
"tuning_profile": "",
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 2.5,
"diff_pair_gap": 0.25,
"diff_pair_width": 0.13,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Mains",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 1,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 2.0,
"tuning_profile": "",
"via_diameter": 2.2,
"via_drill": 1.0,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.3,
"diff_pair_gap": 0.25,
"diff_pair_width": 0.13,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Power",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 0,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 1.0,
"tuning_profile": "",
"via_diameter": 1.0,
"via_drill": 0.5,
"wire_width": 6
}
],
"meta": {
"version": 5
},
"net_colors": {},
"netclass_assignments": {},
"netclass_patterns": [
{
"netclass": "Power",
"pattern": "GND"
},
{
"netclass": "Power",
"pattern": "+5V"
},
{
"netclass": "Power",
"pattern": "+3V3"
},
{
"netclass": "Mains",
"pattern": "/L_IN"
},
{
"netclass": "Mains",
"pattern": "/L_F"
},
{
"netclass": "Mains",
"pattern": "/L_SW"
},
{
"netclass": "Mains",
"pattern": "/L_PUMP"
},
{
"netclass": "Mains",
"pattern": "/N"
},
{
"netclass": "Mains",
"pattern": "/PE"
},
{
"netclass": "Mains",
"pattern": "unconnected-(K1-*"
}
]
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": false,
"name": "Grouped By Value",
"sort_asc": true,
"sort_field": "Référence"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_bus_thickness": 12.0,
"default_junction_size": 40.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"default_wire_thickness": 6.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.3,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.3
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"ngspice": {
"fix_include_paths": true,
"fix_passive_vals": false,
"meta": {
"version": 0
},
"model_mode": 0,
"workbook_filename": ""
},
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_adjust_passive_values": false,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "ofensteuerung.kicad_sch",
"name": "ofensteuerung",
"uuid": "00000000-0000-0000-0000-000000000000"
}
]
},
"sheets": [],
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}
File diff suppressed because it is too large. Load diff
+34
View File
@@ -0,0 +1,34 @@
(kicad_symbol_lib (version 20231120) (generator "gen_sch.py")
(symbol "WEMOS_D1_mini" (pin_names (offset 1.016)) (exclude_from_sim no) (in_bom yes) (on_board yes)
(property "Reference" "A" (at 0 12.70 0) (effects (font (size 1.27 1.27))))
(property "Value" "WEMOS_D1_mini" (at 0 -12.70 0) (effects (font (size 1.27 1.27))))
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Description" "Wemos D1 mini (ESP8266) auf 2x 1x8 Buchsenleisten" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(symbol "WEMOS_D1_mini_0_1" (rectangle (start -15.24 11.43) (end 15.24 -11.43) (stroke (width 0.254) (type default)) (fill (type background))))
(symbol "WEMOS_D1_mini_1_1" (pin input line (at -17.78 8.89 0) (length 2.54) (name "RST" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) (pin input line (at -17.78 6.35 0) (length 2.54) (name "A0" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at -17.78 3.81 0) (length 2.54) (name "D0/GPIO16" (effects (font (size 1.27 1.27)))) (number "3" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at -17.78 1.27 0) (length 2.54) (name "D5/GPIO14" (effects (font (size 1.27 1.27)))) (number "4" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at -17.78 -1.27 0) (length 2.54) (name "D6/GPIO12" (effects (font (size 1.27 1.27)))) (number "5" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at -17.78 -3.81 0) (length 2.54) (name "D7/GPIO13" (effects (font (size 1.27 1.27)))) (number "6" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at -17.78 -6.35 0) (length 2.54) (name "D8/GPIO15" (effects (font (size 1.27 1.27)))) (number "7" (effects (font (size 1.27 1.27))))) (pin power_out line (at -17.78 -8.89 0) (length 2.54) (name "3V3" (effects (font (size 1.27 1.27)))) (number "8" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at 17.78 8.89 180) (length 2.54) (name "TX/GPIO1" (effects (font (size 1.27 1.27)))) (number "9" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at 17.78 6.35 180) (length 2.54) (name "RX/GPIO3" (effects (font (size 1.27 1.27)))) (number "10" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at 17.78 3.81 180) (length 2.54) (name "D1/GPIO5" (effects (font (size 1.27 1.27)))) (number "11" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at 17.78 1.27 180) (length 2.54) (name "D2/GPIO4" (effects (font (size 1.27 1.27)))) (number "12" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at 17.78 -1.27 180) (length 2.54) (name "D3/GPIO0" (effects (font (size 1.27 1.27)))) (number "13" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at 17.78 -3.81 180) (length 2.54) (name "D4/GPIO2" (effects (font (size 1.27 1.27)))) (number "14" (effects (font (size 1.27 1.27))))) (pin power_in line (at 17.78 -6.35 180) (length 2.54) (name "GND" (effects (font (size 1.27 1.27)))) (number "15" (effects (font (size 1.27 1.27))))) (pin power_in line (at 17.78 -8.89 180) (length 2.54) (name "5V" (effects (font (size 1.27 1.27)))) (number "16" (effects (font (size 1.27 1.27)))))))
(symbol "ADS1115_Modul" (pin_names (offset 1.016)) (exclude_from_sim no) (in_bom yes) (on_board yes)
(property "Reference" "A" (at 0 8.89 0) (effects (font (size 1.27 1.27))))
(property "Value" "ADS1115_Modul" (at 0 -8.89 0) (effects (font (size 1.27 1.27))))
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Description" "ADS1115 16-bit ADC Breakout (HW-ADS1115/CJMCU, 1x10 Pin)" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(symbol "ADS1115_Modul_0_1" (rectangle (start -6.35 7.62) (end 6.35 -7.62) (stroke (width 0.254) (type default)) (fill (type background))))
(symbol "ADS1115_Modul_1_1" (pin power_in line (at -8.89 5.08 0) (length 2.54) (name "VDD" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) (pin power_in line (at -8.89 2.54 0) (length 2.54) (name "GND" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27))))) (pin input line (at -8.89 0.00 0) (length 2.54) (name "SCL" (effects (font (size 1.27 1.27)))) (number "3" (effects (font (size 1.27 1.27))))) (pin bidirectional line (at -8.89 -2.54 0) (length 2.54) (name "SDA" (effects (font (size 1.27 1.27)))) (number "4" (effects (font (size 1.27 1.27))))) (pin input line (at -8.89 -5.08 0) (length 2.54) (name "ADDR" (effects (font (size 1.27 1.27)))) (number "5" (effects (font (size 1.27 1.27))))) (pin output line (at 8.89 5.08 180) (length 2.54) (name "ALRT" (effects (font (size 1.27 1.27)))) (number "6" (effects (font (size 1.27 1.27))))) (pin input line (at 8.89 2.54 180) (length 2.54) (name "A0" (effects (font (size 1.27 1.27)))) (number "7" (effects (font (size 1.27 1.27))))) (pin input line (at 8.89 0.00 180) (length 2.54) (name "A1" (effects (font (size 1.27 1.27)))) (number "8" (effects (font (size 1.27 1.27))))) (pin input line (at 8.89 -2.54 180) (length 2.54) (name "A2" (effects (font (size 1.27 1.27)))) (number "9" (effects (font (size 1.27 1.27))))) (pin input line (at 8.89 -5.08 180) (length 2.54) (name "A3" (effects (font (size 1.27 1.27)))) (number "10" (effects (font (size 1.27 1.27)))))))
(symbol "ZMCT103C" (pin_names (offset 1.016)) (exclude_from_sim no) (in_bom yes) (on_board yes)
(property "Reference" "T" (at 0 5.08 0) (effects (font (size 1.27 1.27))))
(property "Value" "ZMCT103C" (at 0 -5.08 0) (effects (font (size 1.27 1.27))))
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Description" "Stromwandler ZMCT103C 1000:1, Primär = Drahtbrücke durchs Loch" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(symbol "ZMCT103C_0_1" (rectangle (start -7.62 3.81) (end 7.62 -3.81) (stroke (width 0.254) (type default)) (fill (type background))))
(symbol "ZMCT103C_1_1" (pin passive line (at -10.16 1.27 0) (length 2.54) (name "P1 (L_SW)" (effects (font (size 1.27 1.27)))) (number "3" (effects (font (size 1.27 1.27))))) (pin passive line (at -10.16 -1.27 0) (length 2.54) (name "P2 (L_PUMP)" (effects (font (size 1.27 1.27)))) (number "4" (effects (font (size 1.27 1.27))))) (pin passive line (at 10.16 1.27 180) (length 2.54) (name "S1" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) (pin passive line (at 10.16 -1.27 180) (length 2.54) (name "S2" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27)))))))
(symbol "MAX6675_Modul" (pin_names (offset 1.016)) (exclude_from_sim no) (in_bom yes) (on_board yes)
(property "Reference" "A" (at 0 8.89 0) (effects (font (size 1.27 1.27))))
(property "Value" "MAX6675_Modul" (at 0 -8.89 0) (effects (font (size 1.27 1.27))))
(property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(property "Description" "MAX6675 Thermoelement-Modul (Typ K), 1x5 Pin" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))
(symbol "MAX6675_Modul_0_1" (rectangle (start -6.35 7.62) (end 6.35 -7.62) (stroke (width 0.254) (type default)) (fill (type background))))
(symbol "MAX6675_Modul_1_1" (pin power_in line (at -8.89 5.08 0) (length 2.54) (name "GND" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) (pin power_in line (at -8.89 2.54 0) (length 2.54) (name "VCC" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27))))) (pin input line (at -8.89 0.00 0) (length 2.54) (name "SCK" (effects (font (size 1.27 1.27)))) (number "3" (effects (font (size 1.27 1.27))))) (pin input line (at -8.89 -2.54 0) (length 2.54) (name "CS" (effects (font (size 1.27 1.27)))) (number "4" (effects (font (size 1.27 1.27))))) (pin output line (at -8.89 -5.08 0) (length 2.54) (name "SO" (effects (font (size 1.27 1.27)))) (number "5" (effects (font (size 1.27 1.27)))))))
)
@@ -0,0 +1,35 @@
(footprint "ADS1115_Module_1x10" (version 20240108) (generator "gen") (layer "F.Cu")
(descr "ADS1115 Breakout (HW-ADS1115), 1x10 Stiftleiste 2,54 mm, Platine ca. 28 x 17,5 mm")
(property "Reference" "A2" (at 11.43 -17.50 0) (layer "F.SilkS") (effects (font (size 1.0 1.0) (thickness 0.15))))
(property "Value" "ADS1115" (at 11.43 -8.00 0) (layer "F.Fab") (effects (font (size 1.0 1.0) (thickness 0.15))))
(pad "1" thru_hole rect (at 0.00 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "VDD" (at 0.00 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "2" thru_hole circle (at 2.54 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "GND" (at 2.54 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "3" thru_hole circle (at 5.08 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "SCL" (at 5.08 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "4" thru_hole circle (at 7.62 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "SDA" (at 7.62 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "5" thru_hole circle (at 10.16 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "ADDR" (at 10.16 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "6" thru_hole circle (at 12.70 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "ALRT" (at 12.70 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "7" thru_hole circle (at 15.24 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "A0" (at 15.24 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "8" thru_hole circle (at 17.78 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "A1" (at 17.78 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "9" thru_hole circle (at 20.32 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "A2" (at 20.32 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "10" thru_hole circle (at 22.86 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "A3" (at 22.86 -2.60 90) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(fp_line (start -2.57 -16.00) (end 25.43 -16.00) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 25.43 -16.00) (end 25.43 1.50) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 25.43 1.50) (end -2.57 1.50) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start -2.57 1.50) (end -2.57 -16.00) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start -2.57 -16.00) (end 25.43 -16.00) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 25.43 -16.00) (end 25.43 1.50) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 25.43 1.50) (end -2.57 1.50) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start -2.57 1.50) (end -2.57 -16.00) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_text user "ADS1115" (at 11.43 -10.00 0) (layer "F.SilkS") (effects (font (size 1.2 1.2) (thickness 0.12))))
(fp_rect (start -1.9 -1.9) (end 24.76 1.9) (stroke (width 0.05) (type solid)) (fill no) (layer "F.CrtYd"))
)
@@ -0,0 +1,18 @@
(footprint "MAX6675_Module_1x5" (version 20240108) (generator "gen") (layer "F.Cu")
(descr "MAX6675 Thermoelement-Modul (ca. 25 x 15 mm), 1x5 Stiftleiste 2,54 mm (GND VCC SCK CS SO), Schraubklemme Typ K am anderen Ende")
(property "Reference" "A3" (at 12 -4.2 0) (layer "F.SilkS") (effects (font (size 1.0 1.0) (thickness 0.15))))
(property "Value" "MAX6675" (at 12 5 0) (layer "F.Fab") (effects (font (size 1.0 1.0) (thickness 0.15))))
(pad "1" thru_hole rect (at 0 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "GND" (at 2.9 0.00 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13)) (justify left)))
(pad "2" thru_hole circle (at 0 2.54) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "VCC" (at 2.9 2.54 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13)) (justify left)))
(pad "3" thru_hole circle (at 0 5.08) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "SCK" (at 2.9 5.08 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13)) (justify left)))
(pad "4" thru_hole circle (at 0 7.62) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "CS" (at 2.9 7.62 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13)) (justify left)))
(pad "5" thru_hole circle (at 0 10.16) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "SO" (at 2.9 10.16 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13)) (justify left)))
(fp_rect (start -1.6 -2.4) (end 23.4 12.56) (stroke (width 0.12) (type solid)) (fill no) (layer "F.SilkS"))
(fp_rect (start -1.6 -2.4) (end 23.4 12.56) (stroke (width 0.1) (type solid)) (fill no) (layer "F.Fab"))
(fp_rect (start -1.9 -1.9) (end 1.9 12.06) (stroke (width 0.05) (type solid)) (fill no) (layer "F.CrtYd"))
)
@@ -0,0 +1,59 @@
(footprint "WEMOS_D1_mini_Socket" (version 20240108) (generator "gen") (layer "F.Cu")
(descr "Wemos D1 mini auf 2x 1x8 Buchsenleiste, Reihenabstand 22,86 mm; USB an +Y")
(property "Reference" "A1" (at 11.43 -15.50 0) (layer "F.SilkS") (effects (font (size 1.0 1.0) (thickness 0.15))))
(property "Value" "WEMOS_D1_mini" (at 11.43 8.90 0) (layer "F.Fab") (effects (font (size 1.0 1.0) (thickness 0.15))))
(pad "1" thru_hole rect (at 0.00 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "RST" (at 2.40 0.00 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "2" thru_hole circle (at 0.00 2.54) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "A0" (at 2.40 2.54 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "3" thru_hole circle (at 0.00 5.08) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "D0" (at 2.40 5.08 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "4" thru_hole circle (at 0.00 7.62) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "D5" (at 2.40 7.62 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "5" thru_hole circle (at 0.00 10.16) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "D6" (at 2.40 10.16 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "6" thru_hole circle (at 0.00 12.70) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "D7" (at 2.40 12.70 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "7" thru_hole circle (at 0.00 15.24) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "D8" (at 2.40 15.24 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "8" thru_hole circle (at 0.00 17.78) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "3V3" (at 2.40 17.78 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "9" thru_hole circle (at 22.86 0.00) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "TX" (at 20.40 0.00 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "10" thru_hole circle (at 22.86 2.54) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "RX" (at 20.40 2.54 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "11" thru_hole circle (at 22.86 5.08) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "D1" (at 20.40 5.08 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "12" thru_hole circle (at 22.86 7.62) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "D2" (at 20.40 7.62 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "13" thru_hole circle (at 22.86 10.16) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "D3" (at 20.40 10.16 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "14" thru_hole circle (at 22.86 12.70) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "D4" (at 20.40 12.70 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "15" thru_hole circle (at 22.86 15.24) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "G" (at 20.40 15.24 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(pad "16" thru_hole circle (at 22.86 17.78) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask"))
(fp_text user "5V" (at 20.40 17.78 0) (layer "F.SilkS") (effects (font (size 0.8 0.8) (thickness 0.13))))
(fp_line (start -1.37 -13.22) (end 24.23 -13.22) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 24.23 -13.22) (end 24.23 20.98) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 24.23 20.98) (end -1.37 20.98) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start -1.37 20.98) (end -1.37 -13.22) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start -1.37 -13.22) (end 24.23 -13.22) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 24.23 -13.22) (end 24.23 20.98) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 24.23 20.98) (end -1.37 20.98) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start -1.37 20.98) (end -1.37 -13.22) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 7.43 15.48) (end 15.43 15.48) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 15.43 15.48) (end 15.43 20.78) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 15.43 20.78) (end 7.43 20.78) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_line (start 7.43 20.78) (end 7.43 15.48) (stroke (width 0.12) (type solid)) (layer "F.SilkS"))
(fp_text user "USB" (at 11.43 18.48 0) (layer "F.SilkS") (effects (font (size 1.0 1.0) (thickness 0.12))))
(fp_text user "ANTENNE" (at 11.43 -7.22 0) (layer "F.SilkS") (effects (font (size 1.0 1.0) (thickness 0.12))))
(fp_line (start -1.80 -1.80) (end 1.80 -1.80) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))
(fp_line (start 1.80 -1.80) (end 1.80 19.58) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))
(fp_line (start 1.80 19.58) (end -1.80 19.58) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))
(fp_line (start -1.80 19.58) (end -1.80 -1.80) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))
(fp_line (start 21.06 -1.80) (end 24.66 -1.80) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))
(fp_line (start 24.66 -1.80) (end 24.66 19.58) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))
(fp_line (start 24.66 19.58) (end 21.06 19.58) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))
(fp_line (start 21.06 19.58) (end 21.06 -1.80) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))
)
@@ -0,0 +1,16 @@
(footprint "ZMCT103C_Drahtbruecke" (version 20240108) (generator "gen") (layer "F.Cu")
(descr "Stromwandler ZMCT103C (18,2 x 10,2 mm, Pins 11,8 mm, Loch 5 mm in 10 mm Höhe). Pads 3/4 = Primärleiter als isolierte Drahtbrücke durchs Loch")
(property "Reference" "T1" (at 0 -7.2 0) (layer "F.SilkS") (effects (font (size 1.0 1.0) (thickness 0.15))))
(property "Value" "ZMCT103C" (at 0 7.2 0) (layer "F.Fab") (effects (font (size 1.0 1.0) (thickness 0.15))))
(pad "1" thru_hole rect (at -5.9 0) (size 2.2 2.2) (drill 1.2) (layers "*.Cu" "*.Mask"))
(pad "2" thru_hole circle (at 5.9 0) (size 2.2 2.2) (drill 1.2) (layers "*.Cu" "*.Mask"))
(pad "3" thru_hole circle (at 0 -10) (size 4.0 4.0) (drill 2.0) (layers "*.Cu" "*.Mask"))
(pad "4" thru_hole circle (at 0 10) (size 4.0 4.0) (drill 2.0) (layers "*.Cu" "*.Mask"))
(fp_rect (start -9.1 -5.1) (end 9.1 5.1) (stroke (width 0.12) (type solid)) (fill no) (layer "F.SilkS"))
(fp_line (start 0 -7.6) (end 0 7.6) (stroke (width 0.3) (type dash)) (layer "F.SilkS"))
(fp_circle (center 0 0) (end 2.5 0) (stroke (width 0.12) (type solid)) (fill no) (layer "F.Fab"))
(fp_rect (start -9.1 -5.1) (end 9.1 5.1) (stroke (width 0.1) (type solid)) (fill no) (layer "F.Fab"))
(fp_rect (start -9.35 -5.35) (end 9.35 5.35) (stroke (width 0.05) (type solid)) (fill no) (layer "F.CrtYd"))
(fp_circle (center 0 -10) (end 2.4 -10) (stroke (width 0.05) (type solid)) (fill no) (layer "F.CrtYd"))
(fp_circle (center 0 10) (end 2.4 10) (stroke (width 0.05) (type solid)) (fill no) (layer "F.CrtYd"))
)
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# render.sh <board> <out.png> : Bestückungsansicht (Kupfer beidseitig + Druck + Courtyard)
b=$1; o=$2; t=/tmp/render_$$; mkdir -p $t
kicad-cli pcb export svg --mode-single -l F.Cu,B.Cu,F.Silkscreen,Edge.Cuts,F.Courtyard,F.Fab --page-size-mode 2 --exclude-drawing-sheet -o $t/b.svg "$b" >/dev/null 2>&1
rsvg-convert -w 1600 -b white $t/b.svg -o "$o"; rm -rf $t
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Zweistufiges Routing mit Freerouting.
1) nur 230-V-Netze, eingesperrt in MAINS_AREA; CT-Sekundärpins/-Stummel mit 4 mm Abstandsklasse
2) Kleinspannung, MAINS_AREA gesperrt (230-V-Bahnen + LV-Stummel bleiben fest)
-> ofensteuerung.kicad_pcb"""
import os, sys, shutil, subprocess, pcbnew
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from design import PROJECT, MAINS, MAINS_AREA, W, H
src = os.path.join(HERE, PROJECT + '_unrouted.kicad_pcb')
dst = os.path.join(HERE, PROJECT + '.kicad_pcb')
shutil.copy(os.path.join(HERE, PROJECT + '.kicad_pro'), src.replace('.kicad_pcb', '.kicad_pro'))
mm = pcbnew.FromMM
JAR = os.path.expanduser('~/tools/freerouting.jar')
passes = int(sys.argv[1]) if len(sys.argv) > 1 else 30
def freeroute(b, tag, mp):
dsn, ses = f'/tmp/ofen_{tag}.dsn', f'/tmp/ofen_{tag}.ses'
if os.path.exists(ses): os.remove(ses)
assert pcbnew.ExportSpecctraDSN(b, dsn)
for attempt in range(4):
if os.path.exists(ses): os.remove(ses)
r = subprocess.run(['java', '-jar', JAR, '-de', dsn, '-do', ses, '-mp', str(mp), '-l', 'en',
'--gui.enabled=false', '--usage_and_diagnostic_data.disable_analytics=true'],
capture_output=True, text=True, timeout=1800)
final = [l for l in r.stdout.splitlines() if 'Auto-routing stage completed' in l]
print(tag, attempt, final[-1][-120:] if final else r.stdout[-1500:])
if final and '(0 unrouted' in final[-1] and os.path.exists(ses):
return ses
mp += 20
raise SystemExit(tag + ': routing unvollständig\n' + r.stdout[-3000:] + r.stderr[-2000:])
def keepout(b, pts, name):
z = pcbnew.ZONE(b); z.SetIsRuleArea(True); z.SetDoNotAllowTracks(True); z.SetDoNotAllowVias(True)
z.SetDoNotAllowZoneFills(True); z.SetDoNotAllowPads(False); z.SetDoNotAllowFootprints(False); z.SetZoneName(name)
ls = pcbnew.LSET(); ls.AddLayer(pcbnew.F_Cu); ls.AddLayer(pcbnew.B_Cu); z.SetLayerSet(ls)
ol = z.Outline(); ol.NewOutline()
for x, y in pts: ol.Append(mm(x), mm(y))
b.Add(z); return z
def strip_zones(b):
zs = [z for z in b.Zones() if not z.GetIsRuleArea()]
for z in zs: b.Remove(z)
return zs
ismains = lambda n: n.lstrip('/') in MAINS or n.startswith('unconnected-(K1-')
# ---------- Stufe 1: 230 V ----------
import json
p1 = '/tmp/ofen_p1.kicad_pcb'
shutil.copy(src, p1)
pro = json.load(open(os.path.join(HERE, PROJECT + '.kicad_pro')))
nsj = pro['net_settings']
d = dict([c for c in nsj['classes'] if c['name'] == 'Default'][0]); d.update(name='Iso', clearance=4.0, priority=0)
nsj['classes'].append(d)
nsj['netclass_patterns'].append({'netclass': 'Iso', 'pattern': 'ISO_*'})
json.dump(pro, open(p1.replace('.kicad_pcb', '.kicad_pro'), 'w'), indent=2)
b1 = pcbnew.LoadBoard(p1)
_zs1 = strip_zones(b1)
_ra1 = [z for z in b1.Zones() if z.GetZoneName() == 'NETZ_230V']
for z in _ra1:
b1.Remove(z) # reine "keine Fläche"-Zone wird im DSN als Sperrfläche exportiert
newnets = {}
def mknet(name):
if name not in newnets:
n = pcbnew.NETINFO_ITEM(b1, name); b1.Add(n); newnets[name] = n
return newnets[name]
for nm in ('ISO_VMID', 'ISO_CT_A', 'COIL_P5V', 'COIL_RELAY_C'):
mknet(nm)
for fp in list(b1.GetFootprints()):
for pd in list(fp.Pads()):
n = pd.GetNetname()
if not n or ismains(n):
continue
if fp.GetReference() == 'T1':
pd.SetNet(mknet('ISO_' + n.lstrip('/')))
elif fp.GetReference() == 'K1':
pd.SetNet(mknet('COIL_' + n.lstrip('/').replace('+', 'P')))
else:
pd.SetNet(b1.FindNet('')) # Hindernis ohne Netz
_tr1 = list(b1.GetTracks())
for t in _tr1:
b1.Remove(t) # LV-Stummel in Stufe 1 weglassen (würden die Sperrflächen verletzen)
b1.SynchronizeNetsAndNetClasses(True)
x1, y1 = MAINS_AREA[2]
keepout(b1, [(x1, 0), (W, 0), (W, H), (x1, H)], 'NUR_LV_R')
keepout(b1, [(0, y1), (x1, y1), (x1, H), (0, H)], 'NUR_LV_U')
ses1 = freeroute(b1, 'netz', passes)
# ---------- Stufe 2: Kleinspannung ----------
b = pcbnew.LoadBoard(src)
zones = strip_zones(b)
assert pcbnew.ImportSpecctraSES(b, ses1)
for t in b.GetTracks():
if ismains(t.GetNetname()):
t.SetLocked(True)
ko = keepout(b, MAINS_AREA, 'ROUTE_KEEPOUT')
ses2 = freeroute(b, 'lv', passes)
assert pcbnew.ImportSpecctraSES(b, ses2)
b.Remove(ko)
for z in zones: b.Add(z)
pcbnew.ZONE_FILLER(b).Fill(b.Zones())
b.BuildConnectivity()
print('unconnected after fill:', b.GetConnectivity().GetUnconnectedCount(True))
pcbnew.SaveBoard(dst, b)
print('saved', dst, 'tracks', len(b.GetTracks()))
+45
View File
@@ -0,0 +1,45 @@
"""Schaltplan-Anordnung (A3, mm, 1,27-Raster). gen_sch.py verlegt alle Signalleitungen automatisch.
BLOCKS: block -> {ref: (x, y, rot, mirror)}; PSYM: (block, net, x, y)."""
def g(v):
return round(round(v / 1.27) * 1.27, 2)
def B(d):
return {k: (g(x), g(y), r, m) for k, (x, y, r, m) in d.items()}
BLOCKS = {
'power': B({'J1': (25.4, 40.64, 0, 'y'), '#FLG01': (40.64, 30.48, 0, None), '#FLG02': (40.64, 53.34, 180, None),
'C1': (48.26, 43.18, 0, None), 'C2': (58.42, 43.18, 0, None), 'R4': (68.58, 40.64, 0, None), 'D3': (68.58, 53.34, 90, None)}),
'mains': B({'J2': (25.4, 101.6, 0, 'y'), 'F1': (50.8, 96.52, 90, None), 'J4': (63.5, 76.2, 90, None),
'K1': (88.9, 101.6, 0, None), 'T1': (125.73, 111.76, 0, None), 'J3': (160.02, 88.9, 0, None)}),
'relay': B({'R1': (63.5, 146.05, 90, None), 'R2': (72.39, 152.4, 0, None), 'Q1': (81.28, 146.05, 0, None),
'D1': (104.14, 128.27, 90, None), 'D2': (116.84, 140.97, 90, None), 'R3': (116.84, 128.27, 0, None)}),
'ct': B({'R5': (144.78, 129.54, 0, None), 'R6': (157.48, 125.73, 0, None), 'R7': (157.48, 138.43, 0, None),
'C5': (167.64, 138.43, 0, None), 'C6': (177.8, 138.43, 0, None)}),
'wemos': B({'A1': (228.6, 151.13, 0, None), 'C3': (215.9, 187.96, 0, None), 'C4': (226.06, 187.96, 0, None)}),
'ads': B({'A2': (309.88, 165.1, 0, None)}),
'max': B({'A3': (309.88, 104.14, 0, None), 'R9': (292.1, 116.84, 0, None), 'C8': (281.94, 109.22, 0, None)}),
'onewire': B({'J5': (391.16, 40.64, 0, None), 'J6': (391.16, 55.88, 0, None), 'J7': (391.16, 71.12, 0, None),
'R8': (372.11, 38.1, 0, None), 'C7': (360.68, 55.88, 0, None)}),
'servo': B({'J8': (391.16, 210.82, 0, None), 'R10': (370.84, 213.36, 90, None), 'C9': (358.14, 220.98, 0, None), 'C10': (347.98, 220.98, 0, None)}),
'pwm': B({'J9': (301.16, 242.00, 0, None), 'R14': (280.84, 239.46, 90, None), 'R15': (270.68, 231.84, 0, None),
'R11': (232.58, 248.35, 90, None), 'R12': (240.20, 254.70, 0, None), 'Q2': (247.82, 248.35, 0, None),
'R13': (250.36, 234.38, 0, None), 'JP1': (250.36, 222.95, 270, None)}),
'erw': B({'J10': (391.16, 152.4, 0, None)}),
'holes': B({'H1': (30.48, 270.0, 0, None), 'H2': (45.72, 270.0, 0, None), 'H3': (60.96, 270.0, 0, None), 'H4': (76.2, 270.0, 0, None)}),
}
LABELS = []
PSYM = []
NOTES = [
(20.32, 25.4, 2.0, 'Versorgung 5 V / 3 A (Servo)'),
(20.32, 66.04, 2.0, '230 V: Netz -> T2A -> Relais || Thermostat -> Stromwandler -> Pumpe'),
(20.32, 71.12, 1.27, 'Thermostat (Schließer) parallel zum Relaiskontakt: Pumpe läuft auch bei ESP-Ausfall'),
(55.88, 119.38, 2.0, 'Relaistreiber (D0/GPIO16)'),
(137.16, 116.84, 2.0, 'Strommessung: ZMCT103C 1000:1, Bürde 100 R, Mitte 1,65 V -> ADS1115 A0-A1'),
(205.74, 124.46, 2.0, 'Wemos D1 mini (ESP8266)'),
(289.56, 144.78, 2.0, 'ADS1115-Modul (I2C 0x48)'),
(276.86, 86.36, 2.0, 'Abgas: MAX6675 + Thermoelement Typ K'),
(345.44, 25.4, 2.0, '1-Wire DS18B20 (3 Klemmen, ein Bus)'),
(330.2, 195.58, 2.0, 'Luftklappe: Servo MG996R'),
(214.63, 215.9, 2.0, 'PWM-Pumpe (optional)'),
(370.84, 134.62, 2.0, 'Erweiterung'),
(20.32, 259.08, 2.0, 'Befestigung M3'),
]
+3
View File
@@ -0,0 +1,3 @@
(sym_lib_table (version 7)
(lib (name "ofensteuerung")(type "KiCad")(uri "${KIPRJMOD}/ofensteuerung.kicad_sym")(options "")(descr ""))
)
+55
View File
@@ -0,0 +1,55 @@
{
"J2": "1caec72d-30f2-543f-8a7b-8d15757bd46e",
"J4": "94cdcc2e-15f3-5b84-8c13-0a414ebd9a4a",
"J3": "874983a2-1f04-5d18-95d9-813e3c95ca4f",
"F1": "28f7fb03-2c32-5a68-ac1a-0dfa9004e102",
"K1": "b0833d88-5b13-575a-8c39-0712ded4a388",
"T1": "a0ec19ea-1bc4-5564-b075-8880441d714c",
"J1": "14db5e1a-5c8a-5b29-9ff4-6cea8c813b20",
"C1": "8873537e-6111-5211-a1b2-eda456206996",
"C2": "a1383de9-f1c7-58fc-bd00-4af2368d5ea7",
"R4": "896b2495-0e93-5444-a79e-a31c87b32f07",
"D3": "853a00a6-f61e-5058-a424-a4f2aaf75a73",
"A1": "170186fe-6865-5841-bb94-24c9647a65b6",
"C3": "a70b1b63-ea4e-5029-ae98-9accd895772e",
"C4": "588b9e19-ad03-5ef3-937e-5b008b57a2fd",
"R1": "7e46d0e7-0adb-52d2-aeff-817c993a8df2",
"R2": "9472fb12-f09d-5118-99fd-0499f1fb8701",
"Q1": "42346c05-a39a-5aad-8ea7-aaecf47bfc30",
"D1": "67f6d85f-ced2-5061-9e14-0e0aa85deebb",
"R3": "3619ba94-600c-5b94-9d4a-2f991be3aa84",
"D2": "729b0b41-1e4f-5358-8c03-ace35885839e",
"R5": "fa666bc4-d233-56e8-9645-58881b4862dc",
"R6": "4e26925e-c36d-5140-810f-d14aa2903b4f",
"R7": "8403744e-6b6d-5cd0-9da0-472a4075b143",
"C5": "01cc4214-37e7-5df8-9da2-18eff3632cf7",
"C6": "8735fdbe-202c-50da-b085-89bbe420f20b",
"A2": "fa890770-a976-50d2-b400-a3575b41e470",
"A3": "8ce1bbb2-48c4-5067-9815-8598faa918f7",
"R9": "78bdcbe4-f5c1-5989-a736-70f5cc57aa27",
"C8": "36dd4acf-4241-5813-bc9e-db8f49c6c62f",
"J5": "7b024d22-065d-5532-92c8-e638eec4001c",
"J6": "cfb39cbc-0c41-5eb9-aaa7-3d48c8772625",
"J7": "c98e076d-86d4-5812-96a1-1cc2cc7579f6",
"R8": "1cac538f-cc88-58af-9dde-4692fa44b808",
"C7": "cca9da88-aa3f-5dea-95c5-b24dc12337c5",
"J8": "f100476c-4cf2-5231-9151-2ae902343366",
"R10": "908bd703-3f78-5fd5-9c5a-3e06598b398c",
"C9": "384fcc73-6639-5801-9c53-652a1213eef3",
"C10": "58a30551-3cb9-5345-8176-c53f55beb9ca",
"J9": "8f4f8411-8abd-5397-90c6-3a189502d35f",
"R11": "0d9a11dc-9d23-561e-bf96-503fb30d7239",
"R12": "8ae27c8e-3f79-57d5-8199-f6bcc519bba5",
"Q2": "b09b2680-f862-5961-ba64-6482bf8aa60d",
"R13": "7d4fbf9b-8f88-59f2-9f2d-c254bd8a0261",
"JP1": "7a1a010a-2d32-58cb-a143-60bd6157c63e",
"R14": "b63b2fdc-a5d6-5577-b327-8780907c1eb5",
"R15": "9c96f871-bb18-53b7-8388-645a29539aa4",
"J10": "e0637304-7b57-5d35-a747-6c92b98d3964",
"H1": "f0799bec-e8cc-52af-ae12-b708a3c4ecea",
"H2": "000a138c-3bf8-52fd-b988-ac152c47ba28",
"H3": "502d18ea-1f8a-5b7e-88c4-56af34d05c92",
"H4": "fb455633-728c-5335-a15a-009097dcce65",
"#FLG01": "1d52e2bb-2e6a-522a-b9d7-7e85b16195c8",
"#FLG02": "4ded5458-e727-5aa5-b2ce-d80a75a4fca3"
}