krishnax12/watchy-eink-smartwatch

Unfinished prototype (not production-ready): 4 native DRC errors, 2 display-flex intersections, and missing antenna radiator copper remain

Version
1.2.7
License
MIT
Stars
1

scripts/check-reference-components.ts

import type { AnyCircuitElement } from "circuit-json";
import { parseKicadSch } from "kicadts";

const schematicPath = process.argv[2];
if (!schematicPath) throw new Error("Pass the Watchy v3 KiCad schematic path");
const circuitPath = process.argv[3] ?? "dist/index/circuit.json";
const schematic = parseKicadSch(await Bun.file(schematicPath).text());
const circuit: AnyCircuitElement[] = await Bun.file(circuitPath).json();

const unitMultipliers: Record<string, number> = {
	"": 1,
	p: 1e-12,
	n: 1e-9,
	u: 1e-6,
	m: 1e-3,
	k: 1e3,
	K: 1e3,
	M: 1e6,
	G: 1e9,
};

function parseNominalQuantity(nominal: string) {
	const quantity = nominal.split(/[\s/]/)[0];
	const match = quantity.match(
		/^([0-9]+(?:\.[0-9]+)?)([pnumkKMG]?)(?:Hz|F|H)?$/,
	);
	if (!match) throw new Error(`Unsupported reference quantity: ${nominal}`);
	return Number(match[1]) * unitMultipliers[match[2]];
}

const comparisons = [];
for (const symbol of schematic.symbols) {
	const reference = symbol.properties.find(
		(property) => property.key === "Reference",
	)?.value;
	if (!reference || !/^[CRLY][0-9]+$/.test(reference)) continue;
	const nominal = symbol.properties.find(
		(property) => property.key === "Value",
	)?.value;
	if (!nominal) throw new Error(`Missing reference quantity: ${reference}`);
	const component = circuit.find(
		(element) =>
			element.type === "source_component" && element.name === reference,
	);
	if (!component || component.type !== "source_component")
		throw new Error(`Missing compiled component: ${reference}`);
	let compiledQuantity: number | string | undefined;
	if (component.ftype === "simple_capacitor")
		compiledQuantity = component.capacitance;
	else if (component.ftype === "simple_resistor")
		compiledQuantity = component.resistance;
	else if (component.ftype === "simple_inductor")
		compiledQuantity = component.inductance;
	else if (component.ftype === "simple_crystal")
		compiledQuantity = component.frequency;
	const actualQuantity =
		typeof compiledQuantity === "string"
			? parseNominalQuantity(compiledQuantity)
			: compiledQuantity;
	const expectedQuantity = parseNominalQuantity(nominal);
	const expectedVoltage = nominal.includes("/")
		? Number(nominal.split("/")[1].replace(/V$/, ""))
		: undefined;
	const actualVoltage =
		component.ftype === "simple_capacitor"
			? component.max_voltage_rating
			: undefined;
	comparisons.push({
		reference,
		nominal,
		expectedQuantity,
		actualQuantity,
		expectedVoltage,
		actualVoltage,
		matches:
			actualQuantity !== undefined &&
			Math.abs(actualQuantity - expectedQuantity) <= expectedQuantity * 1e-9 &&
			(expectedVoltage === undefined || actualVoltage === expectedVoltage),
	});
}
const mismatches = comparisons.filter((comparison) => !comparison.matches);
await Bun.write(
	"reports/reference-components.json",
	JSON.stringify(
		{
			schematicPath,
			circuitPath,
			scope:
				"Compiled R/C/L quantities, explicit capacitor voltage ratings, and crystal frequencies versus KiCad schematic. Does not certify purchased MPN, tolerance, crystal load capacitance, or PCB tuning.",
			comparisons,
		},
		null,
		2,
	),
);
console.log({ checked: comparisons.length, mismatches });
if (mismatches.length) process.exitCode = 1;