muse/esp32-eink-display
A 55×46 mm two-layer ESP32-S3 e-paper controller board with USB‑C power/programming, buck regulation, FPC display connector, UC8179-style boost/charge-pump circuitry, ESD protection, controls, and routed passive components.
- Version
- 1.3.4
- License
- unset
- Stars
- 0
scripts/verify.mjs
import assert from "node:assert/strict"
import {readFileSync,writeFileSync} from "node:fs"
const read = path => readFileSync(new URL(path,import.meta.url),"utf8")
const circuit = JSON.parse(read("../dist/index/circuit.json"))
const elements = type => circuit.filter(e => e.type === type)
const parent = new Map()
const root = id => {if(!parent.has(id)) parent.set(id,id); if(parent.get(id)!==id) parent.set(id,root(parent.get(id))); return parent.get(id)}
const join = ids => ids.slice(1).forEach(id=>parent.set(root(id),root(ids[0])))
for(const e of elements("source_trace")) join([...e.connected_source_port_ids,...e.connected_source_net_ids])
for(const e of elements("source_component_internal_connection")) join(e.source_port_ids)
const comp = name => {const e=elements("source_component").find(e=>e.name===name);assert(e,`Component ${name}`);return e}
const port = (name,pin) => {
const candidates=elements("source_port").filter(e=>e.source_component_id===comp(name).source_component_id&&e.pin_number===pin)
assert.equal(candidates.length,1,`Unique ${name}.${pin}`)
return candidates[0].source_port_id
}
const net = name => {const e=elements("source_net").find(e=>e.name===name);assert(e,`Net ${name}`);return e.source_net_id}
const connected = (name,pin,rail) => assert.equal(root(port(name,pin)),root(net(rail)),`${name}.${pin} -> ${rail}`)
const checks=[]
const check=(name,fn)=>{fn();checks.push(name)}
check("SDK SPI GPIO mapping to raw-panel FPC",()=>{
for(const [fpc,header,name] of [[9,21,"EPD_BUSY"],[10,20,"EPD_RST"],[11,19,"EPD_DC"],[12,18,"EPD_CS"],[13,7,"EPD_SCLK"],[14,17,"EPD_MOSI"]]){
connected("J3",fpc,name);connected("U1",header,name)
}
})
check("3.3 V supply, VDDIO/VCI and four-wire BS1 strap",()=>{
for(const [name,pin]of [["U1",2],["J3",15],["J3",16],["C1",1],["C4",1],["C8",1],["L1",1],["R3",1]])connected(name,pin,"V3V3")
for(const [name,pin]of [["U1",1],["U1",40],["U1",41],["J3",8],["J3",17]])connected(name,pin,"GND")
})
check("AO3400A physical gate/source/drain pin assignment",()=>{
connected("Q1",1,"GDR");connected("Q1",2,"RESE");connected("Q1",3,"BOOST_SW")
connected("J3",2,"GDR");connected("J3",3,"RESE")
connected("R1",1,"GDR");connected("R1",2,"GND")
connected("R2",1,"RESE");connected("R2",2,"GND");connected("L1",2,"BOOST_SW")
assert.equal(comp("Q1").channel_type,"n");assert.equal(comp("Q1").mosfet_mode,"enhancement")
assert.equal(comp("L1").inductance,"10uH");assert(Math.abs(comp("R2").resistance-0.47)<1e-12)
})
check("Reference boost/negative pump diode polarities",()=>{
// Imported MBR0530T1G physical pin1 cathode, pin2 anode.
for(const [name,anode,cathode]of [["D3","BOOST_SW","PREVGH"],["D2","NEG_PUMP","GND"],["D1","PREVGL","NEG_PUMP"]]){
connected(name,2,anode);connected(name,1,cathode)
}
connected("C3",1,"BOOST_SW");connected("C3",2,"NEG_PUMP")
connected("J3",21,"PREVGH");connected("J3",23,"PREVGL")
})
check("Every UC8179 bias rail has the reference capacitor",()=>{
for(const [name,pin,rail,cap]of [["C2",5,"VSHR",1e-6],["C6",18,"VDD",1e-6],["C7",19,"VPP",1e-6],["C9",20,"VSH1",10e-6],["C5",21,"PREVGH",10e-6],["C10",22,"VSL",10e-6],["C11",23,"PREVGL",10e-6],["C12",24,"VCOM",1e-6]]){
connected(name,1,rail);connected(name,2,"GND");connected("J3",pin,rail);assert(Math.abs(comp(name).capacitance-cap)<1e-12)
}
})
check("All e-paper capacitors are rated 50 V",()=>{
for(let n=1;n<=13;n++)assert.equal(comp(`C${n}`).max_voltage_rating,50)
})
check("GPIO6 pairing button, pull-up and RC filter",()=>{
for(const [name,pin]of [["U1",6],["SW1",1],["R3",2],["C13",1]])connected(name,pin,"PAIR_BUTTON")
for(const [name,pin]of [["SW1",2],["C13",2]])connected(name,pin,"GND")
assert.equal(elements("source_port").filter(e=>e.source_component_id===comp("SW1").source_component_id).length,2)
assert.notEqual(root(port("SW1",1)),root(port("SW1",2)),"Normally open switch")
})
check("No unintended logical shorts between named nets",()=>{
const nets=elements("source_net");assert(nets.length>=30)
assert.equal(new Set(nets.map(n=>root(n.source_net_id))).size,nets.length)
})
check("Unused FPC pins and mounting tabs are electrically isolated",()=>{
for(const pin of [1,4,6,7,25,26]){
const id=port("J3",pin)
assert(!elements("source_trace").some(e=>e.connected_source_port_ids.includes(id)),`J3.${pin} must remain unconnected`)
}
})
const pcbPort = (name,pin) => {const ps=elements("pcb_port").filter(e=>e.source_port_id===port(name,pin));assert.equal(ps.length,1);return ps[0]}
check("ESP32-S3 module footprint, ground and reserved PSRAM/strap pins",()=>{
assert.equal(comp("U1").manufacturer_part_number,"ESP32-S3-WROOM-1-N8R8")
for(const pin of [1,40,41])connected("U1",pin,"GND")
assert(Math.abs(pcbPort("U1",1).x-pcbPort("U1",40).x+17.500092)<1e-6)
assert(Math.abs(pcbPort("U1",1).y-pcbPort("U1",2).y-1.27)<1e-6)
for(const pin of [15,16,26,28,29,30])assert(!elements("source_trace").some(e=>e.connected_source_port_ids.includes(port("U1",pin))),`Reserved GPIO/strap U1.${pin} isolated`)
})
check("EN power-up RC, reset and boot controls",()=>{
for(const [name,pin]of [["U1",3],["R4",2],["C20",1],["SW2",1]])connected(name,pin,"ESP_EN")
for(const [name,pin]of [["U1",27],["R5",2],["SW3",1]])connected(name,pin,"ESP_BOOT")
connected("R4",1,"V3V3");connected("R5",1,"V3V3")
for(const name of ["SW2","SW3","C20"])connected(name,2,"GND")
assert.equal(comp("R4").resistance,10000);assert.equal(comp("R5").resistance,10000)
assert(Math.abs(comp("C20").capacitance-1e-6)<1e-12)
assert.equal(comp("C14").capacitance,10e-6);assert.equal(comp("C15").capacitance,100e-9)
})
check("USB-C reversible data, independent CC sink resistors and native USB GPIOs",()=>{
for(const pin of [7,9])connected("J1",pin,"USB_DM")
for(const pin of [8,10])connected("J1",pin,"USB_DP")
for(const pin of [15,16])connected("J1",pin,"V5_USB")
for(const pin of [1,2,3,4,13,14])connected("J1",pin,"GND")
connected("J1",6,"CC1");connected("J1",12,"CC2")
for(const [r,rail]of [["R6","CC1"],["R7","CC2"]]){
connected(r,1,rail);connected(r,2,"GND");assert.equal(comp(r).resistance,5100)
}
for(const [r,pin,rail]of [["R8",13,"USB_DM"],["R9",14,"USB_DP"]]){
connected(r,1,rail);connected(r,2,rail+"_MCU");connected("U1",pin,rail+"_MCU")
assert.equal(comp(r).resistance,22);assert.notEqual(root(port(r,1)),root(port(r,2)))
}
for(const pin of [1,6])connected("U3",pin,"USB_DM")
for(const pin of [3,4])connected("U3",pin,"USB_DP")
connected("U3",2,"GND");connected("U3",5,"V5_USB")
connected("C22",1,"V5_USB");connected("C22",2,"GND")
})
check("Fixed 3.3 V buck feedback, enable, bootstrap and LC filter",()=>{
for(const [pin,rail]of [[1,"V3V3"],[2,"V5_USB"],[3,"V5_USB"],[4,"GND"],[5,"BUCK_SW"],[6,"BUCK_BST"]])connected("U2",pin,rail)
connected("L2",1,"BUCK_SW");connected("L2",2,"V3V3");assert.equal(comp("L2").inductance,"3.9uH")
connected("C17",1,"BUCK_BST");connected("C17",2,"BUCK_SW")
assert.equal(comp("C17").capacitance,100e-9)
connected("C16",1,"V5_USB");connected("C16",2,"GND");assert.equal(comp("C16").capacitance,10e-6)
for(const name of ["C18","C19"]){connected(name,1,"V3V3");connected(name,2,"GND");assert.equal(comp(name).capacitance,22e-6)}
})
check("Imported 24-contact FPC pitch and top-layer escape",()=>{
for(let pin=2;pin<=24;pin++)assert(Math.abs(pcbPort("J3",pin).x-pcbPort("J3",pin-1).x-0.5)<0.003)
const fanouts=elements("pcb_trace").filter(t=>t.route.length===2&&t.route.every(p=>p.route_type==="wire"&&p.layer==="top")&&Math.abs(t.route[0].y+17.70906505)<1e-6&&Math.abs(t.route[1].y+15.7)<1e-6)
assert.equal(fanouts.length,20,"20 connected FPC pins have saved fanout geometry")
})
check("Every placed component has an imported MPN and supplier ID",()=>{
const manifest=JSON.parse(read("../imports/manifest.json"))
assert.equal(manifest.parts.length,20)
for(const entry of manifest.parts){
const source=read(`../${entry.file}`);assert(source.includes('<footprint'));assert(source.includes(entry.lcsc));assert(!source.includes('footprint="'))
for(const name of entry.references){const c=comp(name);assert.equal(c.manufacturer_part_number,entry.manufacturerPartNumber);assert(c.supplier_part_numbers.jlcpcb.includes(entry.lcsc))}
}
assert.equal(elements("pcb_component").length,44)
})
check("Two M2.5 mounts and imported USB locating holes",()=>{assert.equal(elements("pcb_hole").length,4);assert.equal(elements("pcb_plated_hole").length,4)})
check("All routed wire widths at least 0.15 mm",()=>{
assert(elements("pcb_trace").length>40)
for(const t of elements("pcb_trace"))for(const p of t.route)if(p.route_type==="wire")assert(p.width>=0.15-1e-6)
})
check("Copper routing stays on the compact board and below the overhanging antenna",()=>{
const inside=p=>p.x < -27.5 || p.x > 27.5 || p.y < -23 || p.y > 23
for(const t of elements("pcb_trace"))for(let i=0;i<t.route.length;i++){
const a=t.route[i];assert(!inside(a))
if(!i)continue;const b=t.route[i-1];const steps=Math.ceil(Math.hypot(a.x-b.x,a.y-b.y)/0.1)
for(let s=1;s<steps;s++)assert(!inside({x:b.x+(a.x-b.x)*s/steps,y:b.y+(a.y-b.y)*s/steps}))
}
})
check("Zero circuit JSON errors and warnings; no disabled DRC checks",()=>{
assert.deepEqual(circuit.filter(e=>e.type.endsWith("_error")||e.type.endsWith("_warning")),[])
assert(!/drcChecksDisabled|placementDrcChecksDisabled|routingDisabled|schematicDisabled/.test(read("../index.circuit.tsx")))
})
check("Two-layer board and supported minimum manufacturing dimensions",()=>{
const board=elements("pcb_board")[0];assert.equal(board.num_layers,2)
assert.equal(board.thickness,1.6);assert.equal(board.min_trace_width,0.15)
assert.equal(board.min_via_hole_diameter,0.3);assert.equal(board.min_via_pad_diameter,0.5)
assert(elements("pcb_via").length>0)
for(const v of elements("pcb_via")){assert(Math.abs(v.hole_diameter-0.3)<1e-9,"Every via drill is 0.3 mm");assert(v.outer_diameter>=0.5-1e-9)}
})
check("55 × 46 mm outline, unchanged real package sizes and antenna overhang",()=>{
const b=elements("pcb_board")[0];assert.equal(b.width,55);assert.equal(b.height,46)
const u=elements("pcb_component").find(e=>e.source_component_id===comp("U1").source_component_id)
assert(Math.abs(u.width-19.000089)<1e-6);assert(Math.abs(u.height-18.9899036)<1e-6)
assert(u.center.y+10.5053511>23,"Antenna starts beyond PCB edge")
assert(pcbPort("U1",1).y+0.45+0.3<23,"Castellated pads retain edge clearance")
const mounts=elements("pcb_hole").filter(h=>h.pcb_component_id==null);assert.equal(mounts.length,2)
for(const h of mounts)assert.equal(h.hole_diameter,2.7)
})
check("Latest installed toolchain and built-in autorouter pipeline",()=>{
const pkg=JSON.parse(read("../package.json"))
assert.equal(pkg.devDependencies.tscircuit,"0.0.2742")
assert.equal(pkg.devDependencies["@tscircuit/capacity-autorouter"],"0.0.953")
const src=read("../index.circuit.tsx");assert(src.includes('preset:"auto_local"'));assert(src.includes('algorithmFn={createMuseRouter}'));assert(read('../routing.ts').includes('new AutoroutingPipelineSolver(input)'))
})
check("Firmware target, octal PSRAM, flash and button overlay",()=>{
const overlay=read("../firmware/sdkconfig.muse-eink")
for(const line of ['CONFIG_IDF_TARGET="esp32s3"','CONFIG_HOMEHUB_BUTTON_GPIO=6','CONFIG_SPIRAM_MODE_OCT=y','CONFIG_ESPTOOLPY_FLASHSIZE="8MB"','CONFIG_HOMEHUB_LED_BACKEND_RETERMINAL_UC8179=y','CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y'])assert(overlay.includes(line))
})
const report={checkedAt:new Date().toISOString(),revision:"D",result:"PASS",checks,placedComponents:elements("pcb_component").length,importedPackages:20,pcbTraces:elements("pcb_trace").length,boardSizeMm:{width:55,height:46},viaDrillDiameterMm:0.3,viaCopperDiameterMm:0.5,autorouter:"builtin @tscircuit/capacity-autorouter 0.0.953",drcErrors:0,drcWarnings:0,limitations:["Firmware not built: ESP-IDF unavailable","No hardware or enclosure fit testing","USB impedance requires fabricator confirmation against the chosen stackup","Raw-panel stock and lead time unconfirmed"]}
writeFileSync(new URL("../dist/validation.json",import.meta.url),JSON.stringify(report,null,2)+"\n")
console.log(`PASS: ${checks.length} checks; ${report.placedComponents} imported components; ${report.pcbTraces} traces; zero DRC errors/warnings.`)
