Drogheda/scripts/2026-09-25_v6_T14_sim_holder_primary.py

133 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""v6 T14 — nano-SIM holder becomes the PRIMARY SIM (Telcel will not supply MFF2).
Context: T13 (17-Sep) built a dual-SIM block with the soldered MFF2 (U22) populated and
the JAE SF72S006 holder (J14) DNP. On 25-Sep Leo confirmed Telcel will NOT provide the
MFF2, so as-is the board would ship with jumpers to an empty footprint and no SIM socket.
This script (schematic only — the PCB picks it up via Update PCB from Schematic):
1. Flips population: J14 + R131..R134 -> populated ; U22 + R127..R130 -> DNP.
The MFF2 footprint stays on the board as a future option.
2. Fixes the 5 ERC errors left by T13:
- no-connect flags on J14 C6 (VPP), CSW, DSW
- PWR_FLAG on /SIMB_VDD and /SIMA_VDD (both are fed through 0R jumpers,
so ERC could not see a driver)
Method: clone-and-transform from existing blocks in the same file (PF19 for the flags,
an existing no_connect), fresh UUIDs, invariant guards. Dry-run by default.
python3 2026-09-25_v6_T14_sim_holder_primary.py hbox-watchdog-v6.kicad_sch # dry run
python3 2026-09-25_v6_T14_sim_holder_primary.py hbox-watchdog-v6.kicad_sch --apply # writes .T14bak
"""
import re, sys, uuid, shutil
POPULATE = ['J14', 'R131', 'R132', 'R133', 'R134']
MAKE_DNP = ['U22', 'R127', 'R128', 'R129', 'R130']
NO_CONNECT = [(327.3, 487.54), (352.7, 482.46), (352.7, 485.0)] # J14 C6, CSW, DSW
FLAGS = [('PF20', 310.0, 453.81, 'SIMB_VDD'), ('PF21', 310.0, 423.81, 'SIMA_VDD')]
def span(s, i):
d = 0; instr = False; esc = False; j = i
while True:
c = s[j]
if esc: esc = False
elif c == '\\': esc = True
elif c == '"': instr = not instr
elif not instr:
if c == '(': d += 1
elif c == ')':
d -= 1
if d == 0: return j + 1
j += 1
def balanced(s):
d = 0; instr = False; esc = False
for c in s:
if esc: esc = False; continue
if c == '\\': esc = True; continue
if c == '"': instr = not instr; continue
if instr: continue
if c == '(': d += 1
elif c == ')': d -= 1
if d < 0: return False
return d == 0
UUID = re.compile(r'\(uuid "([0-9a-f-]{36})"\)')
def sym_block(s, ref):
m = re.search(r'\(property "Reference" "%s"\s' % re.escape(ref), s)
if not m: raise SystemExit('reference not found: ' + ref)
a = s.rfind('\n\t(symbol', 0, m.start()) + 1
return a, span(s, a)
def main():
path = sys.argv[1]; apply = '--apply' in sys.argv
s0 = open(path).read(); s = s0
assert balanced(s0), 'input not paren-balanced'
uuids0 = set(UUID.findall(s0))
changes = []
# 1. population flip — only the symbol's own top-level (dnp …) token
for ref, want in [(r, 'no') for r in POPULATE] + [(r, 'yes') for r in MAKE_DNP]:
a, e = sym_block(s, ref); blk = s[a:e]
m = re.search(r'\n\t\t\(dnp (yes|no)\)', blk)
if not m: raise SystemExit('no dnp flag on ' + ref)
if m.group(1) != want:
blk2 = blk[:m.start()] + '\n\t\t(dnp %s)' % want + blk[m.end():]
s = s[:a] + blk2 + s[e:]
changes.append(f'{ref}: dnp {m.group(1)} -> {want}')
# 2a. no-connect flags (skip any already present)
tmpl = re.search(r'\n\t\(no_connect\s*\(at [^)]*\)\s*\(uuid "[^"]+"\)\s*\)', s).group(0)
existing_nc = {(round(float(x), 2), round(float(y), 2))
for x, y in re.findall(r'\(no_connect\s*\(at (-?[\d.]+) (-?[\d.]+)\)', s)}
add = ''
for x, y in NO_CONNECT:
if (round(x, 2), round(y, 2)) in existing_nc: continue
nb = re.sub(r'\(at [^)]*\)', f'(at {x:g} {y:g})', tmpl, count=1)
nb = UUID.sub(f'(uuid "{uuid.uuid4()}")', nb, count=1)
add += nb; changes.append(f'no_connect @({x:g},{y:g})')
# 2b. PWR_FLAGs cloned from PF19
a, e = sym_block(s, 'PF19'); pf = s[a:e]
ox, oy = map(float, re.search(r'\n\t\t\(at (-?[\d.]+) (-?[\d.]+) ', pf).groups())
for ref, x, y, net in FLAGS:
if re.search(r'\(property "Reference" "%s"\s' % ref, s): continue
nb = pf
def mv(m):
px, py = float(m.group(1)), float(m.group(2))
return f'(at {px - ox + x:g} {py - oy + y:g}{m.group(3)})'
nb = re.sub(r'\(at (-?[\d.]+) (-?[\d.]+)((?: -?[\d.]+)?)\)', mv, nb)
nb = nb.replace('"PF19"', f'"{ref}"')
nb = UUID.sub(lambda m: f'(uuid "{uuid.uuid4()}")', nb)
add += '\n' + nb; changes.append(f'{ref} PWR_FLAG on /{net} @({x:g},{y:g})')
if add:
cut = s.rstrip().rfind('\n)')
s = s[:cut] + add + s[cut:]
# guards
assert balanced(s), 'output not paren-balanced'
uuids1 = UUID.findall(s)
assert uuids0 <= set(uuids1), 'a pre-existing UUID disappeared'
assert len(uuids1) == len(set(uuids1)), 'duplicate UUID'
refs = re.findall(r'\n\t\t\(property "Reference" "([^"#][^"]*)"', s)
dup = {r for r in refs if refs.count(r) > 1}
assert not dup, f'duplicate references: {dup}'
for ref in POPULATE:
a, e = sym_block(s, ref); assert '\n\t\t(dnp no)' in s[a:e], ref
for ref in MAKE_DNP:
a, e = sym_block(s, ref); assert '\n\t\t(dnp yes)' in s[a:e], ref
print(f'{len(changes)} change(s):'); [print(' ', c) for c in changes]
print(f'UUIDs: {len(uuids0)} kept, {len(uuids1) - len(uuids0)} new, 0 duplicated; paren-balanced')
if not changes:
print('nothing to do (already applied)'); return
if apply:
shutil.copy2(path, path + '.T14bak'); open(path, 'w').write(s); print('APPLIED (backup .T14bak)')
else:
print('DRY RUN — re-run with --apply')
if __name__ == '__main__':
main()