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`)