mohan-bee/fitness-band

A 4-layer BLE wearable mainboard with Li-ion charging/protection, 3.3/1.8 V power regulation, motion and battery sensing, plus a folded 2-layer flex sensor board carrying MAX30102 optical and TMP117 temperature sensors.

Version
0.0.2
License
unset
Stars
0

verify.ts

import assert from "node:assert/strict"
import { readFileSync } from "node:fs"
import { createHash } from "node:crypto"
import type { AnyCircuitElement } from "circuit-json"

function load(board: string) {
  const kind = board === "index" ? "main" : "flex"
  const release = "manufacturing/rev-b"
  const manifest = JSON.parse(readFileSync(`${release}/release-manifest.json`, "utf8"))[kind]
  for (const [path, expected] of [
    [`${release}/${kind}/${kind}.kicad_pcb`, manifest.native_sha256],
    [manifest.circuit_json_path, manifest.circuit_json_sha256],
  ]) assert.equal(createHash("sha256").update(readFileSync(path)).digest("hex"), expected, `${kind}: frozen release file changed`)
  const elements: AnyCircuitElement[] = JSON.parse(readFileSync(manifest.circuit_json_path,"utf8"))
  if(board === "sensor-flex") assert.equal(elements.filter(e=>e.type.endsWith("_error")).length,0,`${board}: unresolved Circuit JSON errors`)
  return elements
}
function pins(elements: AnyCircuitElement[], componentName: string) {
  const component=elements.find(e=>e.type==="source_component" && e.name===componentName)
  assert(component?.type==="source_component",`Missing ${componentName}`)
  return elements.filter(e=>e.type==="source_port" && e.source_component_id===component.source_component_id)
}
function expectPin(elements: AnyCircuitElement[], component: string, number: number, netName?: string) {
  const port=pins(elements,component).find(e=>e.type==="source_port" && e.pin_number===number)
  assert(port?.type==="source_port",`Missing ${component} pin ${number}`)
  const net=elements.find(e=>e.type==="source_net" && e.name===netName)
  if(netName) {
    assert(net?.type==="source_net",`Missing net ${netName}`)
    assert.equal(port.subcircuit_connectivity_map_key,net.subcircuit_connectivity_map_key,`${component}.${number} must be ${netName}`)
  } else {
    assert(!elements.some(e=>e.type==="source_trace" && e.connected_source_port_ids.includes(port.source_port_id)),`${component}.${number} must remain NC`)
  }
}
const main=load("index"), flex=load("sensor-flex")
for(const [board,ref] of [[main,"J_FLEX"],[flex,"J_TAIL"]] as const)
  ["GND","V3V3","GND","SCL","SDA","PPG_INT","V1V8","GND"].forEach((net,i)=>expectPin(board,ref,i+1,net))
expectPin(flex,"U7",11,"V1V8")
for(const number of [9,10]) expectPin(flex,"U7",number,"V3V3")
for(const number of [1,5,6,7,8,14]) expectPin(flex,"U7",number)
expectPin(main,"U5",3,"VBAT")
expectPin(main,"U2",5,"BAT_NTC")
for(const number of [14,15,16]) expectPin(main,"U2",number,"VBAT")
for(const number of [18,19,2,3,17]) expectPin(main,"U2",number,"CHG_5V")
for(const number of [1,20]) expectPin(main,"U2",number,"VSYS")
for(const number of [4,9,10,11,21]) expectPin(main,"U2",number,"GND")
expectPin(main,"U1",28,"V3V3")
expectPin(main,"U1",30,"V3V3")
expectPin(main,"U1",32)
const board=flex.find(e=>e.type==="pcb_board")
assert(board?.type==="pcb_board" && board.material==="flex" && board.num_layers===2)
assert.equal(flex.filter(e=>e.type==="pcb_stiffener").length,3)
assert.equal(flex.filter(e=>e.type==="pcb_bend").length,1)
for(const e of flex) {
  if(e.type==="pcb_via") assert(e.x<=-11 || e.x>=-1,"Via in bend reserve")
  if(e.type==="pcb_component") assert(e.center.x+e.width/2<=-11 || e.center.x-e.width/2>=-1,"Component in bend reserve")
}
const crossings=flex.filter(e=>e.type==="pcb_trace").flatMap(e=>e.route.slice(1).flatMap((p,i)=>{
  const q=e.route[i]!
  if(q.route_type!=="wire" || p.route_type!=="wire" || Math.min(q.x,p.x)>=-1 || Math.max(q.x,p.x)<=-11) return []
  assert.equal(q.layer,"top"); assert.equal(p.layer,"top")
  assert.equal(q.y,p.y,"Bend trace must be straight and perpendicular to bend axis")
  assert.equal(p.width,0.3,"Bend trace must be 0.3 mm")
  return [p]
}))
assert.equal(crossings.length,8,"Expected exactly eight bend crossings")
for(const [kind,elements] of [["main",main],["flex",flex]] as const) {
  const report=JSON.parse(readFileSync(`validation/${kind}-native-drc.json`,"utf8"))
  assert.equal(report.violations.length,0,`${kind}: native DRC violations`)
  assert.equal(report.unconnected_items.length,0,`${kind}: native airwires`)
  const pads: {ref:string,pin:string,net:string,x:number,y:number}[]=JSON.parse(readFileSync(`validation/${kind}-native-pads.json`,"utf8"))
  const nativeNetByConnectivity=new Map<string,string>()
  let checked=0
  for(const e of elements) {
    if(e.type!=="pcb_smtpad" || !e.pcb_port_id) continue
    const port=elements.find(p=>p.type==="pcb_port" && p.pcb_port_id===e.pcb_port_id)
    assert(port?.type==="pcb_port")
    const source=elements.find(p=>p.type==="source_port" && p.source_port_id===port.source_port_id)
    assert(source?.type==="source_port")
    const component=elements.find(c=>c.type==="source_component" && c.source_component_id===source.source_component_id)
    assert(component?.type==="source_component")
    const native=pads.find(p=>p.ref===component.name && p.pin===String(source.pin_number))
    assert(native,`${component.name}.${source.pin_number} missing from native release`)
    const center="x" in e ? e : {
      x:(Math.min(...e.points.map(p=>p.x))+Math.max(...e.points.map(p=>p.x)))/2,
      y:(Math.min(...e.points.map(p=>p.y))+Math.max(...e.points.map(p=>p.y)))/2,
    }
    assert(Math.hypot(native.x-(100+center.x),native.y-(100-center.y))<0.01,`${component.name}.${source.pin_number} native pad moved`)
    const net=elements.find(n=>n.type==="source_net" && n.subcircuit_connectivity_map_key===source.subcircuit_connectivity_map_key)
    if(net?.type==="source_net") assert.equal(native.net,net.name,`${component.name}.${source.pin_number} native net mismatch`)
    if(source.subcircuit_connectivity_map_key) {
      const previous=nativeNetByConnectivity.get(source.subcircuit_connectivity_map_key)
      if(previous!==undefined) assert.equal(native.net,previous,`${component.name}.${source.pin_number} split source connectivity in native release`)
      else nativeNetByConnectivity.set(source.subcircuit_connectivity_map_key,native.net)
    }
    checked++
  }
  console.log(`PASS ${kind}: ${checked} native pad positions/named nets match tscircuit; zero native DRC violations and airwires`)
}
console.log("PASS: interconnect, charger pin map, supply domains, NC pins, flex reserves and eight straight bend traces")