AnasSarkiz/usb-c-pd-brushed-motor

USB-C PD-powered motor controller with protected input, selectable 5/9/12 V buck supply, MCU qualification, PWM H-bridge drive, overvoltage sensing, regenerative energy-dump resistors, and safety interlocks.

Version
0.0.1-a48-cloud-pipeline9-unrouted-c41d79a-305b1e7a
License
unset
Stars
0

scripts/review-usb-c-replacement.py

"""Review C5184243 pin/land correspondence and the authorized X-only correction.

The archived official import is immutable evidence. GCT revisionB3 specifies
48VDC/5A, the following recommended land dimensions and +/-0.05mm tolerance.
"""
import argparse
import hashlib
import json
from decimal import Decimal
from pathlib import Path
from importlib.util import module_from_spec, spec_from_file_location
geometry_spec = spec_from_file_location("pad_review", Path(__file__).with_name("review-usb-c-pad-shifts.py"))
if geometry_spec is None or geometry_spec.loader is None:
    raise RuntimeError("Historical pad geometry reader unavailable")
geometry_module = module_from_spec(geometry_spec)
geometry_spec.loader.exec_module(geometry_module)
read_footprint = geometry_module.read_footprint
from ground_copper_geometry import pad_geometry

# Import the existing geometry reader without changing the historical A44 audit.

def review(source):
    original = Path('evidence/usb-c-C5184243-original-import-A45.tsx.txt').read_bytes()
    original_sha = hashlib.sha256(original).hexdigest()
    if original_sha != 'bfc396fc889c4dd3a1e8d5977c482db3060ed772cbd913a62b0fcb4959d3fc29':
        raise ValueError('Archived official import has changed')
    pads, protected = read_footprint(source.read_text())
    original_pads, original_protected = read_footprint(original.decode())
    if protected != original_protected or set(pads) != set(range(5, 17)):
        raise ValueError('Protected supplier definition or pin inventory changed')
    nominal_x = {5:3.2,6:2.4,7:-3.2,8:-2.4,9:1.75,10:1.25,11:.75,12:.25,13:-.25,14:-.75,15:-1.25,16:-1.75}
    shifts = []
    for pin, pad in pads.items():
        old = original_pads[pin]
        if old['protected'] != pad['protected']:
            raise ValueError('Supplier geometry changed outside SMT X positions')
        shift = pad['xs'][0] - old['xs'][0]
        if abs(shift) > Decimal('.001') or abs(float(pad['xs'][0])-nominal_x[pin]) > .05:
            raise ValueError('Movement exceeds authorized precision correction or drawing tolerance')
        bounds = pad['shape'].bounds
        if abs(bounds[2]-bounds[0]-(.6 if pin<=8 else .3)) > .05 or abs(bounds[3]-bounds[1]-1.15) > .05:
            raise ValueError('SMT land differs from the manufacturer drawing')
        shifts.append({'pin':pin,'shift_mm':float(shift)})
    ordered = sorted(pads,key=lambda pin:pads[pin]['shape'].bounds[0])
    gaps = [{'first_pin':first,'second_pin':second,'clearance_mm':pads[first]['shape'].distance(pads[second]['shape'])} for first,second in zip(ordered,ordered[1:])]
    if min(g['clearance_mm'] for g in gaps)<.2:
        raise ValueError('An actual adjacent-pad gap remains below0.20mm')
    probe_path = Path('dist/tests/supplier-audit/C5184243/circuit.json')
    probe = json.loads(probe_path.read_text())
    ports = [e for e in probe if e['type']=='source_port']
    if len(ports)!=16 or sorted(int(e['pin_number']) for e in ports)!=list(range(1,17)):
        raise ValueError('Missing or extra electrical pins')
    expected_hints={1:'EH4',2:'EH2',3:'EH1',4:'EH3',5:'GND1',6:'VBUS1',7:'GND2',8:'VBUS2',9:'CC2',10:'SBU1',11:'Dp2',12:'Dn1',13:'Dp1',14:'Dn2',15:'CC1',16:'SBU2'}
    for port in ports:
        pin=int(port['pin_number'])
        if expected_hints[pin] not in port['port_hints']:
            raise ValueError('Importer pin labeling disagrees with drawing review')
        pcb_port=next(e for e in probe if e['type']=='pcb_port' and e['source_port_id']==port['source_port_id'])
        lands=[e for e in probe if e['type'] in ('pcb_smtpad','pcb_plated_hole') and e.get('pcb_port_id')==pcb_port['pcb_port_id']]
        if len(lands)!=1 or f'pin{pin}' not in lands[0]['port_hints']:
            raise ValueError('Electrical pin to footprint correspondence fails')
    holes=[e for e in probe if e['type']=='pcb_hole']
    if len(holes)!=2 or any(e.get('pcb_port_id') or abs(e['hole_diameter']-.65)>.05 or abs(abs(e['x'])-2.89)>.05 for e in holes):
        raise ValueError('Locating holes do not match the drawing or are electrical')
    plated=[e for e in probe if e['type']=='pcb_plated_hole']
    for hole in plated:
        if abs(abs(hole['x'])-4.32)>.05 or abs(hole['hole_width']-.6)>.05 or abs(hole['outer_width']-1.)>.05:
            raise ValueError('Shell slot width/span differs from drawing')
        front=hole['y']<0
        if abs(hole['hole_height']-(1.4 if front else 1.7))>.05 or abs(hole['outer_height']-(1.8 if front else 2.1))>.05:
            raise ValueError('Shell slot length differs from drawing')
        if abs((2.1150644-hole['y'])-(7.35-2.6 if front else 7.35-2.6-4.18))>.05:
            raise ValueError('Shell slot longitudinal position differs from drawing')
    # Relative hole separation4.18mm and peg separation5.78mm are independent of origin.
    if abs(max(e['y'] for e in plated)-min(e['y'] for e in plated)-4.18)>.05:
        raise ValueError('Shell slot pitch mismatch')
    diagnostics=[e for e in probe if 'warning' in e['type'] or 'error' in e['type']]
    allowed={'source_refdes_convention_warning','source_no_power_pin_defined_warning','source_no_ground_pin_defined_warning'}
    if len(diagnostics)!=3 or any(e['type'] not in allowed for e in diagnostics) or any(e['type']=='pcb_trace' for e in probe):
        raise ValueError('Unexpected diagnostics or routing in the supplier probe')
    return {'source':str(source),'source_sha256':hashlib.sha256(source.read_bytes()).hexdigest(),'original_sha256':original_sha,'probe_sha256':hashlib.sha256(probe_path.read_bytes()).hexdigest(),'supplier':'C5184243','electrical_pins':16,'smt_lands':12,'grounded_shell_slots':4,'non_electrical_locating_holes':2,'protected_supplier_definition_unchanged':True,'minimum_clearance_mm':min(g['clearance_mm'] for g in gaps),'gaps':gaps,'shifts':shifts,'voltage_rating_v_dc':48,'collective_vbus_current_a':5,'maximum_contract_budget_v':21,'manufacturer_drawing':'evidence/usb-c-C5184243-manufacturer-drawing-A45.pdf','manufacturer_pcb_layout_tolerance_mm':.05,'diagnostics_retained':diagnostics,'limitations':'THT paste export and full-board placement/3D/copper reviewed separately; no physical qualification implied.'}

if __name__=='__main__':
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--source',type=Path,default=Path('imports/USB4105_GF_A_120.tsx'))
    parser.add_argument('--report',type=Path)
    options=parser.parse_args();report=review(options.source)
    if options.report:options.report.write_text(json.dumps(report,indent=2)+'\n')
    print(json.dumps(report))