文件
phonerail/kicad/scripts/unconnected_inventory.py

63 行
3.6 KiB
Python

"""Classify singleton unconnected nets without declaring them electrically safe."""
from collections import Counter
import csv
import json
import re
def classify(board, ref, pin):
if board == 2 and re.fullmatch(r'J7[1-5]', ref):
return 'phone_tail_contact', 'Check Xiaomi J1401 signal assignment and retained phone functions before NC'
if board == 1 and ref == 'U20':
return 'mcu_recovery' if pin in ('27', '36', '37') else 'mcu_spare_or_strap', 'Review recovery interface, boot straps and module memory variant'
if board == 1 and ref in ('U6', 'U7'):
return 'unused_dac_channel', 'Unused by current rail map; verify Hi-Z at reset, NVM restore and faults before NC'
if board == 2 and ref == 'U32':
if pin in ('21', '22', '27', '28', '35', '37', '38'):
return 'hub_overcurrent', 'Internal pull-up documented; review port power control and system overcurrent responsibility'
if pin in ('20', '23', '26', '29', '30', '36', '39'):
return 'hub_port_power', 'Review firmware-controlled TPS22918 sequencing versus hub port power requests'
if pin in ('53', '55'):
return 'hub_unused_usb_port', 'Verify port 6/7 disable straps and USB enumeration'
return 'hub_led_or_strap', 'Review sampled strap default and enabled LED function before NC'
if board == 2 and re.fullmatch(r'U4[1-5]', ref) and pin == '3':
return 'edl_normally_closed', 'NC means normally closed switch port; confirm COM/NO wiring and default-open EDL behavior'
if board == 2 and ref == 'U34':
return 'temperature_alert', 'Firmware polls temperature; review fault coverage without OS interrupt'
if ref in ('J22', 'J23'):
return 'usb_unused_contact', 'Confirm USB2-only SBU policy or externally powered debug VBUS policy'
if board == 1 and ref == 'J1':
return 'input_connector_mechanical', 'Confirm connector pad 3/4 are mechanical anchors and soldering requirements'
return 'unclassified', 'Manual schematic and datasheet review required'
def inventory(board):
rows = []
for net, endpoints in sorted(board.net_members.items()):
if len(endpoints) != 1 or not net.startswith('unconnected-'):
continue
ref, pin = next(iter(endpoints))
kind = board.pin_types[(ref, pin)]
if 'no_connect' in kind.split('+'):
continue
category, action = classify(board.number, ref, pin)
rows.append({'board': board.number, 'sheet': board.parts[ref]['page'], 'reference': ref,
'pin': pin, 'function': board.functions[(ref, pin)], 'type': kind,
'net': net, 'category': category, 'status': 'REVIEW_REQUIRED', 'next_action': action})
if len(rows) != board.erc.get('pin_not_connected', 0):
raise ValueError('Singleton inventory does not account for every unconnected-pin ERC item')
return rows
def write_inventory(out, rows):
with (out / 'unconnected-pins.csv').open('w', encoding='utf-8-sig', newline='') as stream:
writer = csv.DictWriter(stream, fieldnames=['board', 'sheet', 'reference', 'pin', 'function',
'type', 'net', 'category', 'status', 'next_action'])
writer.writeheader()
writer.writerows(rows)
summary = {'total': len(rows), 'by_category': dict(Counter(r['category'] for r in rows)),
'by_board': dict(Counter(r['board'] for r in rows)),
'note': 'Every row remains open; classification is not an NC approval or hardware acceptance'}
(out / 'unconnected-summary.json').write_text(json.dumps(summary, indent=2) + '\n', encoding='utf-8')
return summary