imrishabh18/rp2040-motor-controller

A library of PCB component definitions covering MOSFETs, motor drivers, USB-PD control, sensors, current monitors, transistors, diodes, resistors, speakers, connectors, and mirrored bottom-side logos, with pin mappings, footprints, and 3D models.

Version
1.0.55
License
unset
Stars
5

scripts/validate-top-assembly.ts

import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { createHash } from "node:crypto";

const input = process.argv[2] ?? "dist/index/circuit.json";
const raw = readFileSync(input, "utf8");
const json: any[] = JSON.parse(raw);
const sources = new Map(json.filter(e => e.type === "source_component").map(e => [e.source_component_id, e]));
const parts = json.filter(e => e.type === "pcb_component" && sources.has(e.source_component_id));
const name = (part: any) => sources.get(part.source_component_id).name;
const bottom = parts.filter(e => e.layer === "bottom");
if (bottom.length !== 1 || name(bottom[0]) !== "U_ENCODER" || !bottom[0].do_not_place) {
  throw new Error("Only the DNP encoder may remain on the bottom");
}
const testPoints = [...sources.values()].filter(e => e.ftype === "simple_test_point" || e.name.startsWith("TP_"));
if (testPoints.length) throw new Error("Dedicated test points remain");
const populated = parts.filter(e => !e.do_not_place && !/^MH[1-4]$/.test(name(e)));
if (populated.some(e => e.layer !== "top")) throw new Error("Factory assembly requires a bottom component");
for (const designator of ["U_PD_MON", "C_PD_MON", "C_ENCODER", "C_ENCODER_BULK"]) {
  const part = parts.find(e => name(e) === designator);
  if (!part || part.layer !== "top") throw new Error(`${designator} must be on top`);
  if (Boolean(part.do_not_place) !== designator.startsWith("C_ENCODER")) {
    throw new Error(`Unexpected population state for ${designator}`);
  }
}

// Compare actual electrical edges using physical component pins, independently
// of regenerated source IDs and cached connectivity labels. Exclude only the
// seven deliberately removed test-point components.
function electricalPartition(circuit: any[]) {
  const names = new Map(circuit.filter(e => e.type === "source_component").map(e => [e.source_component_id, e.name]));
  const parent = new Map<string, string>();
  const root = (id: string): string => {
    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: string[] = 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]!));
  }
  const groups = new Map<string, string[]>();
  for (const e of circuit.filter(e => e.type === "source_port")) {
    const component = names.get(e.source_component_id);
    if (!component || component.startsWith("TP_")) continue;
    const group = root(e.source_port_id);
    groups.set(group, [...groups.get(group) ?? [], `${component}.${e.pin_number ?? e.name}`]);
  }
  return [...groups.values()].map(group => [...new Set(group)].sort().join("|")).sort();
}
let preservesFunctionalNetlist: boolean | null = null;
if (process.argv[3]) {
  const baseline = JSON.parse(readFileSync(process.argv[3], "utf8"));
  preservesFunctionalNetlist = JSON.stringify(electricalPartition(baseline)) === JSON.stringify(electricalPartition(json));
  if (!preservesFunctionalNetlist) throw new Error("Functional pin connectivity changed beyond test-point removal");
}
const report = {
  circuitJsonSha256: createHash("sha256").update(raw).digest("hex"),
  factoryAssemblySide: "top", populatedTopComponents: populated.length, populatedBottomComponents: 0,
  bottomComponents: bottom.map(e => ({ designator: name(e), doNotPlace: e.do_not_place, center: e.center })),
  doNotPlaceComponents: parts.filter(e => e.do_not_place).map(name).sort(),
  dedicatedTestPoints: testPoints.length, preservesFunctionalNetlist,
  removedTestPoints: ["TP_SWCLK", "TP_GND", "TP_SWDIO", "TP_3V3", "TP_PD", "TP_VMOTOR", "TP_PD_GND"],
  programming: "USB BOOT/RUN retained; external SWD probe pads removed",
};
mkdirSync("reports", { recursive: true });
writeFileSync("reports/single-sided-assembly.json", JSON.stringify(report, null, 2) + "\n");
console.log(JSON.stringify(report, null, 2));