AnasSarkiz/magnetic-shutter-remote-r8

A USB‑C rechargeable battery-powered ESP32‑C3 wireless controller with Li‑ion charging, protected 3.3‑V buck‑boost regulation, thermal/safety monitoring, status LEDs, buttons, programming UART, crystal clock, and 2.4‑GHz antenna matching network.

Version
0.4.0-prototype
License
unset
Stars
0

scripts/audit-r8-circuit.ts

import { any_circuit_element } from "circuit-json";
import { z } from "zod";

// Electrical safety contracts from the selected module, TI and battery data.
const circuit = any_circuit_element.array().parse(
	await Bun.file(
		z
			.string()
			.min(1)
			.parse(Bun.argv[2] ?? "dist/index/circuit.json"),
	).json(),
);
const components = circuit.filter((e) => e.type === "source_component");
const ports = circuit.filter((e) => e.type === "source_port");
const nets = circuit.filter((e) => e.type === "source_net");
// Repeated supplier lands use canonical explicit internal connections. Never
// infer a shared net from a component name or matching terminal label alone.
const memberships = ports.map((port) => ({
	sourcePortId: port.source_port_id,
	connectivityKey: port.subcircuit_connectivity_map_key,
}));
const internalConnections = circuit.filter(
	(e) => e.type === "source_component_internal_connection",
);
let changed = true;
while (changed) {
	changed = false;
	for (const connection of internalConnections) {
		const group = memberships.filter((m) =>
			connection.source_port_ids.includes(m.sourcePortId),
		);
		const keys = new Set(
			group.flatMap((m) => (m.connectivityKey ? [m.connectivityKey] : [])),
		);
		if (keys.size > 1)
			throw new Error("Conflicting internally connected terminal nets");
		const key = [...keys][0];
		if (!key) continue;
		for (const membership of group) {
			if (!membership.connectivityKey) {
				membership.connectivityKey = key;
				changed = true;
			}
		}
	}
}
const contracts = [
	{
		ref: "SW2",
		mpn: "TS24CA",
		part: "C393942",
		pins: { 1: "SHUTTER", 2: "GND", 3: "GND", 4: "GND" },
	},
	{
		ref: "Q1",
		mpn: "2N7002",
		part: "C8545",
		pins: { 1: "USB5V", 2: "GND", 3: "REG_EN" },
	},
	{
		ref: "Q2",
		mpn: "2N7002",
		part: "C8545",
		pins: { 1: "TEMP_OK", 2: "GND", 3: "CHARGE_DISABLE" },
	},
	{
		ref: "SW1",
		mpn: "MSK12C02",
		part: "C431540",
		pins: { 1: "BATTERY_OK", 2: "POWER_SWITCH", 3: "GND", 4: "GND" },
	},
	{
		ref: "U4",
		mpn: "TPS3839G33DBZR",
		part: "C485802",
		pins: { 1: "GND", 2: "BATTERY_OK", 3: "VBAT" },
	},
	{
		ref: "U1",
		mpn: "ESPC3-12-N4",
		part: "C19949072",
		pins: {
			3: "EN",
			6: "SHUTTER",
			7: "PAIR",
			8: "V3",
			15: "GND",
			12: "STATUS_LED",
			18: "BOOT",
			21: "UART_RX",
			22: "UART_TX",
		},
	},
	{
		ref: "U3",
		mpn: "TPS63031DSKR",
		part: "C15516",
		pins: {
			1: "V3",
			2: "INDUCTOR_L2",
			3: "GND",
			4: "INDUCTOR_L1",
			5: "VBAT",
			6: "REG_EN",
			7: "GND",
			8: "VBAT",
			9: "GND",
			10: "V3",
			11: "GND",
		},
	},
	{
		ref: "J2",
		mpn: "SM02B-SRSS-TB(LF)(SN)",
		part: "C160402",
		pins: { 1: "GND", 2: "VBAT", 3: "GND", 4: "GND" },
	},
	{
		ref: "J3",
		mpn: "BM03B-SRSS-TB(LF)(SN)",
		part: "C160389",
		pins: { 1: "UART_RX", 2: "GND", 3: "UART_TX", 4: "GND", 5: "GND" },
	},
	{
		ref: "L1",
		mpn: "SWPA3015S1R5NT",
		part: "C56594",
		pins: { 1: "INDUCTOR_L2", 2: "INDUCTOR_L1" },
	},
];
for (const contract of contracts) {
	const component = components.find((e) => e.name === contract.ref);
	if (
		!component ||
		component.manufacturer_part_number !== contract.mpn ||
		!component.supplier_part_numbers?.jlcpcb?.includes(contract.part)
	) {
		throw new Error(`${contract.ref}: exact manufacturer identity mismatch`);
	}
	for (const [pin, netName] of Object.entries(contract.pins)) {
		const port = ports.find(
			(e) =>
				e.source_component_id === component.source_component_id &&
				e.pin_number === Number(pin),
		);
		const net = nets.find((e) => e.name === netName);
		if (
			!port ||
			!net?.subcircuit_connectivity_map_key ||
			memberships.find((m) => m.sourcePortId === port.source_port_id)
				?.connectivityKey !== net.subcircuit_connectivity_map_key
		) {
			throw new Error(`${contract.ref}.pin${pin}: expected ${netName}`);
		}
	}
}
const r3 = components.find((e) => e.name === "R3");
if (
	r3?.ftype !== "simple_resistor" ||
	r3.resistance !== 1000 ||
	r3.supplier_part_numbers?.jlcpcb?.[0] !== "C21190"
) {
	throw new Error("Charge setting must be the qualified exact 1k resistor");
}
const radio = components.find((e) => e.name === "U1");
if (
	!ports.find(
		(p) =>
			p.source_component_id === radio?.source_component_id &&
			p.pin_number === 8,
	)?.requires_power
) {
	throw new Error("C3 supply metadata is missing");
}
const board = circuit.find((e) => e.type === "pcb_board");
if (board?.width !== 44 || board.height !== 56 || board.num_layers !== 2) {
	throw new Error("Board envelope differs from battery/antenna qualification");
}
console.log(
	`R8 electrical contracts PASS; ${components.length} fitted references`,
);