#!/usr/bin/env python3 """Generate KiCad 7 schematic (.kicad_sch) for the loop vehicle detector.""" import re, uuid, os BASE = "/root/.openclaw/workspace/loop-detector/loop-detector-kicad" SYM = os.path.join(BASE, "loopdet.kicad_sym") SCH = os.path.join(BASE, "loop-detector.kicad_sch") def uid(): return str(uuid.uuid4()) # ---------- extract lib_symbols from .kicad_sym ---------- data = open(SYM, encoding="utf-8").read() # only extract TOP-LEVEL (symbol "X" ...) blocks: track paren depth while scanning sym_blocks = [] pat = re.compile(r'\(symbol "([^"]+)"') # find all matches, then filter to depth-1 (inside kicad_symbol_lib only) depth = 0 top_starts = [] for m in pat.finditer(data): # compute depth at match start by counting parens before it prefix = data[:m.start()] d = prefix.count('(') - prefix.count(')') if d == 1: # directly inside kicad_symbol_lib top_starts.append(m.start()) for start in top_starts: depth = 0 j = start while j < len(data): if data[j] == '(': depth += 1 elif data[j] == ')': depth -= 1 if depth == 0: break j += 1 sym_blocks.append(data[start:j+1]) # embedded symbol names must carry the library prefix (e.g. "loopdet:C") so that # lib_id "loopdet:C" resolves to the embedded definition without hitting the table import re as _re def _prefix(sym_block): return _re.sub(r'\(symbol "([^"]+)"', r'(symbol "loopdet:\1"', sym_block, count=1) lib_symbols = "(lib_symbols\n" + "\n".join(" " + _prefix(b) for b in sym_blocks) + "\n)\n" # ---------- components ---------- # (lib, ref, value, x, y, rot, ref_at, val_at) COMPONENTS = [ ("CONN2", "J1", "CONN_2P", 40, 130, 0, (40, 136), (40, 124)), ("TVS_BI", "TVS1", "TVS_BIDI", 62, 130, 0, (62, 136), (62, 124)), ("XFMR", "T1", "1:1", 90, 130, 0, (90, 138), (90, 120)), ("C", "C1", "100~470p", 107, 130, 90, (113, 136), (113, 124)), ("Q_NPN", "Q1", "S9018", 130, 130, 0, (126.5, 136),(126.5, 124)), ("R", "Rc", "2.2k", 138, 126.19,0, (134.5, 132),(134.5, 120)), ("R", "Rb1", "100k", 124.92,121.5,90, (131, 121.5),(131, 117)), ("R", "Rb2", "47k", 124.92,137.5,90, (131, 137.5),(131, 133)), ("R", "Re", "1k", 132.54,139.5,90, (138.5, 139.5),(138.5, 135)), ("C", "C2", "100p", 138, 135.5, 90, (144, 135.5),(144, 131)), ("C", "Cc2", "10p", 148, 126.19,0, (148, 132), (148, 120)), ("COMP", "U1", "LM393", 170, 130, 0, (170, 137), (170, 123)), ("R", "R3", "10k", 150, 124, 90, (156, 124), (156, 119.5)), ("R", "R4", "10k", 150, 138.5, 90, (156, 138.5),(156, 134)), ("MCU", "MCU1", "STM32F103C8", 215, 130, 0, (215, 143), (215, 117)), ("R", "R5", "1k", 245, 124.92,0, (245, 131), (245, 119)), ("OPTO", "U2", "PC817", 265, 124.92,0, (265, 131), (265, 119)), ("MOS_N", "Q2", "N-MOS", 300, 126.19,0, (296.5, 132),(296.5, 120)), ("RELAY_SPDT","K1", "SPDT 12V", 325, 122.38,0, (325, 130), (325, 116)), ("R", "R6", "1k", 245, 130, 0, (245, 136), (245, 124)), ("LED", "D2", "LED", 280, 130, 0, (280, 136), (280, 124)), ] # ---------- wires ---------- WIRES = [ # A net: J1.p1 - TVS1.p1 - T1.p1 ((37.46, 127.46), (82.38, 127.46)), ((59.46, 127.46), (59.46, 130)), # B net: J1.p2 - TVS1.p2 - T1.p2 ((37.46, 132.54), (82.38, 132.54)), ((64.54, 132.54), (64.54, 130)), # T1 sec upper: p3 - C1.p1 - base input ((97.62, 127.46), (110, 127.46)), ((110, 127.46), (110, 130)), ((110, 130), (124.92, 130)), # T1 sec lower: p4 - C1.p2 - GND ((97.62, 132.54), (107, 132.54)), ((107, 132.54), (107, 145)), # VCC bus ((100, 115), (320, 115)), ((140.54, 126.19), (140.54, 115)), ((124.92, 118.96), (124.92, 115)), ((150, 121.46), (150, 115)), ((317.38, 119.84), (317.38, 115)), # GND bus ((100, 145), (340, 145)), ((124.92, 140.04), (124.92, 145)), ((132.54, 142.04), (132.54, 145)), ((138, 138.04), (138, 145)), ((257.38, 127.46), (257.38, 145)), ((302.54, 130), (302.54, 145)), ((282.54, 130), (282.54, 145)), # base net ((124.92, 124.04), (124.92, 134.96)), # collector net ((132.54, 126.19), (145.46, 126.19)), ((138, 126.19), (138, 132.96)), # emitter ((132.54, 133.81), (132.54, 136.96)), # comp in+ ((150.54, 126.19), (162.38, 126.19)), ((162.38, 126.19), (162.38, 127.46)), # vref ((150, 126.54), (150, 135.96)), ((150, 132.54), (162.38, 132.54)), # comp out -> PA0 ((177.62, 130), (177.62, 124.92)), ((177.62, 124.92), (202.3, 124.92)), # PA1 -> R5 ((227.7, 124.92), (242.46, 124.92)), # R5 -> OPTO.1 ((247.54, 124.92), (257.38, 124.92)), ((257.38, 124.92), (257.38, 122.38)), # OPTO.4 -> gate ((272.62, 122.38), (294.92, 122.38)), ((294.92, 122.38), (294.92, 126.19)), # drain -> K1.A2 ((302.54, 122.38), (317.38, 122.38)), ((317.38, 122.38), (317.38, 124.92)), # PA2 -> R6 ((227.7, 130), (242.46, 130)), # R6 -> D2.A ((247.54, 130), (277.46, 130)), ] # ---------- net labels ---------- LABELS = [ ("VCC", (100, 115), 0), ("GND", (100, 145), 0), ("VCC", (227.7, 135.08), 0), ("GND", (202.3, 135.08), 0), ("COM", (332.62, 122.38), 0), ("NO", (332.62, 117.3), 0), ("NC", (332.62, 127.46), 0), ] # ---------- texts ---------- TEXTS = [ ("环路车检器原理图 — LC 振荡 + 频率计数方案(推荐①)", (20, 20), 3.5), ("信号链:环形线圈 → T1 匹配变压器 → 考毕兹振荡 → 比较器整形 → MCU 测频判车 → 光耦隔离 → 继电器输出", (20, 30), 1.5), ("接路面环形线圈(3~5匝,双绞屏蔽线 ≤20m)", (20, 108), 1.27), ("→ 道闸控制器 / PLC 输入", (340, 114), 1.27), ("【设计说明】", (20, 165), 1.5), ("1) 线圈经 T1 匹配变压器隔离耦合,抗地环路干扰;", (20, 173), 1.27), ("2) 谐振频率 f0 ≈ 1/(2π√(L·C1)),建议 20~150kHz(待现场验证);", (20, 181), 1.27), ("3) MCU 周期法测频(输入捕获),灵敏度阈值多档可调 + 回差防抖;", (20, 189), 1.27), ("4) 基准频率上电自学习 + 慢速跟踪,抗温漂(雨雪/昼夜温差);", (20, 197), 1.27), ("5) 输出经光耦 U2 隔离驱动继电器 K1,防浪涌反灌烧 MCU;", (20, 205), 1.27), ("6) 软件加看门狗;继电器粘连/掉线检测;", (20, 213), 1.27), ("7) 电源:VIN 12~24V → 防反接 + TVS → LDO 5V → LDO 3.3V(本页未画出);", (20, 221), 1.27), ("8) 相邻车道串扰:线圈间距 ≥1m,必要时交替频率;", (20, 229), 1.27), ("【关键参数(参考值,待现场验证)】", (240, 165), 1.5), ("C1: 100~470pF C2: 100pF", (240, 173), 1.27), ("Rb1: 100k Rb2: 47k", (240, 181), 1.27), ("Rc: 2.2k Re: 1k", (240, 189), 1.27), ("Q1: S9018/2N3904 Q2: N-MOS", (240, 197), 1.27), ("U1: LM393 比较器 U2: PC817 光耦", (240, 205), 1.27), ("K1: SPDT 12V/24V 继电器", (240, 213), 1.27), ("MCU: STM32F103/GD32F103", (240, 221), 1.27), ] # ---------- build ---------- def fmt_xy(p): return f"(xy {p[0]:g} {p[1]:g})" parts = [] ROOT_UUID = uid() parts.append("(kicad_sch") parts.append(" (version 20230121)") parts.append(' (generator eeschema)') parts.append(f" (uuid {ROOT_UUID})") parts.append(' (paper "A3")') parts.append(" (title_block") parts.append(' (title "Loop Vehicle Detector")') parts.append(' (date "2026-08-21")') parts.append(' (rev "1.0")') parts.append(' (company "xde")') parts.append(' (comment 1 "LC Oscillation + Frequency Counting")') parts.append(" )") parts.append("") # lib_symbols (indent 2) parts.append(lib_symbols.rstrip("\n")) parts.append("") # components for lib, ref, value, x, y, rot, ref_at, val_at in COMPONENTS: parts.append(" (symbol") parts.append(f" (lib_id \"loopdet:{lib}\")") parts.append(f" (at {x:g} {y:g} {rot})") parts.append(" (unit 1)") parts.append(" (in_bom yes)") parts.append(" (on_board yes)") parts.append(" (dnp no)") parts.append(f" (uuid {uid()})") parts.append(f" (property \"Reference\" \"{ref}\" (at {ref_at[0]:g} {ref_at[1]:g} 0) (effects (font (size 1.27 1.27)) (justify left)))") parts.append(f" (property \"Value\" \"{value}\" (at {val_at[0]:g} {val_at[1]:g} 0) (effects (font (size 1.27 1.27)) (justify left)))") parts.append(f" (property \"Footprint\" \"\" (at {x:g} {y:g} 0) (effects (font (size 1.27 1.27)) hide))") parts.append(f" (property \"Datasheet\" \"\" (at {x:g} {y:g} 0) (effects (font (size 1.27 1.27)) hide))") # pin uuids (number of pins depends on lib) PINMAP = { "R": ["1", "2"], "C": ["1", "2"], "TVS_BI": ["1", "2"], "LED": ["A", "K"], "Q_NPN": ["B", "C", "E"], "MOS_N": ["G", "D", "S"], "XFMR": ["1", "2", "3", "4"], "OPTO": ["1", "2", "4", "3"], "COMP": ["+", "-", "OUT"], "MCU": ["PA0", "GND", "PA1", "PA2", "VDD"], "RELAY_SPDT": ["A1", "A2", "COM", "NO", "NC"], "CONN2": ["1", "2"], } for pn in PINMAP[lib]: parts.append(f" (pin \"{pn}\" (uuid {uid()}))") parts.append(" (instances") parts.append(" (project \"loop-detector\"") parts.append(f" (path \"/{ROOT_UUID}\"") parts.append(f" (reference \"{ref}\") (unit 1)") parts.append(" )") parts.append(" )") parts.append(" )") parts.append(" )") parts.append("") # wires for p1, p2 in WIRES: parts.append(" (wire") parts.append(f" (pts {fmt_xy(p1)} {fmt_xy(p2)})") parts.append(" (stroke (width 0) (type default))") parts.append(f" (uuid {uid()})") parts.append(" )") parts.append("") # net labels for name, pos, ang in LABELS: parts.append(" (label") parts.append(f" \"{name}\" (at {pos[0]:g} {pos[1]:g} {ang})") parts.append(" (effects (font (size 1.27 1.27)) (justify left bottom))") parts.append(f" (uuid {uid()})") parts.append(" )") parts.append("") # texts for s, pos, size in TEXTS: parts.append(" (text") parts.append(f" \"{s}\" (at {pos[0]:g} {pos[1]:g} 0)") parts.append(f" (effects (font (size {size:g} {size:g})))") parts.append(f" (uuid {uid()})") parts.append(" )") parts.append("") parts.append(" (sheet_instances") parts.append(' (path "/" (page "1"))') parts.append(" )") parts.append(")") out = "\n".join(parts) + "\n" open(SCH, "w", encoding="utf-8").write(out) print("wrote", SCH, len(out), "bytes")