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

+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"
}