muse/book-reading-clip-lamp

A 60‑mm ESP32‑S3 lamp-head PCB combining USB‑C battery charging/power regulation, OV5640 autofocus camera interfacing, I²S microphone and differential speaker audio, six‑LED boost-driven reading light, buttons, status indicators, and battery monitoring.

Version
0.2.5
License
unset
Stars
0

verify-camera-interface.py

SchematicSchematic preview for verify-camera-interface.py
"""Audit assembled circuit JSON against the chosen camera drawing and TI pinouts."""
import json,csv,sys
from pathlib import Path
p=Path(__file__).parent
d=json.loads((p/(sys.argv[1] if len(sys.argv)>1 else 'dist/index/circuit.json')).read_text())
components={x['name']:x for x in d if x['type']=='source_component'}
ports={x['source_port_id']:x for x in d if x['type']=='source_port'}
nets={x['source_net_id']:x['name'] for x in d if x['type']=='source_net'}
portnets={pid:set() for pid in ports}
for t in (x for x in d if x['type']=='source_trace'):
 for pid in t['connected_source_port_ids']:
  portnets[pid].update(nets[nid] for nid in t.get('connected_source_net_ids',[]))
def net(ref,pin):
 matches=[x for x in ports.values() if x.get('source_component_id')==components[ref]['source_component_id'] and (x.get('pin_number')==pin or f'pin{pin}' in x.get('port_hints',[]))]
 assert len(matches)==1,(ref,pin,matches)
 return portnets[matches[0]['source_port_id']]
def expect(ref,pin,n):assert net(ref,pin)==({n} if n else set()),(ref,pin,net(ref,pin),n)
for row in csv.DictReader((p/'camera-pinout.csv').open()):
 expect('J2',int(row['FPC contact']),None if row['Board net']=='NC' else row['Board net'])
for pin in [25,26]:expect('J2',pin,'GND')
for i,(a,b) in enumerate([(2,18),(4,16),(6,14),(8,12),(11,9),(13,7),(15,5),(17,3)]):
 expect('U10',a,f'FPC_D{i}');expect('U10',b,f'CAM_D{i}')
for a,b,s in [(2,18,'PCLK'),(4,16,'VSYNC'),(6,14,'HREF')]:
 expect('U11',a,'FPC_'+s);expect('U11',b,'CAM_'+s)
for ref in ['U10','U11']:expect(ref,20,'V3V3');expect(ref,10,'GND');expect(ref,1,'CAM_PWDN')
for pin in [8,11,13,15,17]:expect('U11',pin,'GND')
for pin in [12,9,7,5,3]:expect('U11',pin,None)
expect('U10',19,'CAM_PWDN');expect('U11',19,'V3V3')
for pin,n in {1:'GND',2:'CAM_XCLK',3:'FPC_XCLK_DRV',4:'GND',5:'CAM_RESET',6:'FPC_RESET',7:'GND',8:'FPC_PWDN',9:'CAM_PWDN',10:'GND',11:None,12:'GND',13:'CAM2V8',14:'CAM2V8'}.items():expect('U12',pin,n)
for pin,n in {1:'CAM_SCL',2:'GND',3:'CAM2V8',4:'FPC_SCL',5:'FPC_SDA',6:'FPC_RESET',7:'V3V3',8:'CAM_SDA'}.items():expect('U13',pin,n)
for ref,out,en in [('U8','CAM2V8','V3V3'),('U9','CAM1V5','CAM2V8')]:
 for pin,n in {1:'V3V3',2:'GND',3:en,4:None,5:out}.items():expect(ref,pin,n)
for row in csv.DictReader((p/'pin-map.csv').open()):
 expect('U1',int(row['module_pad']),None if row['board_net']=='NC' else row['board_net'])
expected={'J2':'C202112','U8':'C2868951','U9':'C2867152','U10':'C7668','U11':'C7668','U12':'C7813','U13':'C324082'}
for ref,sku in expected.items():
 assert sku in components[ref]['supplier_part_numbers']['jlcpcb'],ref
 pc=next(x for x in d if x['type']=='pcb_component' and x['source_component_id']==components[ref]['source_component_id'])
 assert pc['layer']=='bottom',ref
 assert any(x['type']=='cad_component' and x['pcb_component_id']==pc['pcb_component_id'] for x in d),ref
assert len([x for x in d if x['type']=='schematic_sheet'])==9
bom=list(csv.DictReader((p/'bill-of-materials.csv').open(encoding='utf-8-sig')))
for r in [f'R{i}' for i in range(1,34)]+[f'C{i}' for i in range(1,32)]:
 assert sum(r in x['Reference designators'].split('; ') for x in bom)==1,r
print('PASS: all24 FPC contacts +2 hold-downs; DVP/control/rail pinouts; preserved41 ESP32 pads; JLC source and CAD model records;9 sheets; all64 passives in BOM')
print('Software connectivity audit only; not a power-sequencing, timing or physical camera test.')