T14 script extended (idempotent): J14 gets LCSC C2977286 and a primary-SIM description. ERC 0 errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJ4eAB6zjQT49RVwpgfKdt
160 lines
7.2 KiB
Python
160 lines
7.2 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)
|
|
3. Gives J14 its LCSC field (C2977286) so it lands in the JLCPCB BOM, and rewrites its
|
|
instance Description (was "DNP alternate to U22").
|
|
|
|
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')]
|
|
J14_LCSC = 'C2977286' # JAE SF72S006VBDR2500 — JLC "Pre-ordered Products": pre-order stock before PCBA
|
|
J14_DESC = ('Nano-SIM holder JAE SF72S006VBDR2500 (LCSC C2977286) - PRIMARY SIM since T14 '
|
|
'(2026-09-25, Telcel will not supply MFF2). U22 MFF2 is the DNP alternate.')
|
|
|
|
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:]
|
|
|
|
# 3. J14 becomes a JLCPCB-assembled part: give it the LCSC field (cloned from R131's
|
|
# LCSC property block) and fix its instance Description, which still said "DNP alternate".
|
|
a, e = sym_block(s, 'J14'); blk = s[a:e]
|
|
if not re.search(r'\(property "LCSC" ', blk):
|
|
ra, re_ = sym_block(s, 'R131'); rblk = s[ra:re_]
|
|
m = re.search(r'\n\t\t\(property "LCSC" "', rblk)
|
|
pa = m.start() + 3; lc = rblk[pa:span(rblk, pa)] # from '(' — indent added below
|
|
lc = re.sub(r'"LCSC" "[^"]*"', f'"LCSC" "{J14_LCSC}"', lc, count=1)
|
|
jx, jy = re.search(r'\n\t\t\(at (-?[\d.]+) (-?[\d.]+) ', blk).groups()
|
|
lc = re.sub(r'\(at [^)]*\)', f'(at {jx} {jy} 0)', lc, count=1)
|
|
d = re.search(r'\n\t\t\(property "Description" "', blk)
|
|
da = d.start() + 1; de = span(blk, da)
|
|
blk = blk[:de] + '\n\t\t' + lc + blk[de:]
|
|
changes.append(f'J14: + LCSC {J14_LCSC}')
|
|
old_desc = '"Nano-SIM holder JAE SF72S006 (LCSC C2977286) - DNP alternate to U22"'
|
|
if old_desc in blk:
|
|
blk = blk.replace(old_desc, '"' + J14_DESC + '"', 1)
|
|
changes.append('J14: Description -> primary SIM')
|
|
s = s[:a] + blk + s[e:]
|
|
|
|
# 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
|
|
a, e = sym_block(s, 'J14')
|
|
assert f'(property "LCSC" "{J14_LCSC}"' in s[a:e], 'J14 LCSC missing'
|
|
|
|
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()
|