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
tests/rishabh-review.test.ts
import { test, expect } from "bun:test"
import fs from "node:fs"
import path from "node:path"
import crypto from "node:crypto"
import {originalSourceContract,stablePhysicalContract,stripGrouping} from "./software-current-contract"
const baseline = "../../checkpoints/nema17-before-rishabh-review-v053-DuFOGi/restore-verified"
const read = (p: string) => JSON.parse(fs.readFileSync(p, "utf8"))
const before: any[] = read(baseline + "/dist/index/circuit.json")
const after: any[] = read("dist/index/circuit.json")
const of = (c: any[], t: string) => c.filter(e => e.type === t)
const component = (c: any[], name: string) => c.find(e => e.type === "source_component" && e.name === name)
const placed = (c: any[], name: string) => c.find(e => e.type === "pcb_component" && e.source_component_id === component(c, name).source_component_id)
const sha = (p: string) => crypto.createHash("sha256").update(fs.readFileSync(p)).digest("hex")
test("all original parts/values/connections remain, apart from the explicit DAC reference feed", () => {
originalSourceContract(before,after)
const fuse = component(after, "F_VM")
const originalIds=new Set(of(before,"source_port").map(e=>e.source_port_id))
const ports = structuredClone(of(after, "source_port").filter(e=>originalIds.has(e.source_port_id)))
const output = ports.find(e => e.source_component_id === fuse.source_component_id && e.pin_number === 2)
expect(output.provides_power).toBe(true)
delete output.provides_power // Intentional electrical annotation: this is the fused power feed.
expect(ports.map(stripGrouping)).toEqual(of(before, "source_port").map(stripGrouping))
})
test("bulk capacitor is assembled on top with unchanged value, rating and polarity", () => {
const cap = component(after, "C_VM_BULK"), pcb = placed(after, "C_VM_BULK")
expect(cap.manufacturer_part_number).toBe("CA55-D050M226TE100")
expect(cap.supplier_part_numbers.jlcpcb).toEqual(["C41417029"])
expect(cap.capacitance).toBeCloseTo(22e-6, 10)
expect(cap.max_voltage_rating).toBe(50)
expect(cap.are_pins_interchangeable).toBe(false)
expect(pcb.layer).toBe("top")
expect(pcb.center).toEqual({x:18.4,y:-7.9})
expect(pcb.rotation).toBe(90)
expect(pcb.do_not_place).toBe(false)
const cad = after.find(e => e.type === "cad_component" && e.pcb_component_id === pcb.pcb_component_id)
expect(cad.position.z).toBeGreaterThan(0)
expect(cad.rotation).toEqual({x:0,y:0,z:90})
expect(cad.model_obj_url).toBe(before.find(e => e.type === "cad_component" && e.pcb_component_id === pcb.pcb_component_id).model_obj_url)
const tvs = placed(after,"D_VM_TVS")
expect(tvs.layer).toBe("bottom")
expect(after.find(e=>e.type==="cad_component"&&e.pcb_component_id===tvs.pcb_component_id).position.z).toBeLessThan(0)
})
test("the eight reviewed bulk-capacitor moves and the new reference-resistor move are the ONLY original placement changes", () => {
const moves = read("review-placement.json")
const moved = new Set(of(after, "pcb_component").filter(e => moves[after.find(s => s.source_component_id === e.source_component_id && s.type === "source_component").name]).map(e => e.pcb_component_id))
expect(Object.keys(moves).sort()).toEqual(["C_VM_BULK","D_VM_TVS","D_VM_BLOCK","R_CURRENT_A_FILTER","C_CURRENT_A_FILTER","R_TEMP_ALERT","R_PD_12V","R_PD_DEBUG"].sort())
expect(moved.size).toBe(8)
for(const [name,move] of Object.entries(moves) as [string,any][]) {
const p=placed(after,name)
expect(p.center.x).toBeCloseTo(move.x,9)
expect(p.center.y).toBeCloseTo(move.y,9)
expect(p.rotation).toBe(move.rotation)
expect(p.layer).toBe(move.layer)
expect(of(after,"pcb_smtpad").filter(e=>e.pcb_component_id===p.pcb_component_id).every(e=>e.layer===move.layer)).toBe(true)
}
stablePhysicalContract({before,after,allowedMoves:new Set([...Object.keys(moves),"R_REF_TOP","C_U_2V5_IN","C_ETH_1"])})
})
test("board outline, screw holes, USB positions and complete motor assembly are preserved", () => {
expect(of(after, "pcb_board").map(stripGrouping)).toEqual(of(before, "pcb_board").map(stripGrouping))
expect(of(after, "pcb_hole").map(stripGrouping)).toEqual(of(before, "pcb_hole").map(stripGrouping))
for (const type of new Set(before.filter(e => e.type.startsWith("cad_") && e.type !== "cad_component").map(e => e.type)))
expect(of(after, type).map(stripGrouping)).toEqual(of(before, type).map(stripGrouping))
for (const name of ["J_USB_DATA", "J_USB_PWR", "J_ETH", "J_MOTOR", "U_MOTOR", "MH1", "MH2", "MH3", "MH4"])
expect(stripGrouping(placed(after, name))).toEqual(stripGrouping(placed(before, name)))
})
test("all thirteen sheets have explanatory sections and zero current style / page-boundary issues", () => {
const sheets = of(after, "schematic_sheet")
expect(sheets.length).toBe(13)
expect(sheets.find(s => s.sheet_index === 0)?.name).toBe("overview")
for (const s of sheets) {
expect(of(after, "schematic_rect").filter(e => e.schematic_sheet_id === s.schematic_sheet_id && !e.schematic_component_id)).toEqual([])
expect(of(after, "schematic_line").filter(e => e.schematic_sheet_id === s.schematic_sheet_id).length).toBeGreaterThanOrEqual(2)
expect(of(after, "schematic_text").filter(e => e.schematic_sheet_id === s.schematic_sheet_id).length).toBeGreaterThan(6)
}
expect(of(after, "schematic_element_outside_sheet_warning")).toEqual([])
const report = read("reports/review-style/analysis.json")
expect(report.circuitSha256).toBe(sha("dist/index/circuit.json"))
expect(report.analyzerVersion).toBe("0.0.82")
expect(report.issueCount).toBe(0)
})
test("all vias remain full-stack 0.30 mm holes / 0.45 mm pads", () => {
for (const v of of(after, "pcb_via")) {
expect(v.hole_diameter).toBe(.3)
expect(v.outer_diameter).toBe(.45)
expect(v.layers).toEqual(["top", "inner1", "inner2", "bottom"])
}
})
test("all unrelated firmware and Linux module sources remain byte-identical", () => {
const declared=new Set(["firmware/PROTOCOL.md","firmware/motor-control.py","firmware/test_motor_control.py",
"firmware/stm32/Makefile","firmware/stm32/main.c","firmware/stm32/motion.c","firmware/stm32/motion.h","firmware/stm32/test_motion.c"])
let count = 0
function visit(dir: string) {
for (const e of fs.readdirSync(path.join(baseline, dir), { withFileTypes: true })) {
if (["node_modules", "dist", "build", "reports", ".git"].includes(e.name)) continue
const p = path.join(dir, e.name)
if (e.isDirectory()) visit(p)
else { if(!declared.has(p))expect(sha(p)).toBe(sha(path.join(baseline, p)));count++ }
}
}
visit("modules"); visit("firmware")
expect(count).toBe(56)
})