equipment
Electronic Load Molecule
Public Made by Adomby adom
Siglent SDL1020X-E electronic load equipment prototype: reconstructed carrier STEP/GLB, constant-current dashboard, serial-pinned SCPI driver, control-panel integration guide and purchase budget.
main
John Lauer
Add electronic load equipment prototype, carrier reconstruction and control dashboard
d1c6e4f
1d ago
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Electronic Load Molecule</title><link rel="icon" href="favicon.svg"><style>
:root{color-scheme:dark;--bg:#0d1117;--panel:#161b22;--line:#303b46;--text:#e6edf3;--muted:#96a5b4;--teal:#00b8b1}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:15px system-ui,sans-serif}header{height:44px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 22px;background:var(--panel)}header b{letter-spacing:.06em}main{max-width:1440px;margin:auto;padding:28px}.eyebrow{color:var(--teal);font-size:12px;letter-spacing:.15em;text-transform:uppercase}h1{font-size:34px;margin:8px 0}p{color:var(--muted);line-height:1.6}h2{font-size:17px;margin:0 0 16px}.grid{display:grid;grid-template-columns:1.35fr 1fr;gap:20px}.card{border:1px solid var(--line);background:var(--panel);border-radius:12px;padding:22px}.badge{font:12px ui-monospace,monospace;padding:6px 10px;border:1px solid #75672a;color:#efd376;border-radius:20px}.values{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:20px 0}.values b{display:block;font:30px ui-monospace,monospace;margin-top:7px}.values span{font-size:12px;color:var(--muted)}button,input{font:inherit;border:1px solid #435360;border-radius:6px;padding:10px 14px;background:#202a35;color:var(--text)}button{cursor:pointer}button:hover{border-color:var(--teal)}button:disabled{opacity:.35;cursor:not-allowed}button.primary{background:var(--teal);color:#071414;font-weight:650}button.stop{background:#762d30;border-color:#d86464}input{width:110px}.controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.presets{display:flex;gap:8px;margin:14px 0}small,.muted{color:var(--muted)}a{color:#4bd0c8}canvas{width:100%;display:block}#model{height:370px;touch-action:none;background:radial-gradient(ellipse at center,#253442,#0d1117);border-radius:8px}#chart{height:150px}.row{display:flex;justify-content:space-between;gap:14px;margin:14px 0;font-size:13px}.flow{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-top:20px}.flow div{border:1px solid var(--line);padding:16px;border-radius:8px}.flow b{display:block;margin-bottom:8px}.flow small{line-height:1.6;display:block}#toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);padding:12px 22px;background:#164945;border:1px solid var(--teal);border-radius:8px;max-width:90vw;display:none}#fault{color:#ff9999;min-height:22px}#identity{overflow-wrap:anywhere}footer{margin-top:20px;color:var(--muted);font-size:12px}@media(max-width:850px){.grid{grid-template-columns:1fr}.flow{grid-template-columns:1fr 1fr}main{padding:16px}h1{font-size:26px}.values b{font-size:24px}}
</style><header><b>ADOM / ELECTRONIC LOAD</b><span id="mode" class="badge">CONNECTING</span><a href="https://wiki.adom.inc/john/buck-12v5v-molecule" target="_blank">Buck molecule</a></header><main><div class="eyebrow">Probing workcell equipment</div><h1>A programmable load for the buck molecule</h1><p>Siglent SDL1020X-E · Constant current · 5 V / 1 A DUT profile</p><div class="grid"><section class="card"><div class="row"><h2>Instrument + molecule carrier</h2><span class="badge">FIT UNVERIFIED</span></div><canvas id="model" aria-label="Interactive reconstructed electronic load and carrier"></canvas><div class="controls" style="margin-top:12px"><button id="orbit" title="Rotate the generated model continuously">Orbit</button><button id="explode" title="Separate instrument from carrier for inspection">Explode</button><button id="home" title="Return to the initial isometric view">Reset view</button><a href="model.step">STEP</a><a href="model.glb">GLB</a></div><div class="row"><span>256 × 410 × 115 mm instrument</span><span>5.8 kg</span></div><small>Drag to rotate. Manufacturer dimensions; reconstructed details. Carrier and support interfaces require fit and load verification.</small></section><section class="card"><h2>Load control</h2><small id="identity">No instrument connected</small><div class="values"><div><span>DUT VOLTAGE</span><b id="volts">—</b></div><div><span>LOAD CURRENT</span><b id="amps">—</b></div><div><span>ABSORBED POWER</span><b id="watts">—</b></div></div><div class="row"><b id="inputState">INPUT OFF</b><span>Profile ceiling: 1.000 A</span></div><label for="setpoint">Constant current (A)</label><div class="controls" style="margin-top:10px"><input id="setpoint" type="number" min="0" max="1" step="0.001" value="0.1"><button id="apply" title="Set and verify the constant-current demand">Apply</button><button id="on" class="primary" title="Enable the load at the current setpoint">Load ON</button><button id="off" class="stop" title="Disable the instrument input and confirm readback">Load OFF</button></div><div class="presets"><button data-a="0.1" title="Set a 100 mA load">0.10 A</button><button data-a="0.5" title="Set a 500 mA load">0.50 A</button><button data-a="1" title="Set the buck's 1 A rated load">1.00 A</button></div><div id="fault" role="alert"></div><canvas id="chart" aria-label="Recent voltage and current trend"></canvas><div class="row"><span>Teal: volts / 6 · Amber: amps / 1.2</span><a href="api/export.csv" download="load-measurements.csv">Export CSV</a></div><small>Readback trend, not oscilloscope acquisition. Closing this page expires its 10 s control lease. A broken hardware link can leave the input state unknown.</small></section></div><div class="flow"><div><b>01 / Power the DUT</b><small>12 V source → control-panel PS1 → buck VIN/GND. Confirm rail and return wiring.</small></div><div><b>02 / Apply the load</b><small>Buck J4 VOUT → load +. Buck J5 GND → load −. Keep force leads short and paired.</small></div><div><b>03 / Control & read</b><small>Workcell CM5 → USBTMC or LAN SCPI. The load has its own mains supply.</small></div><div><b>04 / Correlate</b><small id="cp">Control-panel telemetry: disconnected. Scope required for ripple and transient response.</small></div></div><footer>Planning reconstruction and software prototype. No physical load, carrier, calibration or buck test has been qualified. <a href="provenance.json">Model provenance</a></footer></main><div id="toast" role="status"></div><script type="module">
import * as THREE from "./three.js";
const $=s=>document.querySelector(s);let state={},history=[];let timer;function toast(t,bad=false){$('#toast').textContent=t;$('#toast').style.display='block';$('#toast').style.background=bad?'#642c30':'#164945';clearTimeout(timer);timer=setTimeout(()=>$('#toast').style.display='none',5000)}async function command(cmd,extra={}){try{let r=await fetch('api/cmd',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({cmd,...extra})});let j=await r.json();if(!r.ok)throw Error(j.error);if(cmd!='heartbeat')toast(cmd==='current'?`Current set to ${extra.amps.toFixed(3)} A`:`Load ${cmd.toUpperCase()}`);render(j.state)}catch(e){toast(e.message,true)}}
$('#apply').onclick=()=>command('current',{amps:Number($('#setpoint').value)});$('#on').onclick=()=>command('on');$('#off').onclick=()=>command('off');document.querySelectorAll('[data-a]').forEach(b=>b.onclick=()=>{$('#setpoint').value=b.dataset.a;command('current',{amps:Number(b.dataset.a)})});function render(s){state=s;$('#mode').textContent=s.simulated?'SIMULATION / NO HARDWARE':s.connected?'LIVE INSTRUMENT':'DISCONNECTED';$('#identity').textContent=s.identity||'Configure an instrument resource and serial to connect';$('#inputState').textContent=s.input_on?'INPUT ON':'INPUT OFF';$('#inputState').style.color=s.input_on?'#00b8b1':'#96a5b4';$('#fault').textContent=s.error||'';for(let [k,id,u] of [['voltage_v','volts','V'],['current_a','amps','A'],['power_w','watts','W']])$('#'+id).textContent=s.measurement?s.measurement[k].toFixed(3)+' '+u:'—';for(let id of ['apply','on','off'])$('#'+id).disabled=!s.connected;document.querySelectorAll('[data-a]').forEach(b=>b.disabled=!s.connected);if(s.control_panel)$('#cp').textContent=s.control_panel.connected?'Control-panel API reachable. Verify attached board identity and rail mapping before testing.':'Control-panel telemetry: disconnected. Scope required for ripple and transient response.';if(s.measurement){history.push(s.measurement);history=history.slice(-180);chart()}}
let events=new EventSource('api/stream');events.onmessage=e=>render(JSON.parse(e.data));events.onerror=()=>{$('#mode').textContent='CONNECTION LOST';for(let id of ['apply','on','off'])$('#'+id).disabled=true;$('#volts').textContent=$('#amps').textContent=$('#watts').textContent='—'};setInterval(()=>{if(state.input_on)command('heartbeat')},2000);window.addEventListener('error',e=>fetch('console',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({error:e.message})}));
function chart(){let c=$('#chart'),d=devicePixelRatio;c.width=c.clientWidth*d;c.height=c.clientHeight*d;let x=c.getContext('2d'),w=c.width,h=c.height;x.strokeStyle='#303b46';for(let i=1;i<5;i++){x.beginPath();x.moveTo(0,h*i/5);x.lineTo(w,h*i/5);x.stroke()}for(let [k,max,col] of [['voltage_v',6,'#00b8b1'],['current_a',1.2,'#efc26b']]){x.strokeStyle=col;x.lineWidth=2*d;x.beginPath();history.forEach((p,i)=>{let a=i/179*w,b=h-10-p[k]/max*(h-20);i?x.lineTo(a,b):x.moveTo(a,b)});x.stroke()}}
// Render the exact tessellated CAD solids with depth testing and consistent lighting.
const canvas=$('#model'),renderer=new THREE.WebGLRenderer({canvas,antialias:true,alpha:true});renderer.setPixelRatio(devicePixelRatio);const scene=new THREE.Scene(),camera=new THREE.PerspectiveCamera(38,1,.001,10);camera.up.set(0,0,1);scene.add(new THREE.HemisphereLight(0xddeeff,0x444444,2.2));const light=new THREE.DirectionalLight(0xffffff,3);light.position.set(-.5,-.6,1);scene.add(light);let meshes=[],az=-.8,el=.55,spin=false,explode=false,drag=null;
fetch('model.json').then(r=>r.json()).then(data=>{for(const m of data){const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(m.positions,3));g.setIndex(m.indices);g.computeVertexNormals();const color=new THREE.Color(...m.color.map(n=>n/255));const mesh=new THREE.Mesh(g,new THREE.MeshStandardMaterial({color,roughness:.65,metalness:.12,side:THREE.DoubleSide}));mesh.name=m.name;scene.add(mesh);meshes.push(mesh)}draw()});
function draw(){const w=canvas.clientWidth,h=canvas.clientHeight;renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix();let radius=.82;camera.position.set(.144+radius*Math.cos(el)*Math.sin(az),.224-radius*Math.cos(el)*Math.cos(az),.06+radius*Math.sin(el));camera.lookAt(.144,.224,.065);for(let m of meshes)m.position.z=explode&&!/carrier|support|retaining|datum/.test(m.name)?.11:0;renderer.render(scene,camera)}
canvas.onpointerdown=e=>{drag=[e.clientX,e.clientY];canvas.setPointerCapture(e.pointerId)};canvas.onpointermove=e=>{if(drag){az-=(e.clientX-drag[0])*.008;el=Math.max(.1,Math.min(1.4,el+(e.clientY-drag[1])*.006));drag=[e.clientX,e.clientY];draw()}};canvas.onpointerup=()=>drag=null;
$('#orbit').onclick=()=>{spin=!spin;$('#orbit').setAttribute('aria-pressed',spin);toast(spin?'Orbit started':'Orbit stopped')};$('#explode').onclick=()=>{explode=!explode;$('#explode').setAttribute('aria-pressed',explode);draw()};$('#home').onclick=()=>{az=-.8;el=.55;explode=false;spin=false;draw()};setInterval(()=>{if(spin){az+=.018;draw()}},50);addEventListener('resize',()=>{draw();chart()});window.eloadView={orbit:()=>$('#orbit').click(),explode:()=>$('#explode').click(),reset:()=>$('#home').click()};
</script></html>