imrishabh18/rp2040-motor-controller
A library of PCB component definitions covering MOSFETs, motor drivers, USB-PD control, sensors, current monitors, transistors, diodes, resistors, speakers, connectors, and mirrored bottom-side logos, with pin mappings, footprints, and 3D models.
- Version
- 1.0.55
- License
- unset
- Stars
- 5
scripts/package-pg-revision.py
"""Package only a fully verified PG revision and its exact evidence."""
import hashlib, json, re, shutil, zipfile
from pathlib import Path
root=Path.cwd();out=root.parents[1]/'outputs';report=root/'reports/pg-sense-2026-10-22'
raw=(root/'dist/index/circuit.json').read_bytes();j=json.loads(raw);sha=hashlib.sha256(raw).hexdigest()
tests=(report/'full-tests.log').read_text()
assert re.search(r'\b34 pass\b',tests) and re.search(r'\b0 fail\b',tests),'Full regression suite must pass'
assert 'Issue count: 0' in (report/'style-analysis.log').read_text()
assert 'No shorts detected' in (report/'final-checks.log').read_text()
assert 'Errors: 0' in (report/'final-checks.log').read_text()
assert 'Exit code: 0' in (report/'schematic-placement.log').read_text()
assert 'Errors: 0' in (report/'placement.log').read_text()
assert not re.search(r'error TS\d+', (report/'typecheck.log').read_text())
assert not any(e['type'].endswith('_error') for e in j)
audits={}
for name in ['validation.json','single-sided-assembly.json','manufacturing-audit.json','fabrication-audit.json','ground-copper-connectivity.json','exported-clearances.json']:
p=root/'reports'/name
if not p.exists() and name=='exported-clearances.json':continue
data=json.loads(p.read_text());assert data.get('circuitJsonSha256',data.get('sha256'))==sha,name
audits[name]=data
assembly=audits['single-sided-assembly.json'];assert assembly['populatedTopComponents']==107 and assembly['populatedBottomComponents']==0
mfg=audits['manufacturing-audit.json']
for key in ['shortContacts','splitNets','holeSpacingViolations','holeToCopperViolations','polygonPadClearanceViolations','copperToBoardEdgeViolations','pthDrillToCopperViolations','npthToCopperViolations','unmappedElements']:assert not mfg[key],key
assert audits['ground-copper-connectivity.json']['groundCopperConnectedComponents']==1
old=json.loads((report/'baseline/circuit.json').read_text())
keys=set(json.loads((root/'tests/fixtures/pre-pg-preserved-copper.json').read_text())['geometryKeys'])
def copper(circuit):return {e['pcb_trace_id']:[{k:v for k,v in p.items() if k in keys} for p in e['route']] for e in circuit if e['type']=='pcb_trace'}
a,b=copper(old),copper(j)
assert set(a)<=set(b)
changed=[id for id in a if a[id]!=b[id]];assert changed==['top_assembly_215'],changed
assert len(set(b)-set(a))==12 and all(id.startswith('pg_') for id in set(b)-set(a))
def legacy_partition(circuit,names):
components={e['source_component_id']:e['name'] for e in circuit if e['type']=='source_component'}
parent={}
def find(x):
parent.setdefault(x,x)
if parent[x]!=x:parent[x]=find(parent[x])
return parent[x]
for e in circuit:
ids=e.get('connected_source_port_ids',[])+e.get('connected_source_net_ids',[]) if e['type']=='source_trace' else e.get('source_port_ids',[]) if e['type']=='source_component_internal_connection' else []
for x in ids[1:]:parent[find(x)]=find(ids[0])
groups={}
for e in circuit:
if e['type']!='source_port' or components.get(e['source_component_id']) not in names:continue
groups.setdefault(find(e['source_port_id']),[]).append((components[e['source_component_id']],e.get('pin_number'),e['name']))
return sorted(tuple(sorted(g)) for g in groups.values())
oldnames={e['name'] for e in old if e['type']=='source_component'}
assert legacy_partition(old,oldnames)==legacy_partition(j,oldnames),'Existing physical pin connectivity changed'
summary={'version':'1.0.47','published':False,'circuitJsonSha256':sha,'gpio':7,'activeHigh':True,'buffer':'SN74LVC1G97DRLR','newTopComponents':4,'populatedTopComponents':107,'populatedBottomComponents':0,'bottomEncoderDoNotPlace':True,'existingHardwareInterlockPreserved':True,'existingPinConnectivityPreserved':True,'legacyTraceCount':len(a),'unchangedLegacyTraces':len(a)-len(changed),'necessaryReroutes':changed,'newTraces':12,'testsPassed':34,'testsFailed':0,'schematicStyleIssues':0,'nativeDrcErrors':0,'allFourLayerGerberShorts':0,'physicalShorts':0,'disconnectedNets':0,'connectedGroundCopperGroups':1,'existingPcbAngleAdvisories':3,'poweredPrototypeTested':False}
receipt_path=report/'published-release.json'
if receipt_path.exists():
receipt=json.loads(receipt_path.read_text())
assert receipt['version']=='1.0.47' and receipt['jsonValuesMatch'] and receipt['localCircuitJsonSha256']==sha
summary['published']=True
summary['publication']={'url':receipt['url'],'version':receipt['version'],'releaseId':receipt['release']['package_release_id'],'verifiedAt':receipt['verifiedAt']}
(report/'summary.json').write_text(json.dumps(summary,indent=2)+'\n')
notes='''RP2040 motor controller v1.0.47 — local PG readback revision (not published)
GPIO7 high: CH224K supply present and PG asserted low. GPIO7 low: not good.
Use GPIO7 as an input with internal pulls disabled. INA226 still measures actual
PD voltage. Keep GPIO22 low during negotiation and validate the selected voltage
before requesting motor enable. No application firmware is included.
Four new top-side parts: U_PD_PG (SN74LVC1G97DRLR / C2682094), C_PD_PG (100 nF),
R_PD_VDD_PD (47 kohm), and R_PD_GOOD_PD (10 kohm). The existing PG/BSS84 motor
sleep interlock is retained. All 107 populated parts are on top. Only the optional
encoder is on the bottom with doNotPlace; its two top capacitors are also DNP.
Software verification: 34 tests pass, zero schematic style issues, zero native
DRC errors, no shorts on all four Gerber copper layers, no split nets, one
connected ground network. 287 of 288 prior routes are geometrically unchanged;
one local 3.3-V escape was rerouted for GPIO7, and 12 PG branches were added.
The three existing PCB angle advisories remain. No powered prototype test or
fresh supplier inventory audit was performed. Review placement orientation in
the assembler preview before ordering; exact imported footprints are retained.
'''
if summary['published']:
notes=notes.replace('local PG readback revision (not published)','published PG readback revision\nhttps://tscircuit.com/imrishabh18/rp2040-motor-controller')
target=out/'rp2040-motor-controller-pg-source';target.mkdir(exist_ok=True)
for name in ['README.md','index.circuit.tsx','package.json','bun.lock','bunfig.toml','tsconfig.json','tscircuit.config.json']:
shutil.copy2(root/name,target/name)
for name in ['assets','engineering','imports','mechanical','patches','routing','schematic','scripts','tests','vendor']:
shutil.copytree(root/name,target/name,dirs_exist_ok=True,ignore=shutil.ignore_patterns('__pycache__','*.diff.png'))
for name in ['index','schematic']:
shutil.copytree(root/'dist'/name,target/'dist'/name,dirs_exist_ok=True)
for name in audits:
(target/'reports').mkdir(exist_ok=True);shutil.copy2(root/'reports'/name,target/'reports'/name)
shutil.copytree(report,target/'reports/pg-sense-2026-10-22',dirs_exist_ok=True)
(target/'PG-REVISION-NOTES.txt').write_text(notes)
(out/'pg-power-good'/'validation-summary.json').write_text(json.dumps(summary,indent=2)+'\n')
(out/'pg-power-good'/'PG-REVISION-NOTES.txt').write_text(notes)
for name in ['pcb-top.png','pcb-top.svg']:shutil.copy2(root/'dist'/name,out/'pg-power-good'/name)
source=out/'rp2040-motor-controller-pg-source.zip'
with zipfile.ZipFile(source,'w',zipfile.ZIP_DEFLATED,compresslevel=9) as z:
for p in sorted(target.rglob('*')):
if p.is_file():z.write(p,p.relative_to(out))
with zipfile.ZipFile(out/'pg-power-good-schematics.zip','w',zipfile.ZIP_DEFLATED,compresslevel=9) as z:
for p in sorted((out/'pg-power-good').rglob('*')):
if p.is_file():z.write(p,p.relative_to(out/'pg-power-good'))
print(json.dumps({'sourceZip':str(source),'sourceBytes':source.stat().st_size,'summary':summary},indent=2))
