AnasSarkiz/magnetic-shutter-remote-r8
A USB‑C rechargeable battery-powered ESP32‑C3 wireless controller with Li‑ion charging, protected 3.3‑V buck‑boost regulation, thermal/safety monitoring, status LEDs, buttons, programming UART, crystal clock, and 2.4‑GHz antenna matching network.
- Version
- 0.4.0-prototype
- License
- unset
- Stars
- 0
scripts/r9_board_geometry.py
"""Independent R9 antenna exclusion. Board XY mm, +Z top.
Only the actual antenna terminal lands and its top RF feed are permitted;
the native allowTraces option is not an electrical clearance exemption.
"""
from shapely.geometry import box
def antenna_exclusion(circuit):
"""Return the actual antenna component and validated exclusion polygon, XY mm."""
antenna = next(e for e in circuit if e['type'] == 'source_component' and e['name'] == 'ANT1')
if antenna['supplier_part_numbers']['jlcpcb'] != ['C22423581']:
raise ValueError('Antenna review requires the selected exact supplier part')
pcb = next(e for e in circuit if e['type'] == 'pcb_component' and e['source_component_id'] == antenna['source_component_id'])
bands = [e for e in circuit if e['type'] == 'pcb_keepout' and e.get('excluded_pcb_component_ids') == [pcb['pcb_component_id']]]
if len(bands) != 1:
raise ValueError('Require one actual antenna exclusion')
band = bands[0]
if band['shape'] != 'rect' or set(band['layers']) != {'top', 'bottom'}:
raise ValueError('Antenna exclusion must cover both copper layers')
x, y = band['center']['x'], band['center']['y']
exclusion = box(x-band['width']/2, y-band['height']/2, x+band['width']/2, y+band['height']/2)
if any(abs(a-b) > 1e-6 for a,b in zip(exclusion.bounds, (12.25,21,18.75,28))):
raise ValueError('Unexpected R9 antenna exclusion geometry')
return pcb, exclusion
def rf_intrusions(params):
circuit = params['circuit']
pcb, exclusion = antenna_exclusion(circuit)
rf = next(e for e in circuit if e['type']=='source_net' and e['name']=='RF_50')['subcircuit_connectivity_map_key']
allowed_pads = {e['pcb_smtpad_id'] for e in circuit if e['type']=='pcb_smtpad' and e['pcb_component_id']==pcb['pcb_component_id']}
if len(allowed_pads) != 2:
raise ValueError('Require both actual antenna terminal lands')
failures = []
for copper in params['copper']:
if copper.geometry.intersection(exclusion).area <= params['epsilon_mm']**2:
continue
if copper.id in allowed_pads and copper.kind=='smt' and copper.layer=='top':
continue
if copper.kind=='track' and copper.layer=='top' and copper.net==rf:
continue
failures.append(copper.id+':'+copper.layer)
return failures