astra/iphone-encoder-counter

A CR2450-powered MSP430 FRAM counter combines an always-on dual-channel TMR rotation sensor, pushbutton input, and ST25DV NFC tag with an integrated tuned printed antenna and I²C/GPIO interface.

Version
1.0.3
License
unset
Stars
0

scripts/check_exports.py

"""Check native Gerber/CPL geometry and catalog-matched assembly BOM."""
from pathlib import Path
import json,zipfile,re,csv,io,collections,math
root=Path(__file__).resolve().parents[1]
d=json.loads((root/'dist/index/circuit.json').read_text())
catalog=json.loads((root/'jlc-catalog-check.json').read_text())
z=zipfile.ZipFile(root/'fabrication/iphone-encoder-counter-jlc.zip')
def segment(a,b,w):
 pts=sorted([(round(a[0],6),round(a[1],6)),(round(b[0],6),round(b[1],6))])
 return tuple(pts)+ (round(w,6),)
expected={'top':set(),'bottom':set()}
for e in d:
 if e['type']!='pcb_trace':continue
 for a,b in zip(e['route'],e['route'][1:]):
  if a['route_type']==b['route_type']=='wire' and a['layer']==b['layer'] and (a['x'],a['y'])!=(b['x'],b['y']):expected[a['layer']].add(segment((a['x'],a['y']),(b['x'],b['y']),a['width']))
actual={}
for layer,f in [('top','F_Cu.gbr'),('bottom','B_Cu.gbr')]:
 s=z.read(f).decode();aps={m[0]:(m[1],m[2]) for m in re.findall(r'%ADD(\d+)([A-Z]+),([^*]+)\*%',s)};x=y=0;ap=None;paths=set()
 for l in s.splitlines():
  m=re.fullmatch(r'D(\d+)\*',l)
  if m:ap=m[1];continue
  if not l.startswith(('X','Y')):continue
  xm=re.search(r'X(-?\d+)',l);ym=re.search(r'Y(-?\d+)',l);dm=re.search(r'D0([123])\*',l);assert dm,l
  nx=int(xm[1])/1e6 if xm else x;ny=int(ym[1])/1e6 if ym else y
  if dm[1]=='1' and (x,y)!=(nx,ny):
   assert aps[ap][0]=='C';paths.add(segment((x,y),(nx,ny),float(aps[ap][1])))
  x,y=nx,ny
 actual[layer]=paths
 assert paths==expected[layer], (layer,'missing',list(expected[layer]-paths)[:4],'extra',list(paths-expected[layer])[:4])
# Drill positions in the same signed-mm coordinate system.
s=z.read('drill-L1-L2.drl').decode()
hits=collections.Counter((round(float(a),4),round(float(b),4)) for a,b in re.findall(r'^X(-?[0-9.]+)Y(-?[0-9.]+)$',s,re.M))
want=collections.Counter((round(e['x'],4),round(e['y'],4)) for e in d if e['type'] in ['pcb_via','pcb_plated_hole'])
assert hits==want,(hits-want,want-hits)
bom=list(csv.DictReader(io.StringIO(z.read('bom.csv').decode())))
cpl=list(csv.DictReader(io.StringIO(z.read('pick_and_place.csv').decode())))
assert {x['Designator'] for x in bom}=={x['Designator'] for x in cpl}
assert len(bom)==23 and not ({'L1','J2'} & {x['Designator'] for x in bom})
source={e['name']:e for e in d if e['type']=='source_component'}
code=(root/'index.circuit.tsx').read_text()
for b in bom:
 c=b['JLCPCB Part #']
 assert c and c in source[b['Designator']]['supplier_part_numbers']['jlcpcb']
 if c=='C17208043':
  listing=json.loads((root/'validation/u2-jlc-listing.json').read_text())
  assert listing['jlcpcb_part_number']==c and listing['manufacturer_part_number']==source[b['Designator']]['manufacturer_part_number']
 else:
  r=catalog['parts'][c]['results'][0]
  assert r['stock']>0 and r['mfr']==source[b['Designator']]['manufacturer_part_number']
for p in cpl:
 line=next(l for l in code.splitlines() if 'name="'+p['Designator']+'"' in l)
 x=float(re.search(r'pcbX=\{(-?[\d.]+)\}',line)[1]);y=float(re.search(r'pcbY=\{(-?[\d.]+)\}',line)[1])
 assert abs(float(p['Mid X'])-x)<.001 and abs(float(p['Mid Y'])-y)<.001
 rotation=re.search(r'pcbRotation=\{(-?[\d.]+)\}',line)
 assert p['Layer']=='top' and float(p['Rotation'])==(float(rotation[1]) if rotation else 0)
assert not any('error' in e['type'] for e in d)
report={'result':'PASS','copper_segments_by_layer':{k:len(v) for k,v in actual.items()},'copper_matches_circuit_json':True,'drill_hits':sum(hits.values()),'jlc_assembled_parts_per_board':len(bom),'bom_cpl_reference_sets_match':True,'all_cpl_centers_match_source':True,'assembly_side':'top','all_assembly_parts_have_jlcpcb_ids':True,'sourcing_blockers':['U2 C17208043: zero JLC stock; confirm sourcing and assembly acceptance','U3 C5267456: live stock 3, insufficient for assumed 5-board batch'],'catalog_checked_at_utc':catalog['checked_at_utc']}
(root/'validation/export-audit.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report,indent=2))
,s,re.M))\nwant=collections.Counter((round(e['x'],4),round(e['y'],4)) for e in d if e['type'] in ['pcb_via','pcb_plated_hole'])\nassert hits==want,(hits-want,want-hits)\nbom=list(csv.DictReader(io.StringIO(z.read('bom.csv').decode())))\ncpl=list(csv.DictReader(io.StringIO(z.read('pick_and_place.csv').decode())))\nassert {x['Designator'] for x in bom}=={x['Designator'] for x in cpl}\nassert len(bom)==23 and not ({'L1','J2'} \u0026 {x['Designator'] for x in bom})\nsource={e['name']:e for e in d if e['type']=='source_component'}\ncode=(root/'index.circuit.tsx').read_text()\nfor b in bom:\n c=b['JLCPCB Part #']\n assert c and c in source[b['Designator']]['supplier_part_numbers']['jlcpcb']\n if c=='C17208043':\n listing=json.loads((root/'validation/u2-jlc-listing.json').read_text())\n assert listing['jlcpcb_part_number']==c and listing['manufacturer_part_number']==source[b['Designator']]['manufacturer_part_number']\n else:\n r=catalog['parts'][c]['results'][0]\n assert r['stock']\u003e0 and r['mfr']==source[b['Designator']]['manufacturer_part_number']\nfor p in cpl:\n line=next(l for l in code.splitlines() if 'name=\"'+p['Designator']+'\"' in l)\n x=float(re.search(r'pcbX=\\{(-?[\\d.]+)\\}',line)[1]);y=float(re.search(r'pcbY=\\{(-?[\\d.]+)\\}',line)[1])\n assert abs(float(p['Mid X'])-x)\u003c.001 and abs(float(p['Mid Y'])-y)\u003c.001\n rotation=re.search(r'pcbRotation=\\{(-?[\\d.]+)\\}',line)\n assert p['Layer']=='top' and float(p['Rotation'])==(float(rotation[1]) if rotation else 0)\nassert not any('error' in e['type'] for e in d)\nreport={'result':'PASS','copper_segments_by_layer':{k:len(v) for k,v in actual.items()},'copper_matches_circuit_json':True,'drill_hits':sum(hits.values()),'jlc_assembled_parts_per_board':len(bom),'bom_cpl_reference_sets_match':True,'all_cpl_centers_match_source':True,'assembly_side':'top','all_assembly_parts_have_jlcpcb_ids':True,'sourcing_blockers':['U2 C17208043: zero JLC stock; confirm sourcing and assembly acceptance','U3 C5267456: live stock 3, insufficient for assumed 5-board batch'],'catalog_checked_at_utc':catalog['checked_at_utc']}\n(root/'validation/export-audit.json').write_text(json.dumps(report,indent=2)+'\\n');print(json.dumps(report,indent=2))\n","content_mimetype":"application/octet-stream","created_at":"2026-09-28T22:46:24.910Z"}; window.SSR_PACKAGE_RELEASES = [{"package_release_id":"467c3f9b-77b0-4a2c-a0ea-fc47490ed553","package_id":"c9b86543-94f1-4be3-8a2d-dd17f99fe787","version":"1.0.3","github_commit_sha":null,"is_locked":false,"is_latest":true,"ready_to_build":true,"is_pr_preview":false,"created_at":"2026-09-28T22:46:24.618Z","circuit_json_build_error":null,"circuit_json_build_error_last_updated_at":null,"has_transpiled":false,"transpilation_error":null,"transpilation_logs":null,"transpilation_in_progress":false,"transpilation_started_at":null,"transpilation_completed_at":null,"transpilation_is_stale":true,"transpilation_display_status":"pending","fs_sha":"md5-2dc921973ac2528afa86dcd89a0f6799","circuit_json_build_logs":null,"circuit_json_build_in_progress":false,"circuit_json_build_started_at":null,"circuit_json_build_completed_at":null,"circuit_json_build_is_stale":true,"circuit_json_build_display_status":"pending","image_generation_error":null,"image_generation_error_last_updated_at":null,"image_generation_logs":null,"image_generation_in_progress":false,"image_generation_started_at":null,"image_generation_completed_at":null,"image_generation_is_stale":true,"image_generation_display_status":"pending","pcb_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=467c3f9b-77b0-4a2c-a0ea-fc47490ed553\u0026file_path=dist%2Findex%2Fpcb.svg","sch_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=467c3f9b-77b0-4a2c-a0ea-fc47490ed553\u0026file_path=dist%2Findex%2Fschematic.svg","cad_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=467c3f9b-77b0-4a2c-a0ea-fc47490ed553\u0026file_path=dist%2Findex%2F3d.png","package_release_website_url":null,"display_status":"pending","latest_package_build_id":"9f18d478-9224-400d-bd17-d600d531fb73","user_code_job_started_at":"2026-09-28T22:46:30.744Z","user_code_job_completed_at":"2026-09-28T22:48:23.600Z","user_code_job_error":null,"user_code_job_completed_logs":null,"user_code_job_log_stream_url":"https://usercode.tscircuit.com/user_code_jobs/view_log_stream?user_code_job_id=c8452cbe10289bf9b5fdd64cd92fef2fa92a5b0c01f8b959703c1b6767b126f4","ai_review_requested":false,"github_pr_number":null,"github_pr_title":null,"github_branch_name":null},{"package_release_id":"f6208e5f-4561-45bf-8530-6728afc824e2","package_id":"c9b86543-94f1-4be3-8a2d-dd17f99fe787","version":"1.0.2","github_commit_sha":null,"is_locked":false,"is_latest":false,"ready_to_build":true,"is_pr_preview":false,"created_at":"2026-09-28T22:19:55.435Z","circuit_json_build_error":null,"circuit_json_build_error_last_updated_at":null,"has_transpiled":false,"transpilation_error":null,"transpilation_logs":null,"transpilation_in_progress":false,"transpilation_started_at":null,"transpilation_completed_at":null,"transpilation_is_stale":true,"transpilation_display_status":"pending","fs_sha":"md5-268affbdfb4070349e4e1c3de3f3b8be","circuit_json_build_logs":null,"circuit_json_build_in_progress":false,"circuit_json_build_started_at":null,"circuit_json_build_completed_at":null,"circuit_json_build_is_stale":true,"circuit_json_build_display_status":"pending","image_generation_error":null,"image_generation_error_last_updated_at":null,"image_generation_logs":null,"image_generation_in_progress":false,"image_generation_started_at":null,"image_generation_completed_at":null,"image_generation_is_stale":true,"image_generation_display_status":"pending","pcb_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=f6208e5f-4561-45bf-8530-6728afc824e2\u0026file_path=dist%2Findex%2Fpcb.svg","sch_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=f6208e5f-4561-45bf-8530-6728afc824e2\u0026file_path=dist%2Findex%2Fschematic.svg","cad_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=f6208e5f-4561-45bf-8530-6728afc824e2\u0026file_path=dist%2Findex%2F3d.png","package_release_website_url":null,"display_status":"pending","latest_package_build_id":"48dfe251-495b-47a6-b249-69cdf228cd00","user_code_job_started_at":"2026-09-28T22:20:00.826Z","user_code_job_completed_at":"2026-09-28T22:22:11.382Z","user_code_job_error":null,"user_code_job_completed_logs":null,"user_code_job_log_stream_url":"https://usercode.tscircuit.com/user_code_jobs/view_log_stream?user_code_job_id=449017ff065f328ecb09514c79382b4ac63c1acb9c9def0784b3856ffc95d588","ai_review_requested":false,"github_pr_number":null,"github_pr_title":null,"github_branch_name":null},{"package_release_id":"f0b20551-e13b-431b-90fe-ec9af178ca29","package_id":"c9b86543-94f1-4be3-8a2d-dd17f99fe787","version":"1.0.1","github_commit_sha":null,"is_locked":false,"is_latest":false,"ready_to_build":true,"is_pr_preview":false,"created_at":"2026-09-28T22:08:35.266Z","circuit_json_build_error":null,"circuit_json_build_error_last_updated_at":null,"has_transpiled":false,"transpilation_error":null,"transpilation_logs":null,"transpilation_in_progress":false,"transpilation_started_at":null,"transpilation_completed_at":null,"transpilation_is_stale":true,"transpilation_display_status":"pending","fs_sha":"md5-7338bc36859f77930bd56b4d6976cd47","circuit_json_build_logs":null,"circuit_json_build_in_progress":false,"circuit_json_build_started_at":null,"circuit_json_build_completed_at":null,"circuit_json_build_is_stale":true,"circuit_json_build_display_status":"pending","image_generation_error":null,"image_generation_error_last_updated_at":null,"image_generation_logs":null,"image_generation_in_progress":false,"image_generation_started_at":null,"image_generation_completed_at":null,"image_generation_is_stale":true,"image_generation_display_status":"pending","pcb_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=f0b20551-e13b-431b-90fe-ec9af178ca29\u0026file_path=dist%2Findex%2Fpcb.svg","sch_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=f0b20551-e13b-431b-90fe-ec9af178ca29\u0026file_path=dist%2Findex%2Fschematic.svg","cad_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=f0b20551-e13b-431b-90fe-ec9af178ca29\u0026file_path=dist%2Findex%2F3d.png","package_release_website_url":null,"display_status":"pending","latest_package_build_id":"ac58e89d-d0cd-4e48-a31f-919821af4ab5","user_code_job_started_at":"2026-09-28T22:08:41.075Z","user_code_job_completed_at":"2026-09-28T22:10:49.234Z","user_code_job_error":null,"user_code_job_completed_logs":null,"user_code_job_log_stream_url":"https://usercode.tscircuit.com/user_code_jobs/view_log_stream?user_code_job_id=2f4547b3cfe600bcbd77342df4206361e4f8a8fd0e7c03780fcb934062500677","ai_review_requested":false,"github_pr_number":null,"github_pr_title":null,"github_branch_name":null},{"package_release_id":"28e85181-0dd0-4b96-ae0b-976a94891a3d","package_id":"c9b86543-94f1-4be3-8a2d-dd17f99fe787","version":"1.0.0","github_commit_sha":null,"is_locked":false,"is_latest":false,"ready_to_build":true,"is_pr_preview":false,"created_at":"2026-09-28T21:54:31.425Z","circuit_json_build_error":null,"circuit_json_build_error_last_updated_at":null,"has_transpiled":false,"transpilation_error":null,"transpilation_logs":null,"transpilation_in_progress":false,"transpilation_started_at":null,"transpilation_completed_at":null,"transpilation_is_stale":true,"transpilation_display_status":"pending","fs_sha":"md5-7ededd2ca7bd088fe57c64290e2da4d1","circuit_json_build_logs":null,"circuit_json_build_in_progress":false,"circuit_json_build_started_at":null,"circuit_json_build_completed_at":null,"circuit_json_build_is_stale":true,"circuit_json_build_display_status":"pending","image_generation_error":null,"image_generation_error_last_updated_at":null,"image_generation_logs":null,"image_generation_in_progress":false,"image_generation_started_at":null,"image_generation_completed_at":null,"image_generation_is_stale":true,"image_generation_display_status":"pending","pcb_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=28e85181-0dd0-4b96-ae0b-976a94891a3d\u0026file_path=dist%2Findex%2Fpcb.svg","sch_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=28e85181-0dd0-4b96-ae0b-976a94891a3d\u0026file_path=dist%2Findex%2Fschematic.svg","cad_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=28e85181-0dd0-4b96-ae0b-976a94891a3d\u0026file_path=dist%2Findex%2F3d.png","package_release_website_url":null,"display_status":"pending","latest_package_build_id":"4f56f752-8ff6-4340-9e00-d078b0f11df1","user_code_job_started_at":"2026-09-28T21:54:36.120Z","user_code_job_completed_at":"2026-09-28T21:56:47.147Z","user_code_job_error":null,"user_code_job_completed_logs":null,"user_code_job_log_stream_url":"https://usercode.tscircuit.com/user_code_jobs/view_log_stream?user_code_job_id=8286f3df0e9b9fc4fa37bf888636ded5e143e4da472ff20c726bf80962509ede","ai_review_requested":false,"github_pr_number":null,"github_pr_title":null,"github_branch_name":null}]; window.SSR_PACKAGE_BUILDS = []; window.SSR_PACKAGE_ROUTE = {"author":"astra","packageName":"iphone-encoder-counter","packageNameWithScope":"astra/iphone-encoder-counter","version":null,"isLegacyRoute":false,"kind":"file","filePath":"scripts/check_exports.py"};