v6 T10 sensor set (submodule 83ed72e) + T10 script
This commit is contained in:
parent
0823de6c29
commit
7fea0a5c11
@ -1 +1 @@
|
||||
Subproject commit b92a18710b694d80c59c840183378a00efddc3ac
|
||||
Subproject commit 83ed72e2d63ef357640189df993fd8765a87f543
|
||||
257
scripts/2026-08-31_v6_T10_sensors.py
Normal file
257
scripts/2026-08-31_v6_T10_sensors.py
Normal file
@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python3
|
||||
"""v6 T10 - final-version sensor set: embedded SHT40 + two cheap external NTC probes.
|
||||
|
||||
- U3 SHT40-AD1B stays soldered on-board (already in the design) = tair + humidity.
|
||||
- DELETE U4 TMP102 (replaced by the 2nd NTC probe channel).
|
||||
- DELETE P5 TEMP_PROBE jack (external SHT40 probe; redundant vs on-board U3, and a
|
||||
probe SHT40 would collide with U3 at I2C address 0x44 anyway).
|
||||
- KEEP P12 + R42 10k pullup + C24 100nF -> PC5 ADC (/NTC_TEMP) = external probe #1 (tntc).
|
||||
- ADD P18 (same JST-XH 2-pin) + R126 10k/1% (3V3 pullup) + C157 100nF -> /NTC2 -> PC4
|
||||
(U16 pin 24, ADC12_IN14) = external probe #2. Identical topology to P12's channel so
|
||||
the firmware conversion carries over (per-probe Beta constant in firmware).
|
||||
|
||||
Usage: python3 2026-08-31_v6_T10_sensors.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')
|
||||
APPLY = '--apply' in sys.argv
|
||||
ROOT = '/561403b3-895e-4153-a7be-3876ae930d43'
|
||||
|
||||
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_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\t\t(symbol "', i+10)
|
||||
body = libsrc[i: j if j>0 else len(libsrc)]
|
||||
return [(num, float(x), float(y)) for x,y,rot,num in PIN_RE2.findall(body)]
|
||||
|
||||
sch = open(SCH, encoding='utf-8').read()
|
||||
uuids_before = set(re.findall(r'\(uuid "([0-9a-f-]+)"\)', sch))
|
||||
lib_cached = sch.split('(lib_symbols',1)[1]
|
||||
def cachedpins(name): return libpins(lib_cached, name)
|
||||
|
||||
# ---------- 1. locate the doomed instances ----------
|
||||
def find_inst(ref):
|
||||
for b in re.finditer(r'\n\t\(symbol\n', sch):
|
||||
a = b.start()+1
|
||||
e = span_from(sch, sch.index('(', a))
|
||||
blk = sch[a:e]
|
||||
if '(property "Reference" "%s"' % ref in blk:
|
||||
l = re.search(r'\(lib_id "([^"]+)"', blk).group(1)
|
||||
m = re.search(r'\(at ([-\d.]+) ([-\d.]+) (\d+)\)', blk)
|
||||
assert m.group(3) == '0' and '(mirror' not in blk[:400], (ref,'not rot0/nomirror')
|
||||
return (a, e, l, float(m.group(1)), float(m.group(2)))
|
||||
sys.exit('%s not found' % ref)
|
||||
|
||||
doomed = {}
|
||||
for ref in ('U4','P5'):
|
||||
doomed[ref] = find_inst(ref)
|
||||
# sanity: expected identities
|
||||
assert 'TMP102' in sch[doomed['U4'][0]:doomed['U4'][1]]
|
||||
assert 'TEMP_PROBE' in sch[doomed['P5'][0]:doomed['P5'][1]]
|
||||
|
||||
# refdes guards for additions
|
||||
for r in ('P18','R126','C157'):
|
||||
if '(property "Reference" "%s"' % r in sch: sys.exit('refdes %s already used' % r)
|
||||
assert '(property "Reference" "P12"' in sch and '(property "Reference" "R42"' in sch
|
||||
|
||||
# ---------- 2. delete labels / no_connects at the doomed pins, then the symbols ----------
|
||||
def pin_coords_of(inst):
|
||||
a,e,l,X,Y = inst
|
||||
pp = cachedpins(l)
|
||||
assert pp, 'no lib pins for %s' % l
|
||||
return {(round(X+px,3), round(Y-py,3)) for num,px,py in pp}
|
||||
|
||||
kill_coords = set()
|
||||
for ref in doomed: kill_coords |= pin_coords_of(doomed[ref])
|
||||
|
||||
removed_lab = removed_nc = 0
|
||||
out=[]; last=0
|
||||
for m in re.finditer(r'\n\t\((label|global_label) "[^"]+"\s*\n\s*\(at ([-\d.]+) ([-\d.]+) (\d+)\)(?:(?!\n\t\().)*?\n\t\)', sch, re.S):
|
||||
c=(round(float(m.group(2)),3),round(float(m.group(3)),3))
|
||||
if c in kill_coords:
|
||||
out.append(sch[last:m.start()]); last=m.end(); removed_lab+=1
|
||||
out.append(sch[last:]); sch=''.join(out)
|
||||
out=[]; last=0
|
||||
for m in re.finditer(r'\n\t\(no_connect\s*\n\s*\(at ([-\d.]+) ([-\d.]+)\)(?:(?!\n\t\().)*?\n\t\)', sch, re.S):
|
||||
c=(round(float(m.group(1)),3),round(float(m.group(2)),3))
|
||||
if c in kill_coords:
|
||||
out.append(sch[last:m.start()]); last=m.end(); removed_nc+=1
|
||||
out.append(sch[last:]); sch=''.join(out)
|
||||
|
||||
# wires hanging off doomed pins (U4 ADD0 has a 1.27mm wire to a GND label): chain-trace
|
||||
# from the doomed pin coords, delete chained wires + labels at chain points. Guarded: abort
|
||||
# if the chain grows past 3 wires (would mean eating a shared net wire) or touches a
|
||||
# surviving symbol's pin.
|
||||
wires=[]
|
||||
for m in re.finditer(r'\n\t\(wire\s*\n\s*\(pts\s*\n((?:\s*\(xy [-\d.]+ [-\d.]+\)\s*\n?)+)(?:(?!\n\t\().)*?\n\t\)', sch, re.S):
|
||||
pts=[(round(float(a),3),round(float(b),3)) for a,b in re.findall(r'\(xy ([-\d.]+) ([-\d.]+)\)', m.group(1))]
|
||||
wires.append((pts, m.start()+1, m.end()))
|
||||
seen=set(kill_coords); chain=[]; grew=True
|
||||
while grew:
|
||||
grew=False
|
||||
for w in wires:
|
||||
if w in chain: continue
|
||||
if any(p in seen for p in w[0]):
|
||||
chain.append(w); seen|=set(w[0]); grew=True
|
||||
assert len(chain) <= 3, 'wire chain from doomed pins too large: %d' % len(chain)
|
||||
surv_pins=set()
|
||||
for b2 in re.finditer(r'\n\t\(symbol\n(?:(?!\n\t\(symbol\n).)*', sch, re.S):
|
||||
blk2=b2.group(0)
|
||||
rr=re.search(r'\(property "Reference" "([^"]+)"',blk2)
|
||||
if rr and rr.group(1) in doomed: continue
|
||||
ll=re.search(r'\(lib_id "([^"]+)"',blk2); aa=re.search(r'\(at ([-\d.]+) ([-\d.]+) (\d+)\)',blk2)
|
||||
if ll and aa:
|
||||
for num,px,py in (cachedpins(ll.group(1)) or []):
|
||||
surv_pins.add((round(float(aa.group(1))+px,3), round(float(aa.group(2))-py,3)))
|
||||
assert not (seen - kill_coords) & surv_pins, 'wire chain touches a surviving pin - refusing'
|
||||
removed_wires=0
|
||||
for pts,a,e in sorted(chain, key=lambda t:-t[1]):
|
||||
sch = sch[:a-1] + sch[e:]; removed_wires+=1
|
||||
# labels at chain points (e.g. the GND at the far end of the ADD0 stub)
|
||||
out=[]; last=0; removed_lab2=0
|
||||
for m in re.finditer(r'\n\t\((label|global_label) "[^"]+"\s*\n\s*\(at ([-\d.]+) ([-\d.]+) (\d+)\)(?:(?!\n\t\().)*?\n\t\)', sch, re.S):
|
||||
c=(round(float(m.group(2)),3),round(float(m.group(3)),3))
|
||||
if c in (seen - kill_coords):
|
||||
out.append(sch[last:m.start()]); last=m.end(); removed_lab2+=1
|
||||
out.append(sch[last:]); sch=''.join(out)
|
||||
removed_lab += removed_lab2
|
||||
|
||||
# re-find the symbol spans on the label-stripped text, delete (largest offset first)
|
||||
spans = []
|
||||
for ref in doomed:
|
||||
a,e,l,X,Y = find_inst(ref)
|
||||
spans.append((a-1, e)) # a-1 swallows the leading newline
|
||||
for a,e in sorted(spans, key=lambda t:-t[0]):
|
||||
sch = sch[:a] + sch[e:]
|
||||
|
||||
# ---------- 3. additions: P18 + R126 + C157 + PC4 label ----------
|
||||
add = ''
|
||||
newlabels=[]
|
||||
def add_lab(net,x,y,rot,just):
|
||||
global add
|
||||
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())
|
||||
newlabels.append((net,round(x,3),round(y,3)))
|
||||
|
||||
def mksym(lib_id, ref, val, x, y, fp, npins, lcsc=''):
|
||||
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 no)\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),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')
|
||||
|
||||
def place(lib, ref, val, x, y, fp, nets, lcsc=''):
|
||||
pp = cachedpins(lib)
|
||||
if not pp: sys.exit('no pins for %s' % lib)
|
||||
global add
|
||||
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(nets[num],x+px,y-py,rot,just)
|
||||
|
||||
add += ('\n\t(text "2ND EXTERNAL NTC PROBE (T10): same channel as P12/R42/C24 -> PC5. '
|
||||
'This one -> PC4 (ADC12_IN14). TMP102 (U4) and SHT40 probe jack (P5) deleted; '
|
||||
'on-board U3 SHT40 = tair+humidity."\n'
|
||||
'\t\t(exclude_from_sim no)\n\t\t(at 376 254 0)\n\t\t(effects\n\t\t\t(font\n\t\t\t\t(size 1.6 1.6)\n'
|
||||
'\t\t\t)\n\t\t\t(justify left bottom)\n\t\t)\n\t\t(uuid "%s")\n\t)') % uuid.uuid4()
|
||||
|
||||
place('Connector_Generic:Conn_01x02','P18','NTC-PROBE-2',384.0,266.0,
|
||||
'Connector_JST:JST_XH_B2B-XH-A_1x02_P2.50mm_Vertical',{'1':'NTC2','2':'GND'})
|
||||
place('Device:R','R126','10k/1%',396.0,258.0,'Resistor_SMD:R_0603_1608Metric',
|
||||
{'1':'3V3','2':'NTC2'})
|
||||
place('Device:C','C157','100nF',404.0,258.0,'Capacitor_SMD:C_0603_1608Metric',
|
||||
{'1':'NTC2','2':'GND'})
|
||||
|
||||
# PC4 = U16 pin 24 at sheet (518.16, 411.48): label + remove stale NC if present
|
||||
PC4 = (518.16, 411.48)
|
||||
add_lab('NTC2', PC4[0], PC4[1], 0, 'left bottom')
|
||||
out=[]; last=0; pc4_nc=0
|
||||
for m in re.finditer(r'\n\t\(no_connect\s*\n\s*\(at ([-\d.]+) ([-\d.]+)\)(?:(?!\n\t\().)*?\n\t\)', sch, re.S):
|
||||
c=(round(float(m.group(1)),2),round(float(m.group(2)),2))
|
||||
if c == PC4:
|
||||
out.append(sch[last:m.start()]); last=m.end(); pc4_nc+=1
|
||||
out.append(sch[last:]); sch=''.join(out)
|
||||
|
||||
# ---------- 4. collision check on the post-delete text ----------
|
||||
labs = set()
|
||||
for m in re.finditer(r'\n\t\((label|global_label) "[^"]+"\s*\n\s*\(at ([-\d.]+) ([-\d.]+)', sch):
|
||||
labs.add((round(float(m.group(2)),3), round(float(m.group(3)),3)))
|
||||
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)))
|
||||
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)))
|
||||
pin_coords=set()
|
||||
for ref,(lb_,X_,Y_) in inst.items():
|
||||
for num,px,py in (cachedpins(lb_) or []):
|
||||
pin_coords.add((round(X_+px,3), round(Y_-py,3)))
|
||||
INTENTIONAL = {(round(PC4[0],3),round(PC4[1],3))}
|
||||
existing = (labs | 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: %s' % coll[:8])
|
||||
|
||||
end = sch.rstrip(); assert end.endswith(')')
|
||||
sch = end[:-1].rstrip('\n') + add + '\n)\n'
|
||||
|
||||
lost = uuids_before - set(re.findall(r'\(uuid "([0-9a-f-]+)"\)', sch))
|
||||
# the two deleted symbols and their pin/label/nc uuids are EXPECTED losses; count them
|
||||
print('deleted: U4 (TMP102) + P5 (TEMP_PROBE); labels removed: %d, NCs: %d, stub wires: %d' % (removed_lab, removed_nc, removed_wires))
|
||||
print('pre-existing uuids gone: %d (expected: the 2 symbols + their %d labels + %d NCs + pin uuids)' % (len(lost), removed_lab, removed_nc))
|
||||
print('added: P18 NTC-PROBE-2 + R126 10k + C157 100nF; labels: %d; PC4 label placed, PC4 stale NC removed: %d' % (len(newlabels)+1, pc4_nc))
|
||||
print('paren: %d' % bal(sch))
|
||||
if not APPLY:
|
||||
print('\nDRY RUN - nothing written. Re-run with --apply.'); sys.exit(0)
|
||||
if bal(sch)!=0: sys.exit('REFUSING: paren imbalance')
|
||||
shutil.copy2(SCH,SCH+'.T10bak')
|
||||
open(SCH,'w',encoding='utf-8').write(sch)
|
||||
print('\nAPPLIED. Backup: .T10bak')
|
||||
Loading…
Reference in New Issue
Block a user