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-round11-published.mjs
import fs from "node:fs"
import crypto from "node:crypto"
import assert from "node:assert/strict"
const read = p => JSON.parse(fs.readFileSync(p, "utf8"))
const sha = p => crypto.createHash("sha256").update(fs.readFileSync(p)).digest("hex")
const input = process.env.REVIEW_CIRCUIT ?? "dist/index/circuit.json"
const circuit = read(input)
const of = type => circuit.filter(e => e.type === type)
const sources = new Map(of("source_trace").map(e => [e.source_trace_id, e]))
const traces = new Map(of("pcb_trace").map(e => [e.pcb_trace_id, e]))
const geometry = source => {
const result = { lengthMm: 0, minimumMm: Infinity, layers: new Set(), segments: 0 }
for (const t of of("pcb_trace").filter(t => t.source_trace_id === source)) {
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(a.x - b.x, a.y - b.y)
if (length < 1e-8) continue
result.lengthMm += length
result.minimumMm = Math.min(result.minimumMm, a.width, b.width)
result.layers.add(a.layer)
result.segments++
}
}
return { ...result, layers: [...result.layers] }
}
const sourceNamed = name => {
const s = [...sources.values()].find(s => s.name === name)
assert.ok(s, `source trace exists: ${name}`)
return { name, sourceTraceId: s.source_trace_id, requestedMm: s.min_trace_thickness,
...geometry(s.source_trace_id) }
}
const motorRoutes = ["DRV_ISEN_A", "DRV_ISEN_B", "MOTOR_A_PLUS", "MOTOR_A_MINUS",
"MOTOR_B_PLUS", "MOTOR_B_MINUS"].map(sourceNamed)
for (const t of motorRoutes) {
assert.ok(t.segments && t.minimumMm >= .3 - 1e-8)
assert.ok(t.layers.every(l => ["top", "bottom"].includes(l)))
}
const inlineToConnector = ["source_trace_439", "source_trace_454"].map(source => ({
sourceTraceId: source, ...geometry(source) }))
for (const t of inlineToConnector) {
assert.ok(t.segments && t.minimumMm >= .3 - 1e-8)
assert.ok(t.layers.every(l => ["top", "bottom"].includes(l)))
}
const suspectWarnings = ["source_trace_135", "source_trace_353", "source_trace_355",
"source_trace_439", "source_trace_454"].map(source => {
const warning = of("pcb_trace_warning").find(e => e.source_trace_id === source)
assert.ok(warning)
const flagged = traces.get(warning.pcb_trace_id)
assert.ok(flagged && flagged.source_trace_id !== source)
const main = sources.get(source), branch = sources.get(flagged.source_trace_id)
const mainCopper = geometry(source), branchCopper = geometry(branch.source_trace_id)
assert.ok(mainCopper.minimumMm >= main.min_trace_thickness - 1e-8)
assert.ok(branchCopper.minimumMm >= branch.min_trace_thickness - 1e-8)
return { warningId: warning.pcb_trace_warning_id, namedSource: source,
namedSourceCopper: mainCopper, flaggedPcbTrace: warning.pcb_trace_id,
actualFlaggedSource: branch.source_trace_id, actualFlaggedName: branch.name,
actualFlaggedCopper: branchCopper, diagnosticMisassociation: true,
warningSuppressed: false }
})
const vias = of("pcb_via")
assert.equal(vias.length, 352)
assert.ok(vias.every(v => v.hole_diameter === .3 && v.outer_diameter === .45 &&
v.layers.length === 4 && ["top", "inner1", "inner2", "bottom"].every(l => v.layers.includes(l))))
const board = of("pcb_board")[0]
assert.equal(board.thickness, 1.6)
assert.equal(board.num_layers, 4)
const validated = read("reports/round10-fix-status.json")
assert.equal(sha("dist/index/circuit.json"), validated.circuitSha256)
for (const [file, hash] of Object.entries(validated.evidence)) assert.equal(sha(file), hash, file)
const publication = read("reports/round10-publication.json")
const freshCheckEvidence = Object.fromEntries([
"reports/round11-shorts.log", "reports/round11-schematic-placement.log",
"reports/round11-source.log", "reports/round11-native.log",
"reports/round11-pin-specification.log", "reports/round11-published-shorts.log",
"reports/round11-published-schematic-placement.log"
].filter(p => fs.existsSync(p)).map(p => [p, sha(p)]))
const report = {
reviewedAt: new Date().toISOString(), input, circuitSha256: sha(input),
publication, auditedLocalCircuitSha256: validated.circuitSha256, freshCheckEvidence,
fabricationReady: false, electricalOrRoutingChangesDuringReview: false,
preserved: { boardSquareMm: 42.3, layers: 4, thicknessMm: 1.6,
mountingHoles: 4, mountSquareMm: 31, throughVias: vias.length,
viaHoleMm: .3, viaPadMm: .45, blindOrBuriedVias: 0 },
nativeErrors: circuit.filter(e => e.type.endsWith("_error")).length,
nativeWarnings: circuit.filter(e => e.type.endsWith("_warning")).length,
motorRoutes, inlineToConnector, suspectWarnings,
analog: [sourceNamed("CURRENT_A_OUTPUT"), sourceNamed("CURRENT_B_OUTPUT")],
buckReview: ["BUCK_IN_HF", "BUCK_SW", "BUCK_OUT", "BUCK_OUT1", "BUCK_OUT2",
"BUCK_FB", "BUCK_FB_BOTTOM", "BUCK_FF_CAP", "BUCK_FF_RETURN"].map(sourceNamed),
findings: [
{ priority: "P1", id: "assembly-stock", status: "confirmed current procurement blocker",
evidence: "The hash-bound 2026-10-20 stock audit has orderable stock zero for W5500 C32843 and BSS84-7-F C85202 despite displayed stock. No substitution or stock waiver was made.",
action: "Confirm inventory/preorder/consignment or qualify exact replacements before a JLCPCB assembly order.",
artifact: "reports/jlcpcb-stock-audit.json" },
{ priority: "P1", id: "thermal-solder-process", status: "assembly-process signoff missing, not 16 ordinary shorts",
evidence: "Ordinary solder-land/drill intersections are zero. Sixteen deliberate thermal-pad/drill intersection records remain across U1, U_PD and U_MOTOR; filled/capped processing and the thermal stencil/opposite-face process are not approved.",
action: "Obtain a documented thermal-via/stencil/tenting and double-sided assembly process accepted by the assembler. Do not assume a premium fill/cap process or delete thermal stitches.",
artifact: "reports/solder-process-audit.json" },
{ priority: "P1", id: "rear-motor-interface", status: "mechanical product-fit qualification missing",
evidence: "The board-side four-hole/31 mm pattern and nominal 5.5 mm M3 head clearance pass. A generic NEMA17 motor CAD model does not prove the actual 17HS13-0404S1 rear threads, original bolt length, or 6 mm insulating-spacer stack.",
action: "Measure or obtain supplier confirmation of the actual rear fastening and required bolt engagement. Do not promise reuse of the original motor screws.",
artifacts: ["motor-harness.md", "hardware-validation.md"],
source: "https://www.omc-stepperonline.com/nema-17-bipolar-1-8deg-26ncm-36-8oz-in-0-4a-12v-42x42x34mm-4-wires-17hs13-0404s1" },
{ priority: "P2", id: "return-path-and-power-layout", status: "layout/qualification risk, not demonstrated failure",
evidence: "Four layers and inner GND fills do not establish continuous high-speed return paths or controlled impedance. Buck feed-forward routing is about 23 mm plus 6.9 mm return; some 5 V branch/ground necks remain 0.12 mm. Width checks are not a current/thermal or loop-inductance qualification.",
action: "Select the manufacturing stackup, review paired USB/Ethernet routes and their returns/magnetics exclusion, reduce feedback-loop exposure and budget actual branch currents/drop/temperature.",
artifacts: ["routing/physical-copper.json", "reports/round10-width-proof.json"] },
{ priority: "P2", id: "current-output-exposure", status: "confirmed detour, noise error not measured",
evidence: "CURRENT_A_OUTPUT has approximately 86.72 mm of routed centreline versus 8.06 mm for B. Both inline shunts, amplifier connections, filters and four Kelvin checks pass. Readings are nominal filtered samples, not calibrated RMS current or an independent protection loop.",
action: "Shorten and protect the A output/return where possible; characterize PWM settling, offset/gain and aliasing on hardware before relying on its numerical accuracy.",
artifact: "reports/round10-kelvin.log" },
{ priority: "P2", id: "harness-qualification", status: "documented integration requirement, not direct plug-in compatibility",
evidence: "The current motor listing specifies a 2.54 mm connector, while J_MOTOR uses 2.00 mm JST-PH. The four coloured rendered leads and generic jstph4 motor endpoint are illustrative, not an off-the-shelf adapter.",
action: "Identify the actual motor-side housing/contact order, or follow the documented PH pigtail splice plan; verify both coil pairs with an ohmmeter.",
artifact: "motor-harness.md",
source: "https://www.omc-stepperonline.com/nema-17-bipolar-1-8deg-26ncm-36-8oz-in-0-4a-12v-42x42x34mm-4-wires-17hs13-0404s1" },
{ priority: "P2", id: "operational-qualification", status: "not a tested ready-to-use product",
evidence: "carrier.dtsi is explicitly a fragment, not a complete bootable image. MCU firmware builds and host tests pass but PD cold-start/fallback, USB attach/FEL, ROM update/option bytes, watchdog fault injection, current calibration, thermal and regenerative transients have not been qualified on hardware.",
action: "Produce a reproducible complete Linux image and execute the staged first-article acceptance plan, including 1.2 V LDO load/thermal margin and 20-pin STM32 ROM bootloader/version checks.",
artifacts: ["hardware-validation.md", "firmware/carrier.dtsi", "firmware/stm32/README.md"] },
{ priority: "P2", id: "published-documentation", status: "confirmed stale/conflicting current instructions",
evidence: "The published README retains 'not pushed' current-state language and later says the USB DATA VBUS pads are unconnected and its hardware gate is not installed. This conflicts with the implemented host-only VBUS/USB gate. It also contains older no-MCU-current-measurement wording and package-page relative document links that resolve to incorrect URLs.",
action: "Replace accumulated current-state histories with one authoritative current configuration, archive the changelog and link documentation to the actual package files. Retain clear development-only/fabrication-blocker disclosure.",
artifact: "README.md" },
{ priority: "P3", id: "schematic-style-followup", status: "visual cleanup remains; no fresh AI clean bill claimed",
evidence: "Eleven functional sheets and standard USB-C symbols remain. C_U_3V3_OUT is still to the left of U_3V3 while C_U_3V3_IN is on its right, contrary to left-input/right-output flow. The PD/buck sheet separates output capacitors from the associated power flow, current-sensor bypass capacitors are detached from their amplifier blocks, and the motion sheet has unnecessarily long SWD/reset/ground wires. CLI placement passing is not equivalent to a fresh cloud AI Style Analysis.",
action: "Place local bypass/output capacitors beside their associated blocks and rerun the website/AI style review. Do not change electrical nets to obtain cosmetic results.",
artifacts: ["reports/schematic-sheets/04-pd_power.png", "reports/schematic-sheets/05-power_rails.png",
"reports/schematic-sheets/07-motion.png", "reports/schematic-sheets/11-coil_current.png"] }
],
priorValidation: validated.checks,
limits: ["No fabricated hardware, electrical measurements, full 3D collision solver or production CAM approval.",
"153 native warnings are retained; only the five named width-warning associations are explicitly resolved here.",
"No fresh cloud AI schematic-style analysis is claimed. Inventory is point-in-time, not reserved."]
}
if (process.env.WEBSITE_VISUALLY_VERIFIED === "1") {
assert.ok(publication.latestVerified && publication.cloudMatchesAuditedBoard)
const screenshot = "reports/round11-published-website.png"
report.visualReview = {
defaultLatestConfirmedInBrowser: true,
url: "https://tscircuit.com/shibosoftwaredev/f1c100s-linux-nema17-stepper-controller#3d",
screenshot, screenshotSha256: sha(screenshot),
cableVisible: true, qualifiedMechanicalFitClaimed: false,
inspectedLocalViews: ["reports/round10-pcb-top.png", "reports/round10-pcb-bottom.png",
"reports/schematic-sheets/04-pd_power.png", "reports/schematic-sheets/05-power_rails.png",
"reports/schematic-sheets/07-motion.png", "reports/schematic-sheets/11-coil_current.png"],
transientWebsiteApiFetchFailure: "Resolved by reload; unversioned package page shows v0.4.50-dev-20261020."
}
}
assert.equal(report.nativeErrors, 0)
assert.equal(report.nativeWarnings, 153)
fs.writeFileSync("reports/round11-review.json", JSON.stringify(report, null, 2) + "\n")
const markdown = [
"# Round 11 — NEMA17 development-board review", "",
`Reviewed: ${report.reviewedAt}`, "",
`Release: [${publication.version}](${publication.url})`, "",
`Default latest verified: ${publication.latestVerified}. Cloud build: ${publication.buildId}.`, "",
`Circuit SHA-256: \`${report.circuitSha256}\`.`, "",
"This is a development publication, not fabrication approval. No board routing, placement, electrical nets or firmware were changed during this review.", "",
"## Checks", "",
"- 0 native circuit errors; 153 native warnings remain visible.",
"- Four layers, 42.3 mm square, 1.6 mm thickness, four holes on 31 mm square.",
"- All 352 manufactured vias are 0.30 mm drill / 0.45 mm pad, L1–L4 through-vias.",
"- Actual motor and shunt current routes, including inline-shunt-to-connector paths, remain at least 0.30 mm on outer copper.",
"- Five named Ethernet/coil width warnings reference other, separately constrained branches. Those main and branch routes meet their own constraints; warnings were not suppressed.",
"- Fresh local and downloaded-website-artifact all-layer Gerber shorts and schematic placement checks pass; local source/pin/native checks also pass. Round-10 hash-bound continuity, independent copper/drill, mechanical, Kelvin and regression evidence remains unchanged.",
"- All 32 module tests, six BOM tests and firmware/host checks passed in the validated round-10 evidence. Full verification/assembly export remains blocked, not waived.", "",
"## Findings", "",
...report.findings.flatMap(f => [
`### ${f.priority}: ${f.id}`, "", `Status: ${f.status}.`, "", f.evidence, "",
`Next action: ${f.action}`, "",
...(f.source ? [`[Primary source](${f.source})`, ""] : []),
`Evidence: ${(f.artifacts ?? [f.artifact]).filter(Boolean).map(p => `\`${p}\``).join(", ")}.`, ""
]),
"## Scope limits", "", ...report.limits.map(s => `- ${s}`), "",
"The live website renders the motor cable. Its generic motor/connector model is illustrative, not proof of rear fasteners, a purchasable mating harness or exact 3D clearance. No fresh AI schematic-style report or hardware measurement is claimed.", ""
].join("\n")
fs.writeFileSync("reports/round11-review.md", markdown)
console.log(JSON.stringify({ input, circuitSha256: report.circuitSha256,
latestVerified: publication.latestVerified, findings: report.findings.length,
nativeErrors: report.nativeErrors, nativeWarnings: report.nativeWarnings,
motorWidthWarningRegressions: 0, fabricationReady: false }))