))\n if (!match) return null\n return Number(match[1]) * ({p:1e-12,n:1e-9,u:1e-6,m:1e-3,k:1e3,K:1e3,M:1e6,\"\":1}[match[2]])\n}\n\nexport function validateCatalogPart(source, catalog) {\n const errors = []\n const attributes = catalog.attributes ?? {}\n for (const [field, key, unit] of [[\"resistance\",\"Resistance\",\"Ω\"],[\"capacitance\",\"Capacitance\",\"F\"],[\"inductance\",\"Inductance\",\"H\"]]) {\n if (source[field] === undefined) continue\n const actual = parseCatalogValue(attributes[key], unit)\n if (actual === null || Math.abs(actual-source[field]) \u003e Math.max(1e-15, Math.abs(source[field])*1e-8))\n errors.push(`${key}: design ${source[field]}, catalog ${attributes[key] ?? \"unverified\"}`)\n }\n if (source.max_voltage_rating !== undefined \u0026\u0026 source.ftype === \"simple_capacitor\") {\n const actual = parseCatalogValue(attributes[\"Voltage Rating\"], \"V\")\n if (actual === null || actual \u003c source.max_voltage_rating)\n errors.push(`Voltage Rating: design ${source.max_voltage_rating}, catalog ${attributes[\"Voltage Rating\"] ?? \"unverified\"}`)\n }\n if (source.manufacturer_part_number \u0026\u0026 source.manufacturer_part_number !== catalog.manufacturerPartNumber)\n errors.push(`MPN: design ${source.manufacturer_part_number}, catalog ${catalog.manufacturerPartNumber}`)\n return errors\n}\n\nif (import.meta.main) {\n const bytes = fs.readFileSync(\"dist/index/circuit.json\")\n const circuit = JSON.parse(bytes), stock = JSON.parse(fs.readFileSync(\"reports/jlcpcb-stock-audit.json\", \"utf8\"))\n const sha256 = crypto.createHash(\"sha256\").update(bytes).digest(\"hex\")\n assert.equal(stock.circuitSha256, sha256, \"stock report must match actual circuit\")\n const components = new Map(circuit.filter(e =\u003e e.type === \"source_component\").map(e =\u003e [e.name,e]))\n const findings = []\n let checked = 0\n for (const part of stock.parts) for (const name of part.designators) {\n const source = components.get(name)\n assert.ok(source, `catalog designator ${name} exists`)\n assert.equal(source.supplier_part_numbers?.jlcpcb?.[0], part.code)\n checked++\n const errors = validateCatalogPart(source, part)\n if (errors.length) findings.push({name,code:part.code,errors})\n }\n const rx = [\"C_ETH_RXN_AC\",\"C_ETH_RXP_AC\"]\n for (const name of rx) {\n const part = stock.parts.find(p =\u003e p.designators.includes(name))\n if (part?.package !== \"0402\") findings.push({name,errors:[\"RX capacitor requires verified 0402 catalog package\"]})\n }\n assert.equal(checked, stock.populatedComponentCount)\n const report = {checkedAt:new Date().toISOString(),circuitSha256:sha256,populatedChecked:checked,\n findings,scope:\"All populated catalog codes/declared MPNs; R/C/L nominal values; declared capacitor voltage ratings; RX capacitor package. Other package/pin/polarity verification remains in verify.mjs and mechanical inspection.\"}\n fs.writeFileSync(\"reports/bom-validation.json\",JSON.stringify(report,null,2)+\"\\n\")\n console.log(JSON.stringify(report,null,2))\n if (findings.length) process.exitCode=1\n}\n","content_mimetype":"text/javascript","created_at":"2026-10-24T10:42:19.584Z"}; window.SSR_PACKAGE_BUILDS = []; window.SSR_RELATED_PACKAGES = [{"package_id":"6711d713-269d-4ef5-9b16-c3092c520ac2","creator_account_id":"63586009-ac32-446c-b5aa-a60ea81a44bf","latest_package_release_id":"a44a026d-333f-490a-89c7-b4ae57b8387a","last_built_release_id":"a44a026d-333f-490a-89c7-b4ae57b8387a","latest_package_release_fs_sha":"md5-49ede528b8884e7b19962fb2649ccaf5","latest_pcb_preview_image_url":"https://registry-api.tscircuit.com/package_files/view?package_release_id=a44a026d-333f-490a-89c7-b4ae57b8387a\u0026file_path=dist%2Findex%2Fpcb.svg","latest_sch_preview_image_url":"https://registry-api.tscircuit.com/package_files/view?package_release_id=a44a026d-333f-490a-89c7-b4ae57b8387a\u0026file_path=dist%2Findex%2Fschematic.svg","latest_cad_preview_image_url":"https://registry-api.tscircuit.com/package_files/download?package_release_id=a44a026d-333f-490a-89c7-b4ae57b8387a\u0026file_path=dist%2Findex%2F3d.png","latest_version":"1.0.13","license":"unset","owner_org_id":"e4b51b30-273d-4e29-92ed-981078150dff","org_owner_tscircuit_handle":"ShiboSoftwareDev","owner_github_username":"ShiboSoftwareDev","github_repo_full_name":null,"is_source_from_github":false,"description":null,"name":"ShiboSoftwareDev/clip-on-battery-meter-2","unscoped_name":"clip-on-battery-meter-2","star_count":0,"created_at":"2026-04-07T12:04:56.985Z","updated_at":"2026-04-26T12:12:34.734Z","ai_description":"The code defines virtual representations of various electronic components—such as a BAT54WS Schottky diode and LM339 and LM393 voltage comparators—with physical pin configurations, footprints, and 3D models for PCB design.","ai_usage_instructions":null,"is_private":false,"is_unlisted":false,"is_public":true,"public_dist_enabled":false,"has_public_dist":true,"starred_at":null,"default_view":"files","website":null,"github_installation_id":null,"allow_pr_previews":false,"related_type":"same_author","thumbnail_url":"https://registry-api.tscircuit.com/package_files/view?package_release_id=a44a026d-333f-490a-89c7-b4ae57b8387a\u0026file_path=dist%2Findex%2Fpcb.svg"},{"package_id":"f0155dc4-d5c4-4924-b98c-5be0ca6b6bb6","creator_account_id":"97843ce3-26e5-402d-91aa-1f68763ad2dc","latest_package_release_id":"66c53bd4-317b-4137-8137-8b2a26c6547f","last_built_release_id":"66c53bd4-317b-4137-8137-8b2a26c6547f","latest_package_release_fs_sha":"md5-91af0ff278d6de309badac525a13c70f","latest_pcb_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=66c53bd4-317b-4137-8137-8b2a26c6547f\u0026file_path=dist%2Findex%2Fpcb.svg","latest_sch_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=66c53bd4-317b-4137-8137-8b2a26c6547f\u0026file_path=dist%2Findex%2Fschematic.svg","latest_cad_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=66c53bd4-317b-4137-8137-8b2a26c6547f\u0026file_path=dist%2Findex%2F3d.png","latest_version":"0.1.5","license":"MIT","owner_org_id":"e6032e13-003f-41f1-b84f-6adfc66f838b","org_owner_tscircuit_handle":"gokul","owner_github_username":null,"github_repo_full_name":null,"is_source_from_github":false,"description":"Engineering draft: four-layer, computer-powered USB 3 hub with one USB-C host socket, two USB-C and two USB-A accessory sockets, and a full-size SD slot. Board and three schematic previews are available. Routing and production checks are incomplete; not fabrication ready.","name":"gokul/usb3-sd-hub","unscoped_name":"usb3-sd-hub","star_count":0,"created_at":"2026-10-11T02:10:47.315Z","updated_at":"2026-10-16T08:50:19.606Z","ai_description":"Four-layer, computer-powered USB 3 hub PCB built around USB5816C/USB2642 controllers, providing one USB-C host, two USB-C and two USB-A peripheral ports, full-size SD storage, regulated 1.2/3.3 V power, per-port current limiting, ESD protection, and high-speed differential routing.","ai_usage_instructions":null,"is_private":false,"is_unlisted":false,"is_public":true,"public_dist_enabled":false,"has_public_dist":true,"starred_at":null,"default_view":"files","website":null,"github_installation_id":null,"allow_pr_previews":false,"related_type":"similar_ai_description","thumbnail_url":"https://api.tscircuit.com/package_files/view?package_release_id=66c53bd4-317b-4137-8137-8b2a26c6547f\u0026file_path=dist%2Findex%2Fpcb.svg"},{"package_id":"4526141e-6f81-4c10-9cec-4d2551d159a1","creator_account_id":"9f398687-69d0-4ef4-a48c-bb6dc990e7df","latest_package_release_id":"57387f49-bd91-4277-8a82-706b1575c5ed","last_built_release_id":"9256f5d4-aa2c-4b42-9939-aec937743978","latest_package_release_fs_sha":"md5-39bf50c27b23653dc748b2f8c6a5c1eb","latest_pcb_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=57387f49-bd91-4277-8a82-706b1575c5ed\u0026file_path=dist%2Fboard%2Fpcb.svg","latest_sch_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=57387f49-bd91-4277-8a82-706b1575c5ed\u0026file_path=dist%2Fboard%2Fschematic.svg","latest_cad_preview_image_url":"https://api.tscircuit.com/package_files/view?package_release_id=57387f49-bd91-4277-8a82-706b1575c5ed\u0026file_path=dist%2Fboard%2F3d.png","latest_version":"1.1.6","license":"unset","owner_org_id":"7ca3101d-0ece-4182-a775-d3aee7d7f0a0","org_owner_tscircuit_handle":"astra","owner_github_username":null,"github_repo_full_name":null,"is_source_from_github":false,"description":"USB-PD Power Supply with RP2040 controller","name":"astra/pd-power-supply","unscoped_name":"pd-power-supply","star_count":1,"created_at":"2026-09-28T19:51:44.553Z","updated_at":"2026-10-07T19:01:48.739Z","ai_description":"A USB-PD power supply featuring an RP2040 microcontroller and 3D CAD models for its hardware components.","ai_usage_instructions":null,"is_private":false,"is_unlisted":false,"is_public":true,"public_dist_enabled":false,"has_public_dist":true,"starred_at":null,"default_view":"files","website":null,"github_installation_id":null,"allow_pr_previews":false,"related_type":"random","thumbnail_url":"https://api.tscircuit.com/package_files/view?package_release_id=57387f49-bd91-4277-8a82-706b1575c5ed\u0026file_path=dist%2Fboard%2Fpcb.svg"}]; window.SSR_PACKAGE_ROUTE = {"author":"shibosoftwaredev","packageName":"f1c100s-linux-nema17-stepper-controller","packageNameWithScope":"shibosoftwaredev/f1c100s-linux-nema17-stepper-controller","version":null,"isLegacyRoute":false,"kind":"file","filePath":"scripts/validate-bom.mjs"};

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/validate-bom.mjs

import fs from "node:fs"
import crypto from "node:crypto"
import assert from "node:assert/strict"

export function parseCatalogValue(value, unit) {
  if (typeof value !== "string") return null
  const match = value.replaceAll("μ", "u").replaceAll("µ", "u").replaceAll("Ω", "Ω")
    .match(new RegExp(`^([0-9]+(?:\\.[0-9]+)?)\\s*([pnumkKM]?)${unit}$`))
  if (!match) return null
  return Number(match[1]) * ({p:1e-12,n:1e-9,u:1e-6,m:1e-3,k:1e3,K:1e3,M:1e6,"":1}[match[2]])
}

export function validateCatalogPart(source, catalog) {
  const errors = []
  const attributes = catalog.attributes ?? {}
  for (const [field, key, unit] of [["resistance","Resistance","Ω"],["capacitance","Capacitance","F"],["inductance","Inductance","H"]]) {
    if (source[field] === undefined) continue
    const actual = parseCatalogValue(attributes[key], unit)
    if (actual === null || Math.abs(actual-source[field]) > Math.max(1e-15, Math.abs(source[field])*1e-8))
      errors.push(`${key}: design ${source[field]}, catalog ${attributes[key] ?? "unverified"}`)
  }
  if (source.max_voltage_rating !== undefined && source.ftype === "simple_capacitor") {
    const actual = parseCatalogValue(attributes["Voltage Rating"], "V")
    if (actual === null || actual < source.max_voltage_rating)
      errors.push(`Voltage Rating: design ${source.max_voltage_rating}, catalog ${attributes["Voltage Rating"] ?? "unverified"}`)
  }
  if (source.manufacturer_part_number && source.manufacturer_part_number !== catalog.manufacturerPartNumber)
    errors.push(`MPN: design ${source.manufacturer_part_number}, catalog ${catalog.manufacturerPartNumber}`)
  return errors
}

if (import.meta.main) {
  const bytes = fs.readFileSync("dist/index/circuit.json")
  const circuit = JSON.parse(bytes), stock = JSON.parse(fs.readFileSync("reports/jlcpcb-stock-audit.json", "utf8"))
  const sha256 = crypto.createHash("sha256").update(bytes).digest("hex")
  assert.equal(stock.circuitSha256, sha256, "stock report must match actual circuit")
  const components = new Map(circuit.filter(e => e.type === "source_component").map(e => [e.name,e]))
  const findings = []
  let checked = 0
  for (const part of stock.parts) for (const name of part.designators) {
    const source = components.get(name)
    assert.ok(source, `catalog designator ${name} exists`)
    assert.equal(source.supplier_part_numbers?.jlcpcb?.[0], part.code)
    checked++
    const errors = validateCatalogPart(source, part)
    if (errors.length) findings.push({name,code:part.code,errors})
  }
  const rx = ["C_ETH_RXN_AC","C_ETH_RXP_AC"]
  for (const name of rx) {
    const part = stock.parts.find(p => p.designators.includes(name))
    if (part?.package !== "0402") findings.push({name,errors:["RX capacitor requires verified 0402 catalog package"]})
  }
  assert.equal(checked, stock.populatedComponentCount)
  const report = {checkedAt:new Date().toISOString(),circuitSha256:sha256,populatedChecked:checked,
    findings,scope:"All populated catalog codes/declared MPNs; R/C/L nominal values; declared capacitor voltage ratings; RX capacitor package. Other package/pin/polarity verification remains in verify.mjs and mechanical inspection."}
  fs.writeFileSync("reports/bom-validation.json",JSON.stringify(report,null,2)+"\n")
  console.log(JSON.stringify(report,null,2))
  if (findings.length) process.exitCode=1
}