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/compare-netlist.mjs
import fs from "node:fs"
import assert from "node:assert/strict"
function summarize(path, { series = [] } = {}) {
const circuit = JSON.parse(fs.readFileSync(path, "utf8"))
const sources = new Map(circuit.filter(e => e.type === "source_component")
.map(e => [e.source_component_id, e]))
const parent = new Map()
const root = id => {
if (!parent.has(id)) parent.set(id, id)
if (parent.get(id) !== id) parent.set(id, root(parent.get(id)))
return parent.get(id)
}
for (const e of circuit) {
const ids = e.type === "source_trace"
? [...e.connected_source_port_ids ?? [], ...e.connected_source_net_ids ?? []]
: e.type === "source_component_internal_connection" ? e.source_port_ids : []
for (const id of ids.slice(1)) parent.set(root(id), root(ids[0]))
}
// Contract only explicitly approved series additions for the comparison.
// First prove each resistor separates the two intended physical endpoints;
// this projection never alters the design or its connectivity checks.
const ports = circuit.filter(e => e.type === "source_port")
const getPort = ([name, pin]) => ports.find(p =>
sources.get(p.source_component_id)?.name === name && p.pin_number === pin)
for (const item of series) {
const part = [...sources.values()].find(p => p.name === item.name)
assert.ok(part, `${item.name} exists`)
assert.equal(part.resistance, item.resistance, `${item.name} series resistance`)
const a = getPort([item.name, 1]), b = getPort([item.name, 2])
const input = getPort(item.input), output = getPort(item.output)
assert.ok(a && b && input && output, `${item.name} endpoints exist`)
assert.equal(root(a.source_port_id), root(input.source_port_id), `${item.name} driver side`)
assert.equal(root(b.source_port_id), root(output.source_port_id), `${item.name} motor side`)
assert.notEqual(root(a.source_port_id), root(b.source_port_id), `${item.name} has no netlist bypass`)
parent.set(root(b.source_port_id), root(a.source_port_id))
}
const groups = new Map()
for (const port of circuit.filter(e => e.type === "source_port")) {
const name = `${sources.get(port.source_component_id).name}.${port.pin_number ?? port.name}`
const key = root(port.source_port_id)
const members = groups.get(key) ?? []
members.push(name)
groups.set(key, members)
}
const partFields = ["name", "ftype", "manufacturer_part_number", "supplier_part_numbers",
"resistance", "capacitance", "inductance", "frequency", "load_capacitance", "max_voltage_rating", "standard", "pin_count"]
const parts = [...sources.values()].map(e => Object.fromEntries(partFields
.filter(key => e[key] !== undefined).map(key => [key,e[key]])))
.sort((a,b) => a.name.localeCompare(b.name))
return { parts, nets: [...groups.values()].map(g => g.sort()).sort((a,b) => a.join().localeCompare(b.join())) }
}
const baseline = summarize(process.argv[2])
const series = process.argv[6] ? JSON.parse(fs.readFileSync(process.argv[6], "utf8")) : []
const current = summarize(process.argv[3] ?? "dist/index/circuit.json", { series })
if (series.length) console.log(`Checked series insertions: ${series.map(s => s.name).join(", ")}`)
if (process.argv[4]) {
const substitutions=JSON.parse(fs.readFileSync(process.argv[4],"utf8"))
for (const [name,change] of Object.entries(substitutions)) {
const before=baseline.parts.find(p=>p.name===name)
const after=current.parts.find(p=>p.name===name)
assert.ok(before && after, `substitution ${name} exists in both designs`)
for(const field of ["manufacturer_part_number","supplier_part_numbers"]) {
// A later baseline may already contain an earlier approved substitution.
const alreadyApplied=JSON.stringify(before[field])===JSON.stringify(change.after[field])
assert.deepEqual(before[field],alreadyApplied?change.after[field]:change.before[field],`${name} original ${field}`)
assert.deepEqual(after[field],change.after[field],`${name} replacement ${field}`)
before[field]=after[field]
}
}
console.log(`Explicitly checked BOM substitutions: ${Object.keys(substitutions).join(", ")}`)
}
if(process.argv[5]) {
const additions=JSON.parse(fs.readFileSync(process.argv[5],"utf8"))
const names=new Set(additions)
assert.equal(names.size,additions.length,"approved additions are unique")
for(const name of names) {
assert.ok(!baseline.parts.some(p=>p.name===name),`${name} is a new component`)
assert.ok(current.parts.some(p=>p.name===name),`${name} exists in the revised design`)
}
current.parts=current.parts.filter(p=>!names.has(p.name))
current.nets=current.nets.map(members=>members.filter(pin=>!names.has(pin.split(".")[0])))
.filter(members=>members.length).sort((a,b)=>a.join().localeCompare(b.join()))
console.log(`Approved additions: ${additions.join(", ")}; every pre-existing net must remain identical`)
}
assert.deepEqual(current, baseline, "parts and electrical connectivity match the working revision")
console.log(`Unchanged electrical design: ${current.parts.length} components, ${current.nets.length} electrical nets`)