#!/usr/bin/env python3 """v6 T4 - Blocks O+P+Q+R: A7670SA modem, 3.8V OR-input rail, MFF2 SIM, level shifters. Sources of record: A7670SA pin map = EasyEDA/LCSC C18548276 symbol (VBAT 55/56/57, RF_ANT 60 match the SIMCom HW design guide). AP64350 values = datasheet DS41976: Vref 0.8V, RT 200k = 500kHz (must not float), COMP 14k+3.3nF(+47pF), BST 100nF, L 4.7uH, EP->GND. Usage: python3 2026-08-25_v6_T4_lte_island.py [--apply] """ import os, sys, shutil, uuid, re KI = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '03-Architecture-Hardware', 'kicad') SCH = os.path.join(KI, 'hbox-watchdog-v6.kicad_sch') DSYM= os.path.join(KI, 'Drogheda-sym.kicad_sym') VSYM= os.path.join(KI, 'Drogheda-v6.kicad_sym') PCB = os.path.join(KI, 'hbox-watchdog-v6.kicad_pcb') APPLY = '--apply' in sys.argv ROOT = '/561403b3-895e-4153-a7be-3876ae930d43' NC = '' def f(v): v = 0.0 if abs(v) < 1e-9 else v return '%.10g' % v def bal(t): d=0; instr=False; esc=False for ch in t: if esc: esc=False; continue if ch=='\\': esc=True; continue if ch=='"': instr = not instr; continue if instr: continue if ch=='(': d+=1 elif ch==')': d-=1 return d def span_from(text, i): d=0; instr=False; esc=False; j=i while j < len(text): ch=text[j] if esc: esc=False elif ch=='\\': esc=True elif ch=='"': instr = not instr elif not instr: if ch=='(': d+=1 elif ch==')': d-=1 if d==0: return j+1 j+=1 raise ValueError('unbalanced') PIN_RE = re.compile(r'\(pin \w+ \w+\s*\(?at ([-\d.]+) ([-\d.]+) (\d+)\)(?:(?!\(pin ).)*?\(number "([^"]+)"', re.S) PIN_RE2= re.compile(r'\(pin \w+ \w+\s*\n\s*\(at ([-\d.]+) ([-\d.]+) (\d+)\)(?:(?!\(pin ).)*?\(number "([^"]+)"', re.S) def libpins(libsrc, name): i = libsrc.find('(symbol "%s"' % name) if i < 0: return None j = libsrc.find('\n (symbol "', i+10) if j < 0: j = libsrc.find('\n\t\t(symbol "', i+10) body = libsrc[i: j if j>0 else len(libsrc)] pins = PIN_RE2.findall(body) or PIN_RE.findall(body) return [(num, float(x), float(y)) for x,y,rot,num in pins] sch = open(SCH, encoding='utf-8').read() dsym = open(DSYM, encoding='utf-8').read() vsym = open(VSYM, encoding='utf-8').read() # ---------- 0. MFF2 symbol into Drogheda-sym (if absent) ---------- MFF2 = ''' (symbol "MFF2_SIM" (exclude_from_sim no) (in_bom yes) (on_board yes) (property "Reference" "U" (at 0 12.7 0) (effects (font (size 1.27 1.27))) ) (property "Value" "MFF2_SIM" (at 0 10.16 0) (effects (font (size 1.27 1.27))) ) (property "Footprint" "Drogheda:MFF2_5x6_TBD" (at 0 -5.08 0) (effects (font (size 1.27 1.27))) (hide yes) ) (property "Datasheet" "~" (at 0 -7.62 0) (effects (font (size 1.27 1.27))) (hide yes) ) (property "Description" "MFF2 / DFN-8 5x6 mm soldered M2M SIM (ETSI TS 102 671). Pads: 1=C1 VCC, 2=C2 RST, 3=C3 CLK, 4=C4 NC, 5=C5 GND, 6=C6 VPP (NC), 7=C7 I/O, 8=C8 NC. FOOTPRINT TBD - build from the chosen carrier chip datasheet before layout. Carrier-supplied part, not on LCSC." (at 0 -10.16 0) (effects (font (size 1.27 1.27))) (hide yes) ) (symbol "MFF2_SIM_0_1" (rectangle (start -7.62 8.89) (end 7.62 -8.89) (stroke (width 0.254) (type default)) (fill (type background)) ) ) (symbol "MFF2_SIM_1_1" ''' MFF2_PINS=[('1','VCC','power_in',-12.7,5.08,0),('2','RST','input',-12.7,2.54,0),('3','CLK','input',-12.7,0,0), ('4','NC4','passive',-12.7,-2.54,0),('5','GND','power_in',0,-13.97,90),('6','VPP','passive',12.7,-2.54,180), ('7','IO','bidirectional',12.7,2.54,180),('8','NC8','passive',12.7,0,180)] for num,nm,tp,px,py,rot in MFF2_PINS: MFF2 += (' (pin %s line\n (at %s %s %d)\n (length 5.08)\n' ' (name "%s"\n (effects (font (size 1.27 1.27)))\n )\n' ' (number "%s"\n (effects (font (size 1.27 1.27)))\n )\n )\n') % (tp,f(px),f(py),rot,nm,num) MFF2 += ' )\n )\n' mff2_added = False if '(symbol "MFF2_SIM"' not in dsym: end = dsym.rstrip(); assert end.endswith(')') dsym = end[:-1].rstrip('\n') + '\n' + MFF2 + ')\n' mff2_added = True # ---------- 1. locate donors / relabel targets ---------- lib_cached = sch.split('(lib_symbols',1)[1] def cachedpins(name): return libpins(lib_cached, name) inst = {} for b in re.finditer(r'\n\t\(symbol\n(?:(?!\n\t\(symbol\n).)*', sch, re.S): blk=b.group(0) r=re.search(r'\(property "Reference" "([^"]+)"',blk); l=re.search(r'\(lib_id "([^"]+)"',blk); a=re.search(r'\(at ([-\d.]+) ([-\d.]+) (\d+)\)',blk) if r and l and a: inst[r.group(1)]=(l.group(1),float(a.group(1)),float(a.group(2))) def find_labels(text): d={} for m in re.finditer(r'\n\t\((label|global_label) "([^"]+)"\s*\n\s*\(at ([-\d.]+) ([-\d.]+) (\d+)\)', text): d[(round(float(m.group(3)),3), round(float(m.group(4)),3))] = (m.group(2), m.start()+1) return d labs = find_labels(sch) # D4G1 cathode relabel target: the GND label at (74.93, 265.43), PROVEN wire-connected to # D4G1 pin1 by graph trace on 2026-08-25 (the sch has 382 wire segments - the earlier # "zero wires" note was a regex artifact; labels here sit on WIRES, not on pins). kx,ky = 74.93, 265.43 if (kx,ky) not in labs or labs[(kx,ky)][0] != 'GND': sys.exit('expected the D4G1-cathode GND label at %s,%s; found %s' % (kx,ky,labs.get((kx,ky)))) if (55.88,260.35) not in labs or labs[(55.88,260.35)][0] != 'MODEM_LED': sys.exit('orphan MODEM_LED label not found at (55.88,260.35)') # wire vertices + all existing pin coords, for the collision guard wire_vts = set() for m in re.finditer(r'\(wire\s*\n\s*\(pts\s*\n((?:\s*\(xy [-\d.]+ [-\d.]+\)\s*\n?)+)', sch): for a,b in re.findall(r'\(xy ([-\d.]+) ([-\d.]+)\)', m.group(1)): wire_vts.add((round(float(a),3), round(float(b),3))) pin_coords = set() for ref,(lb_,X_,Y_) in inst.items(): pp = cachedpins(lb_) or [] for num,px,py in pp: pin_coords.add((round(X_+px,3), round(Y_-py,3))) # D14 orientation donor (PESD): read its pad nets from the PCB pcb = open(PCB, encoding='utf-8').read() d14 = re.search(r'\n\t\(footprint (?:(?!\n\t\(footprint ).)*?\(property "Reference" "D14"(?:(?!\n\t\(footprint ).)*', pcb, re.S).group(0) d14pads = dict(re.findall(r'\(pad "(\d)"(?:(?!\(pad ").)*?\(net "([^"]*)"\)', d14, re.S)) sig_pin = '1' if d14pads.get('2')=='GND' else '2' d14lib = inst['D14'][0] # ---------- 2. cache the vendor symbols + MFF2 into the schematic ---------- def grab(libsrc, name, prefix): i = libsrc.find(' (symbol "%s"\n' % name) if i < 0: sys.exit('symbol %s not found in source lib' % name) j = libsrc.index('(', i) k = span_from(libsrc, j) return libsrc[j:k].replace('(symbol "%s"' % name, '(symbol "%s:%s"' % (prefix,name), 1) cache_add = '' for nm in ('A7670SA-LASC','AP64350SP-13','74LVC2T45DC,125','PMEG3050EP,115','SMAJ5.0A_C113952'): if '(symbol "Drogheda-v6:%s"' % nm not in sch: cache_add += '\n\t\t' + grab(vsym, nm, 'Drogheda-v6') if '(symbol "Drogheda-sym:MFF2_SIM"' not in sch: cache_add += '\n\t\t' + grab(dsym, 'MFF2_SIM', 'Drogheda-sym') m = re.search(r'\n\t\(lib_symbols\n', sch) k = span_from(sch, sch.index('(', m.start())) sch = sch[:k-1] + cache_add + '\n\t' + sch[k-1:] def newpins(name, src): pp = libpins(src, name) if not pp: sys.exit('no pins found for %s' % name) return pp # ---------- 3. build additions ---------- add = '' nlab = nnc = 0 newlabels = [] def add_lab(net,x,y,rot,just): global add,nlab add += ('\n\t(label "%s"\n\t\t(at %s %s %d)\n\t\t(effects\n\t\t\t(font\n\t\t\t\t(size 1.27 1.27)\n' '\t\t\t)\n\t\t\t(justify %s)\n\t\t)\n\t\t(uuid "%s")\n\t)') % (net,f(x),f(y),rot,just,uuid.uuid4()) nlab += 1; newlabels.append((net,round(x,3),round(y,3))) def add_nc(x,y): global add,nnc add += '\n\t(no_connect\n\t\t(at %s %s)\n\t\t(uuid "%s")\n\t)' % (f(x),f(y),uuid.uuid4()) nnc += 1 def put(net,x,y,rot,just): if net == NC: add_nc(x,y) else: add_lab(net,x,y,rot,just) def mksym(lib_id, ref, val, x, y, fp, npins, lcsc='', dnp=False): props=[('Reference',ref,x-3,y-3,''),('Value',val,x+3,y-3,''),('Footprint',fp,x,y,'\n\t\t\t(hide yes)'), ('Datasheet','~',x,y,'\n\t\t\t(hide yes)'),('Description','',x,y,'\n\t\t\t(hide yes)')] if lcsc: props.append(('LCSC',lcsc,x,y,'\n\t\t\t(hide yes)')) p='' for nm,v,px,py,hide in props: p += ('\t\t(property "%s" "%s"\n\t\t\t(at %s %s 0)%s\n\t\t\t(show_name no)\n\t\t\t(do_not_autoplace no)\n' '\t\t\t(effects\n\t\t\t\t(font\n\t\t\t\t\t(size 1.27 1.27)\n\t\t\t\t)\n\t\t\t)\n\t\t)\n') % (nm,v,f(px),f(py),hide) pn=''.join('\t\t(pin "%s"\n\t\t\t(uuid "%s")\n\t\t)\n' % (n,uuid.uuid4()) for n in npins) return ('\n\t(symbol\n\t\t(lib_id "%s")\n\t\t(at %s %s 0)\n\t\t(unit 1)\n\t\t(body_style 1)\n' '\t\t(exclude_from_sim no)\n\t\t(in_bom yes)\n\t\t(on_board yes)\n\t\t(in_pos_files yes)\n' '\t\t(dnp %s)\n\t\t(uuid "%s")\n%s%s\t\t(instances\n\t\t\t(project "hbox-watchdog-v6"\n' '\t\t\t\t(path "%s"\n\t\t\t\t\t(reference "%s")\n\t\t\t\t\t(unit 1)\n\t\t\t\t)\n\t\t\t)\n\t\t)\n\t)' ) % (lib_id,f(x),f(y),'yes' if dnp else 'no',uuid.uuid4(),p,pn,ROOT,ref) def orient(px,py): if abs(px) < 0.01: return 90,'left bottom' return (0,'left bottom') if px>0 else (180,'right bottom') # ----- Block O: U19 A7670SA ----- U19X,U19Y = 330.0, 330.0 NET19 = {55:'VBAT_MODEM',56:'VBAT_MODEM',57:'VBAT_MODEM', 9:'DOUT_4G_M',10:'DIN_4G_M',1:'MODEM_PWRKEY_M',16:'MODEM_RST_M',66:'MODEM_STATUS_M', 52:'NETLIGHT_1V8',24:'USB_VBUS',27:'USB_DP',28:'USB_DM',6:'MODEM_BOOT',15:'1V8_MODEM', 30:'SIM_VDD',31:'SIM_DATA',32:'SIM_CLK',33:'SIM_RST',60:'RF_LTE'} GND19 = {2,17,18,29,39,45,46,54,58,59,61,62,63,64,65,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,91,92,94} a7pins = newpins('A7670SA-LASC', vsym) if len(a7pins) != 124: sys.exit('A7670SA pin extract failed: %d' % len(a7pins)) add += mksym('Drogheda-v6:A7670SA-LASC','U19','A7670SA-LASC',U19X,U19Y, 'Drogheda-v6:LGA-124_L24.0-W24.0-P1.0-A7670C-LASS',[num for num,_,_ in a7pins],'C18548276') u19 = {'lab':0,'gnd':0,'nc':0} for num,px,py in a7pins: n=int(num); x,y = U19X+px, U19Y-py rot,just = orient(px,py) if n in NET19: add_lab(NET19[n],x,y,rot,just); u19['lab']+=1 elif n in GND19: add_lab('GND',x,y,rot,just); u19['gnd']+=1 else: add_nc(x,y); u19['nc']+=1 # ----- Block P ----- U20X,U20Y = 262.0, 300.0 bpins = newpins('AP64350SP-13', vsym) add += mksym('Drogheda-v6:AP64350SP-13','U20','AP64350SP-13',U20X,U20Y, 'Drogheda-v6:SO-8_L4.9-W3.9-P1.27-LS6.0-BL-EP',[num for num,_,_ in bpins],'C2071691') NET20 = {'1':'BST_MODEM','2':'VIN_MODEM','3':'MODEM_PWR_EN','4':'RT_MODEM','5':'FB_MODEM', '6':'COMP_MODEM','7':'GND','8':'SW_MODEM','9':'GND'} for num,px,py in bpins: rot,just = orient(px,py) add_lab(NET20[num],U20X+px,U20Y-py,rot,just) # diodes: D_Schottky and PMEG symbol both 1=K 2=A; SMAJ pad1 assumed K (VERIFY at layout) DIODES=[('D20','SS34','Device:D_Schottky','Diode_SMD:D_SMA','',240.0,286.0,'VIN_MODEM','24V'), ('D21','PMEG3050EP','Drogheda-v6:PMEG3050EP,115','Drogheda-v6:SOD-128_L3.7-W2.5-LS4.7-RD','C50603',240.0,293.0,'VIN_MODEM','MAIN5V'), ('D22','SMAJ5.0A','Drogheda-v6:SMAJ5.0A_C113952','Drogheda-v6:SMA_L4.4-W2.6-LS5.0-RD','C113952',300.0,370.0,'VBAT_MODEM','GND')] for ref,val,lib,fp,lcsc,x,y,knet,anet in DIODES: nm = lib.split(':')[1] pp = newpins(nm, vsym) if lib.startswith('Drogheda-v6') else cachedpins(lib) add += mksym(lib,ref,val,x,y,fp,[num for num,_,_ in pp],lcsc) for num,px,py in pp: rot,just = orient(px,py) add_lab(knet if num=='1' else anet, x+px,y-py,rot,just) L20X,L20Y=300.0,300.0 lp={num:(px,py) for num,px,py in cachedpins('Device:L')} add += mksym('Device:L','L20','4.7uH/5A',L20X,L20Y,'Inductor_SMD:L_Bourns-SRU8028_8.0x8.0mm',['1','2']) add_lab('SW_MODEM', L20X+lp['1'][0],L20Y-lp['1'][1],90,'left bottom') add_lab('VBAT_MODEM',L20X+lp['2'][0],L20Y-lp['2'][1],90,'left bottom') PASS=[('C127','10uF','Device:C','Capacitor_SMD:C_0805_2012Metric','',252.0,286.0,'VIN_MODEM','GND',False), ('C128','1uF','Device:C','Capacitor_SMD:C_0603_1608Metric','',258.0,286.0,'VIN_MODEM','GND',False), ('C129','100nF','Device:C','Capacitor_SMD:C_0402_1005Metric','',276.0,286.0,'BST_MODEM','SW_MODEM',False), ('R117','200k/1%','Device:R','Resistor_SMD:R_0603_1608Metric','',246.0,310.0,'RT_MODEM','GND',False), ('R116','14k','Device:R','Resistor_SMD:R_0603_1608Metric','',252.0,310.0,'COMP_MODEM','COMP_RC',False), ('C141','3.3nF','Device:C','Capacitor_SMD:C_0402_1005Metric','',258.0,310.0,'COMP_RC','GND',False), ('C142','47pF','Device:C','Capacitor_SMD:C_0402_1005Metric','',264.0,310.0,'COMP_MODEM','GND',False), ('R110','75k/1%','Device:R','Resistor_SMD:R_0603_1608Metric','',282.0,310.0,'VBAT_MODEM','FB_MODEM',False), ('R111','20k/1%','Device:R','Resistor_SMD:R_0603_1608Metric','',288.0,310.0,'FB_MODEM','GND',False), ('R113','100k','Device:R','Resistor_SMD:R_0603_1608Metric','',270.0,310.0,'MODEM_PWR_EN','GND',False), ('C130','220uF/10V','Device:C_Polarized','Capacitor_SMD:CP_Elec_6.3x7.7','C2827940',310.0,370.0,'VBAT_MODEM','GND',False), ('C131','220uF/10V','Device:C_Polarized','Capacitor_SMD:CP_Elec_6.3x7.7','C2827940',318.0,370.0,'VBAT_MODEM','GND',False), ('C132','22uF','Device:C','Capacitor_SMD:C_0805_2012Metric','C86817',326.0,370.0,'VBAT_MODEM','GND',False), ('C133','1uF','Device:C','Capacitor_SMD:C_0603_1608Metric','',332.0,370.0,'VBAT_MODEM','GND',False), ('C134','100nF','Device:C','Capacitor_SMD:C_0402_1005Metric','',338.0,370.0,'VBAT_MODEM','GND',False), ('C135','33pF','Device:C','Capacitor_SMD:C_0402_1005Metric','',344.0,370.0,'VBAT_MODEM','GND',False), ('C136','10pF','Device:C','Capacitor_SMD:C_0402_1005Metric','',350.0,370.0,'VBAT_MODEM','GND',False), ('R112','1k','Device:R','Resistor_SMD:R_0603_1608Metric','',356.0,370.0,'VBAT_MODEM','GND',False), ('C125','2.2uF','Device:C','Capacitor_SMD:C_0603_1608Metric','',380.0,300.0,'1V8_MODEM','GND',False), ('C126','100nF','Device:C','Capacitor_SMD:C_0402_1005Metric','',400.0,352.0,'SIM_VDD','GND',False), ('R106','100R','Device:R','Resistor_SMD:R_0603_1608Metric','',390.0,255.0,'DOUT_4G','USART6_RX',False), ('R109','4.7k','Device:R','Resistor_SMD:R_0603_1608Metric','',380.0,286.0,'NETLIGHT_1V8','Q22_BASE',False), ('R114','100k','Device:R','Resistor_SMD:R_0603_1608Metric','',252.0,340.0,'MODEM_PWRKEY','GND',False), ('R115','100k','Device:R','Resistor_SMD:R_0603_1608Metric','',276.0,340.0,'MODEM_RST','GND',False), ('R107','0R-DNP','Device:R','Resistor_SMD:R_0603_1608Metric','',264.0,255.0,'DOUT_4G_M','DOUT_4G',True), ('R108','0R-DNP','Device:R','Resistor_SMD:R_0603_1608Metric','',270.0,255.0,'DIN_4G','DIN_4G_M',True), ('C137','100nF','Device:C','Capacitor_SMD:C_0402_1005Metric','',248.0,395.0,'1V8_MODEM','GND',False), ('C138','100nF','Device:C','Capacitor_SMD:C_0402_1005Metric','',288.0,395.0,'3V3','GND',False), ('C139','100nF','Device:C','Capacitor_SMD:C_0402_1005Metric','',248.0,418.0,'1V8_MODEM','GND',False), ('C140','100nF','Device:C','Capacitor_SMD:C_0402_1005Metric','',288.0,418.0,'3V3','GND',False)] for ref,val,lib,fp,lcsc,x,y,n1,n2,dnp in PASS: pp={num:(px,py) for num,px,py in cachedpins(lib)} add += mksym(lib,ref,val,x,y,fp,['1','2'],lcsc,dnp=dnp) add_lab(n1,x+pp['1'][0],y-pp['1'][1],90,'left bottom') add_lab(n2,x+pp['2'][0],y-pp['2'][1],90,'left bottom') # ----- Block Q: MFF2 + SIM ESD ----- U22X,U22Y=400.0,340.0 mp = newpins('MFF2_SIM', dsym) add += mksym('Drogheda-sym:MFF2_SIM','U22','MFF2-SIM-carrier',U22X,U22Y,'Drogheda:MFF2_5x6_TBD',[num for num,_,_ in mp]) MQ={'1':'SIM_VDD','2':'SIM_RST','3':'SIM_CLK','4':NC,'5':'GND','6':NC,'7':'SIM_DATA','8':NC} for num,px,py in mp: rot,just = orient(px,py) put(MQ[num],U22X+px,U22Y-py,rot,just) dp={num:(px,py) for num,px,py in cachedpins(d14lib)} for ref,sig,x,y in [('D23','SIM_DATA',420.0,352.0),('D24','SIM_CLK',426.0,352.0),('D25','SIM_RST',432.0,352.0)]: add += mksym(d14lib,ref,'PESD5V0S1BA',x,y,'Diode_SMD:D_SOD-323',['1','2'],'C2827694') add_lab(sig if sig_pin=='1' else 'GND', x+dp['1'][0], y-dp['1'][1],90,'left bottom') add_lab('GND' if sig_pin=='1' else sig, x+dp['2'][0], y-dp['2'][1],90,'left bottom') # ----- Block R ----- spins = newpins('74LVC2T45DC,125', vsym) for ref,x,y,nets in [('U23',255.0,385.0,{'1':'1V8_MODEM','2':'DIN_4G_M','3':NC,'4':'GND','5':'GND','6':'GND','7':'DIN_4G','8':'3V3'}), ('U24',255.0,408.0,{'1':'1V8_MODEM','2':'DOUT_4G_M','3':'MODEM_STATUS_M','4':'GND','5':'1V8_MODEM','6':'MODEM_STATUS','7':'DOUT_4G','8':'3V3'})]: add += mksym('Drogheda-v6:74LVC2T45DC,125',ref,'74LVC2T45',x,y,'Drogheda-v6:VSSOP-8_L2.1-W2.4-P0.50-LS3.2-BL',[num for num,_,_ in spins],'C730237') for num,px,py in spins: rot,just = orient(px,py) put(nets[num],x+px,y-py,rot,just) qn = cachedpins('Device:Q_NMOS_GSD') for ref,gate,drain,x,y in [('Q20','MODEM_PWRKEY','MODEM_PWRKEY_M',246.0,330.0),('Q21','MODEM_RST','MODEM_RST_M',270.0,330.0)]: add += mksym('Device:Q_NMOS_GSD',ref,'2N7002',x,y,'Package_TO_SOT_SMD:SOT-23',[num for num,_,_ in qn]) NQ={'1':gate,'2':'GND','3':drain} for num,px,py in qn: rot,just = orient(px,py) add_lab(NQ[num],x+px,y-py,rot,just) qb = cachedpins('Transistor_BJT:Q_NPN_BEC') add += mksym('Transistor_BJT:Q_NPN_BEC','Q22','MMBT3904',390.0,290.0,'Package_TO_SOT_SMD:SOT-23',[num for num,_,_ in qb]) NB={'1':'Q22_BASE','2':'GND','3':'MODEM_LED_K'} for num,px,py in qb: rot,just = orient(px,py) add_lab(NB[num],390.0+px,290.0-py,rot,just) tp = cachedpins('Connector:TestPoint') add += mksym('Connector:TestPoint','TP8','USB_BOOT',420.0,300.0,'TestPoint:TestPoint_Pad_D1.5mm',[num for num,_,_ in tp]) add_lab('MODEM_BOOT',420.0+tp[0][1],300.0-tp[0][2],90,'left bottom') pf = cachedpins('power:PWR_FLAG') for ref,net,x in [('PF15','VIN_MODEM',300.0),('PF16','VBAT_MODEM',310.0),('PF17','1V8_MODEM',320.0)]: add += mksym('power:PWR_FLAG',ref,'PWR_FLAG',x,255.0,'',[num for num,_,_ in pf]) add_lab(net,x+pf[0][1],255.0-pf[0][2],90,'left bottom') MCU_LBL=[('MODEM_PWRKEY',482.6,436.88),('MODEM_STATUS',482.6,439.42),('MODEM_PWR_EN',482.6,441.96),('MODEM_RST',482.6,444.5)] for net,x,y in MCU_LBL: add_lab(net,x,y,180,'right bottom') INTENTIONAL = {(round(x,3),round(y,3)) for _,x,y in MCU_LBL} # remove stale no_connect markers on MCU pins that now carry labels (PC8-PC11 from this # run, and PC1/PC2/PC3/PA7/PC12 which got labels in T3b while their NCs remained) STALE_NC = INTENTIONAL | {(482.6,419.1),(482.6,421.64),(482.6,424.18),(518.16,411.48),(482.6,447.04)} removed_nc = 0 def rm_stale_nc(text): global removed_nc out=[]; last=0 for m in re.finditer(r'\n\t\(no_connect\s*\n\s*\(at ([-\d.]+) ([-\d.]+)\)(?:(?!\n\t\().)*?\n\t\)', text, re.S): c=(round(float(m.group(1)),3),round(float(m.group(2)),3)) if c in STALE_NC: out.append(text[last:m.start()]); last=m.end(); removed_nc+=1 out.append(text[last:]) return ''.join(out) sch = rm_stale_nc(sch) # ---------- 4. relabels (positions re-found on the current sch) ---------- labs = find_labels(sch) def relabel(coord, old, new): global sch name,start = labs[coord] assert name==old, (coord,name,old) end = span_from(sch, sch.index('(', start)) seg = sch[start:end].replace('"%s"'%old, '"%s"'%new, 1) sch = sch[:start] + seg + sch[end:] todo = [((55.88,260.35),'MODEM_LED','3V3'),((kx,ky),'GND','MODEM_LED_K')] for coord,old,new in sorted(todo, key=lambda t:-labs[t[0]][1]): relabel(coord,old,new) # ---------- 5. collision check + write ---------- existing = (set(find_labels(sch).keys()) | wire_vts | pin_coords) - INTENTIONAL coll = [(n,x,y) for n,x,y in newlabels if (x,y) in existing] if coll: sys.exit('LABEL COLLISION with existing labels/wires/pins: %s' % coll[:5]) end = sch.rstrip(); assert end.endswith(')') sch = end[:-1].rstrip('\n') + add + '\n)\n' print('U19: %d net labels + %d GND + %d NC (sum %d, expect 124)' % (u19['lab'],u19['gnd'],u19['nc'],sum(u19.values()))) print('symbols: U19 U20 U22 U23 U24 D20-D25 Q20-Q22 L20 TP8 PF15-17 + %d passives (2 DNP)' % len(PASS)) print('labels added: %d no_connects added: %d' % (nlab,nnc)) print('relabels: MODEM_LED@(55.88,260.35)->3V3 ; GND@D4G1.K(%s,%s)->MODEM_LED_K' % (kx,ky)) print('PESD orientation cloned from D14: signal pin = %s' % sig_pin) print('stale no_connects removed from labeled MCU pins: %d (expect 9)' % removed_nc) print('MFF2 symbol added to Drogheda-sym: %s' % mff2_added) print('paren: sch=%d dsym=%d' % (bal(sch),bal(dsym))) if not APPLY: print('\nDRY RUN - nothing written. Re-run with --apply.'); sys.exit(0) if bal(sch)!=0 or bal(dsym)!=0: sys.exit('REFUSING: paren imbalance') shutil.copy2(SCH,SCH+'.T4bak'); shutil.copy2(DSYM,DSYM+'.T4bak') open(SCH,'w',encoding='utf-8').write(sch) open(DSYM,'w',encoding='utf-8').write(dsym) print('\nAPPLIED. Backups: *.T4bak')