shibosoftwaredev/f1c100s-linux-nema17-stepper-controller
A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.
- Version
- 0.4.56
- License
- unset
- Stars
- 0
scripts/review-round2.mjs
import fs from "node:fs"
import path from "node:path"
import crypto from "node:crypto"
import assert from "node:assert/strict"
const dir = process.argv[2]
assert.ok(dir, "Review evidence directory required")
const read = file => JSON.parse(fs.readFileSync(file, "utf8"))
const sha = bytes => crypto.createHash("sha256").update(bytes).digest("hex")
const bytes = fs.readFileSync("dist/index/circuit.json")
const circuit = JSON.parse(bytes)
const circuitSha256 = sha(bytes)
const of = type => circuit.filter(e => e.type === type)
const traces = of("source_trace")
function routeMetrics(name) {
const source = traces.find(e => e.name === name)
assert.ok(source, name)
const segments = new Map(), vias = new Set()
for (const t of of("pcb_trace").filter(e => e.source_trace_id === source.source_trace_id)) {
for (const p of t.route) if (p.route_type === "via") vias.add(`${p.x},${p.y}`)
for (let i = 1; i < t.route.length; i++) {
const a = t.route[i-1], b = t.route[i]
if (a.route_type !== "wire" || b.route_type !== "wire" || a.layer !== b.layer) continue
const length = Math.hypot(b.x-a.x, b.y-a.y)
if (length < 1e-7) continue
const ends = [[a.x,a.y],[b.x,b.y]].map(JSON.stringify).sort()
segments.set(JSON.stringify([a.layer,...ends]), {length,layer:a.layer,width:Math.max(a.width,b.width)})
}
}
const runs = [...segments.values()]
return {sourceTraceId:source.source_trace_id,wireLengthMm:runs.reduce((s,r)=>s+r.length,0),
vias:vias.size,minWidthMm:Math.min(...runs.map(r=>r.width)),
layers:[...new Set(runs.map(r=>r.layer))],
lengthByWidthMm:runs.reduce((s,r)=>(s[r.width]=(s[r.width]??0)+r.length,s),{})}
}
const metrics = Object.fromEntries(["CURRENT_A_OUTPUT","CURRENT_B_OUTPUT","TRACE_MOTOR_DIR",
"ETH_TXN_CHIP","ETH_TXP_CHIP","ETH_RXN_CHIP","ETH_RXP_CHIP","ETH_RXN_JACK","ETH_RXP_JACK",
"BUCK_OUT","BUCK_OUT1","BUCK_OUT2","MOTOR_A_PLUS","MOTOR_A_MINUS","MOTOR_B_PLUS",
"MOTOR_B_MINUS","MOTOR_A_SENSED_PLUS","MOTOR_B_SENSED_PLUS"].map(n=>[n,routeMetrics(n)]))
const staleWidthWarnings = []
for (const w of of("pcb_trace_warning")) {
const stated = w.message?.match(/actual: ([\d.]+)mm/)
const source = traces.find(t=>t.source_trace_id === w.source_trace_id)
if (!stated || !source?.name) continue
const actual = routeMetrics(source.name)
if (Math.abs(actual.minWidthMm - Number(stated[1])) > 1e-7)
staleWidthWarnings.push({name:source.name,statedMinMm:Number(stated[1]),emittedMinMm:actual.minWidthMm})
}
const root = "https://registry-api.tscircuit.com/"
async function request(endpoint, body) {
const response = await fetch(root+endpoint,{method:body?"POST":"GET",
headers:body?{"content-type":"application/json"}:undefined,
body:body?JSON.stringify(body):undefined,signal:AbortSignal.timeout(60000)})
assert.ok(response.ok, `${endpoint.split("?")[0]} HTTP ${response.status}`)
return response.json()
}
const name = "shibosoftwaredev/f1c100s-linux-nema17-stepper-controller"
const version = "0.4.47-review-fixes-20261019"
const pkg = (await request("packages/get",{name})).package
const release = (await request("package_releases/get",{package_name_with_version:`${name}@${version}`})).package_release
const files = (await request("package_files/list?"+new URLSearchParams({package_release_id:release.package_release_id}))).package_files
const entry = files.find(f=>f.file_path==="dist/index/circuit.json")
const file = (await request("package_files/get?"+new URLSearchParams({package_file_id:entry.package_file_id}))).package_file
const remoteBytes = file.content_text!=null?Buffer.from(file.content_text):Buffer.from(file.content_base64,"base64")
const remote = JSON.parse(remoteBytes)
const types = new Set(["pcb_board","pcb_component","pcb_port","pcb_smtpad","pcb_plated_hole","pcb_hole",
"pcb_trace","pcb_via","pcb_copper_pour","pcb_keepout","source_component","source_port","source_trace",
"source_net","source_component_internal_connection"])
const normalize = value=>JSON.stringify(value,(_,v)=>typeof v==="number"?Math.round(v*1e8)/1e8:
v&&typeof v==="object"&&!Array.isArray(v)?Object.fromEntries(Object.entries(v).sort(([a],[b])=>a.localeCompare(b))):v)
const critical = c=>c.filter(e=>types.has(e.type)).map(normalize).sort()
assert.deepEqual(critical(remote),critical(circuit),"Reviewed PCB/electrical records equal published board")
const geometry = read(path.join(dir,"expanded-geometry.json"))
const stock = read(path.join(dir,"jlcpcb-stock-audit.json"))
assert.equal(geometry.circuitSha256,circuitSha256)
assert.equal(stock.circuitSha256,circuitSha256)
assert.ok(stock.parts.every(p=>p.status==="in-stock"))
const drill = read("reports/drill-clearance-regression.json")
assert.equal(drill.circuitSha256,circuitSha256)
const historicalStyle = read("reports/schematic-style-audit.json")
const findings = [
{priority:"P1",id:"drill-clearance",certainty:"confirmed fabrication-rule violations",
evidence:"7 foreign-copper/drill findings at 3 via positions; minimum 0.180 mm versus 0.200 mm. No added findings relative to the pre-fix checkpoint.",
action:"Repair the PE10, USB_DP and PD-status fanouts without reducing 0.30/0.45 mm through-vias or clearance rules.",
artifact:"reports/drill-clearance-regression.json",source:"https://jlcpcb.com/capabilities/pcb-capabilities"},
{priority:"P1",id:"signal-via-in-pad-process",certainty:"confirmed pad/drill intersections; assembly process unresolved, not a measured solder failure",
evidence:"Ordinary component lands contain or intersect 0.30 mm through-drills: U_FLASH.5, R_ISEN_A/B.2, U_MOTOR.6/.9 and others. Only tented_on_top/bottom is specified; no filled/capped via process is in the release.",
action:"Move ordinary vias off solder lands with adequate mask separation, or explicitly specify and price qualified filled/capped processing. Review two-sided thermal-via/pad overlaps and stencil apertures separately.",
artifact:"expanded-geometry.json",source:"https://jlcpcb.com/help/article/pcb-via-covering"},
{priority:"P2",id:"silkscreen",certainty:"confirmed geometric and visual manufacturing-label defects",
evidence:"178/206 text records are below 1 mm. Five text anchors are already outside the strict outline, including R_INLINE_A/B; crowded lettering covers pads/other labels on both faces.",
action:"Remove redundant refdes and retain readable in-outline connector polarity, pin-1, power and recovery markings.",
artifacts:["pcb-top.png","pcb-bottom.png"],source:"https://jlcpcb.com/capabilities/pcb-capabilities"},
{priority:"P2",id:"schematic-label-readability",certainty:"visually confirmed, not an electrical short",
evidence:"The SoC-support sheet still crowds long capacitor refdes: C_B_VCC_HP overlaps the neighboring C_D5 label, and the VCC_USB bank labels crowd C_D73. Some automatically generated rail labels are trace names rather than clear voltage names. CLI placement pass does not establish text readability.",
action:"Space or shorten the decoupling-bank labels and clarify voltage-domain aliases; re-render all sheets and run a fresh hash-matched style analysis.",
artifact:"schematic-sheets/01-soc_support.png"},
{priority:"P2",id:"ethernet-layout-emi",certainty:"confirmed departures from layout guide; link/EMI failure not measured",
evidence:"MDI routes change layers, RX routes are unequal and exceed the recommended 25 mm. No dielectric stackup/100-ohm calculation is specified. BOARD_GND pours intersect the jack envelope; shield pins are directly on BOARD_GND rather than a separate chassis domain.",
action:"Review MDI as paired routes with a chosen stackup; establish jack/magnetics copper exclusion and shield/ESD treatment for the actual enclosure.",
source:"https://docs.wiznet.io/Design-Guide/hardware_design_guide"},
{priority:"P2",id:"power-and-analog-layout",certainty:"confirmed routing dimensions; voltage/noise/thermal consequences unmeasured",
evidence:"5 V BUCK_OUT/OUT1/OUT2 have 0.12 mm sections, including inner1 on OUT2; do not infer a 3 A board rating from the regulator/PD selection. CURRENT_A_OUTPUT is about 86.7 mm/5 vias and DIR about 99.1 mm/9 vias. Six motor phase paths themselves retain 0.30 mm outer copper.",
action:"Calculate actual peak/average current, drop and temperature for the 5 V distribution; widen/shorten where needed. Shorten and protect analog and feedback routes, then measure settling/PWM aliasing. Preserve the passing Kelvin topology.",
sources:["https://www.ti.com/lit/ds/symlink/tps54302.pdf","https://www.ti.com/lit/ds/symlink/ina241b.pdf"]},
{priority:"P2",id:"mechanics-and-harness",certainty:"unqualified physical/procurement interface, not a newly proven collision",
evidence:"PCB-side four-hole pattern and nominal M3-head envelopes pass. The motor's rear bolt interface, replacement bolt length and spacer assembly remain unmeasured. Modeled PH cable is not proof of a stocked compatible motor harness. Other-chat crimping/connector-family concerns therefore remain relevant.",
action:"Verify the exact motor and connector/contact order; specify a real adapter or disclosed four-splice pigtail, bolt lengths and spacer stack before procurement.",
artifacts:["motor-harness.md","hardware-validation.md"]},
{priority:"P2",id:"verification-completeness",certainty:"confirmed test failures and diagnostic coverage gaps",
evidence:`Module suite remains 6 pass/26 fail. Native records contain 150 warnings; ${staleWidthWarnings.length} width warnings disagree with the emitted post-replay widths. The saved cloud/AI style audit has a different circuit hash; this turn's CLI placement check is not a fresh AI style run.`,
action:"Repair/separate module profiles appropriately, regenerate diagnostics against final copper, and run hash-matched AI style review before claiming the whole checklist green."},
{priority:"P2",id:"hardware-and-image-qualification",certainty:"tests not performed; nominal transfer and safety architecture are not qualification",
evidence:"No fabricated-board cold-start, Linux image/boot/Ethernet, transient/thermal, watchdog fault-injection or current calibration measurements. DRV8825 nominal VREF is 0.595/0.311 V; TI warns that current accuracy degrades below 1 V. The 50 V tantalum rating derates above 85 C.",
action:"Complete the reproducible Linux image and staged first-article acceptance. Treat both nominal current channels as filtered telemetry, not calibrated RMS or independent protection.",
source:"https://www.ti.com/lit/ds/symlink/drv8825.pdf"}
]
const report = {reviewedAt:new Date().toISOString(),version,releaseId:release.package_release_id,
url:`https://tscircuit.com/${name}?version=${version}#3d`,latestVersion:pkg.latest_version,
reviewingCurrentLatest:pkg.latest_package_release_id===release.package_release_id,
localCircuitSha256:circuitSha256,publishedCircuitSha256:sha(remoteBytes),publishedElectricalAndCopperMatch:true,
fabricationReady:false,designOrRoutingChanged:false,pushedThisRound:false,findings,
checks:{nativeErrors:circuit.filter(e=>e.type.endsWith("_error")).length,nativeWarnings:circuit.filter(e=>e.type.endsWith("_warning")).length,
cliPlacement:"pass",shortsAllLayers:"pass",emittedContinuity:"zero failures/islands",kelvin:"4/4 pass",
copperPreflight:"zero findings at stated 0.10 mm scope",bomValueTests:"6 pass/11 assertions",typecheck:"pass",
firmwareSanitizers:"pass",linuxClient:"6 pass",moduleTests:{pass:6,fail:26},
stock:{checkedAt:stock.checkedAt,parts:stock.populatedComponentCount,codes:stock.uniquePartCount,
allOrderableForFiveBoards:true,reserved:false},independentDrillFindings:drill.currentFindings},
preserved:{outlineMm:[42.3,42.3],layers:4,mounts:4,vias:of("pcb_via").length,
compliantVias:of("pcb_via").every(v=>v.hole_diameter===.3&&v.outer_diameter===.45&&v.layers?.length===4),
sameEdgeUsb:true,standardUsbSymbols:true,topDriver:true,pdAdcAndReadableStatus:true,twoInlineCurrentChannels:true,
motionMcuAndWatchdogArchitecture:true,rgbTemperatureAndMosfetBuzzer:true,noI2cSpiExpansionHeaders:true},
routeMetrics:metrics,metricCaveat:"Identical source-route segments deduplicated; not full-net shortest-path/impedance/return-current analysis.",
staleWidthWarnings,expandedGeometry:geometry,
visualReview:{faces:["pcb-top.png","pcb-bottom.png"],schematicSheets:[1,2,3,4,5,6,7,8,9,10,11],
websiteSchematicInspected:true,freshAiStyleAnalysis:false,
historicalAiStyleHashMatches:historicalStyle.circuitSha256===circuitSha256},
limitations:["No physical inspection, oscilloscope, calibrated current, exact enclosure/hardware fit or production CAM approval",
"Mixed via/PTH gap of 0.443224 mm is a CAM-confirmation item, not an asserted violation of an unverified mixed-pair rule",
"No complete mask/paste/stencil or filled-via process qualification; no fabrication package released"]}
fs.writeFileSync(path.join(dir,"round2-review.json"),JSON.stringify(report,null,2)+"\n")
console.log(JSON.stringify({version,findings:findings.length,publishedMatches:true,checks:report.checks,
staleWidthWarningCount:staleWidthWarnings.length,routeMetrics:metrics},null,2))