muse/book-reading-clip-lamp

A Wi‑Fi/USB‑C ESP32‑S3 reading lamp with OV5640 camera, microphone, speaker amplifier, rechargeable Li‑Po power management, six-LED boost light, USB protection/charging detection, and a clip/gooseneck enclosure with external FlexPIFA antenna.

Version
0.4.3
License
unset
Stars
0

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,n in {4:'CAM2V8',10:'CAM_CORE_INTERNAL',11:'CAM1V8',23:'GND',24:'CAM2V8',25:'GND',26:'GND'}.items():expect('J2',pin,n)
# SN74AXC8T245 PW24: A=3.3 V MCU, B=1.8 V camera.
# TI table7-1: DIR1=DIR2=LOW gives B->A on both banks.
for i in range(8):
 expect('U10',3+i,f'CAM_D{i}');expect('U10',21-i,f'FPC_D{i}')
for a,b,s in [(3,21,'PCLK'),(4,20,'VSYNC'),(5,19,'HREF')]:
 expect('U11',a,'CAM_'+s);expect('U11',b,'FPC_'+s)
for ref in ['U10','U11']:
 for pin,n in {1:'V3V3',2:'GND',11:'GND',12:'GND',13:'GND',22:'CAM_PWDN',23:'CAM1V8',24:'CAM1V8'}.items():expect(ref,pin,n)
for pin in [14,15,16,17,18]:expect('U11',pin,'GND')
for pin in [6,7,8,9,10]:expect('U11',pin,None)
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:'CAM1V8',14:'CAM1V8'}.items():expect('U12',pin,n)
for pin,n in {1:'CAM_SCL',2:'GND',3:'CAM1V8',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','CAM_AVDD_EN'),('U9','CAM1V8','V3V3')]:
 for pin,n in {1:'V3V3',2:'GND',3:en,4:None,5:out}.items():expect(ref,pin,n)
for pin,n in {1:'CAM1V8',2:'CAM_AVDD_EN'}.items():expect('R34',pin,n)
expect('C32',1,'CAM_AVDD_EN');expect('C32',2,'GND')
for ref in ['C24','C25']:expect(ref,1,'CAM_CORE_INTERNAL');expect(ref,2,'GND')
core_members={ref for ref,c in components.items() if any('CAM_CORE_INTERNAL' in portnets[pid] for pid,port in ports.items() if port.get('source_component_id')==c['source_component_id'])}
assert core_members=={'J2','C24','C25'},core_members
assert 'CAM1V5' not in nets.values(),'External core supply must not remain'
for ref in ['C27','C28']:expect(ref,1,'V3V3');expect(ref,2,'GND')
for ref in ['C29','C30','C33','C34','C35']:expect(ref,1,'CAM1V8');expect(ref,2,'GND')
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':'C2877863','U10':'C882718','U11':'C882718','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'])==10
bom=list(csv.DictReader((p/'bill-of-materials.csv').open(encoding='utf-8-sig')))
for r in [name for name in components if name.startswith(('R','C')) and name[1:].isdigit()]:
 assert sum(r in x['Reference designators'].split('; ') for x in bom)==1,r
print('PASS: all24 FPC contacts +2 hold-downs; Seeed1.8 V pinout; AF ground; AXC B-to-A direction/OE; isolated internal core; delayed AVDD; preserved41 ESP32 pads; JLC/CAD;10 sheets; all populated passives in BOM')
print('Software connectivity audit only; not a power-sequencing, timing or physical camera test.')