app
Base Scaffold Control Panel
Public Made by Adomby adom
The Base Scaffold System's control panel: electrical specifications and the connect/disconnect interface between a base scaffold and the workcell ecosystem.
← Commit history
workcell viewer: per-component hover descriptions (user-oriented; CM5 kept system-level), Descriptions toggle
1 file changed
+132
README.html+132@@ -106,6 +106,138 @@ else { g.onclick = () => { groundOn = !groundOn; viewer.setGroundVisible(groundOn); paint(); }; l.onclick = () => { viewer.setBottomLight(!viewer.getBottomLightState()); paint(); }; paint(); ctl.style.display = 'flex';+ // ---- block hover descriptions (workcell) ----+ try {+ const scene = viewer.getScene();+ const tip = document.createElement('div');+ tip.style.cssText = 'position:absolute;display:none;z-index:20;pointer-events:none;' ++ 'background:rgba(22,27,34,.96);border:1px solid rgba(240,246,252,.15);border-radius:8px;' ++ 'padding:8px 11px;max-width:300px;color:#e6edf3;font-size:12.5px;line-height:1.45;' ++ "font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif";+ const tipT = document.createElement('div'); tipT.style.fontWeight = '600';+ const tipB = document.createElement('div'); tipB.style.color = '#9aa4b2'; tipB.style.marginTop = '2px';+ tip.appendChild(tipT); tip.appendChild(tipB);+ document.body.appendChild(tip);+ let tipsOn = true;+ const dBtn = document.createElement('button');+ dBtn.type = 'button'; dBtn.textContent = 'Descriptions'; dBtn.className = 'on';+ ctl.appendChild(dBtn);+ dBtn.onclick = () => { tipsOn = !tipsOn; dBtn.classList.toggle('on', tipsOn); if (!tipsOn) tip.style.display = 'none'; };++ const HDR = {+ pwr: 'Supply inputs (PS1\u2013PS4)',+ meter: 'Rail metering',+ usb: 'USB ports',+ wifi: 'M.2 Wi-Fi slot',+ cm5: 'Workcell controller',+ ring: 'ADOM LED ring',+ sens: 'Sensing',+ probe: 'PiProbe (N1)',+ dock: 'ET60T dock',+ led: 'Status LEDs',+ jmp: 'Ground bonds',+ board: 'Control Panel: Workcell',+ };+ const T = {+ pwr: [HDR.pwr, 'Bench supplies land on WAGO terminals: PS1 0\u201360 V / 12 A (variable), PS2 5 V / 10 A (switched), PS3 user rail, PS4 internal 5 V. Every rail is metered.'],+ meter: [HDR.meter, 'INA228 20-bit monitors \u2014 one per rail \u2014 feed live V / A / W to the Control Panel Dashboard.'],+ ps2sw: [HDR.pwr, 'TPS22990 load switch \u2014 the PS2 rail enable, 3 ms controlled ramp. Switching PS2 off also drops USB VBUS.'],+ usba: [HDR.usb, 'USB-A 3.0 SuperSpeed host, power delivery; VBUS from PS2. Its USB 2.0 lanes are muxed with Bluetooth \u2014 mutually exclusive.'],+ usbc: [HDR.usb, 'USB-C 3.0 SuperSpeed host (G-Switch GT-USB-7052), power delivery 3.0 A; VBUS from PS2.'],+ mux: [HDR.usb, 'TMUXHS221 high-speed mux \u2014 routes the shared USB 2.0 lane to Bluetooth or USB-A.'],+ efuse: [HDR.usb, 'TPS25940 smart eFuse \u2014 protected VBUS path (5 A limit, back-feed block).'],+ cc: [HDR.usb, 'TUSB321 Type-C CC controller.'],+ esd: [HDR.usb, 'USB ESD protection.'],+ wifi: [HDR.wifi, 'M.2 Key-E slot for the AX210-class module: Wi-Fi 6E (2.4 / 5 / 6 GHz, 160 MHz, 2\u00d72 MIMO to ~2.4 Gbps) + Bluetooth 5.3. Wi-Fi on PCIe, Bluetooth on USB.'],+ cm5: [HDR.cm5, 'Raspberry Pi CM5 (system side) \u2014 runs metering, switching and the Control Panel Dashboard. No user wiring lands here.'],+ sd: [HDR.cm5, 'micro-SD \u2014 CM5 storage.'],+ net: [HDR.cm5, 'RJ45 \u2014 workcell network uplink (system side).'],+ ring: [HDR.ring, '18 blue LEDs (0402, C130724) around the logo \u2014 verification for vision-system reliability. The chase animation mirrors the real behavior.'],+ bme: [HDR.sens, 'BME690 environmental sensor slot \u2014 temperature \u221240..+85 \u00b0C, 0\u2013100 %RH, 300\u20131100 hPa, gas/IAQ.'],+ bmi: [HDR.sens, 'BMI270 inertial sensor slot \u2014 accel \u00b12\u201316 g, gyro \u00b1125\u20132000 \u00b0/s.'],+ mic: [HDR.sens, 'MEMS microphone module (N3) \u2014 IM72D128 on I2S, acoustic capture for the dashboard.'],+ sj: [HDR.sens, 'Sensor jumper \u2014 drops in to connect a sensor slot.'],+ probe: [HDR.probe, 'Onboard debug probe \u2014 flashes and halts the scaffold\u2019s user MCU over SWD (stock openocd / picotool).'],+ dock: [HDR.dock, 'Delivers PS1\u2013PS3 + returns, USB 2.0, SWD/UART debug and MCU boot/reset across the blade dock to the scaffold.'],+ ledR: [HDR.led, 'Red status indicator (0402, C130719).'],+ ledG: [HDR.led, 'Green status indicator (0402, C130723).'],+ jmp: [HDR.jmp, 'Ground-bond jumper.'],+ mcL: [HDR.dock, 'Large machine contact \u2014 ~20.8 A (80% derated), mates 14 AWG solid core.'],+ mc: [HDR.board, 'Machine contact \u2014 medium ~6.4 A, solderless press-fit.'],+ sw: [HDR.board, 'SW1 \u2014 service button (SKRPACE010).'],+ ghost: ['Control Panel: Scaffold (ghost)', 'The scaffold half shown mated below \u2014 user power + I/O dock.'],+ board: [HDR.board, 'CM5 carrier and smart power-distribution hub \u2014 the system half of the control panel.'],+ };+ const NAME_RULES = [+ [/^BMEH/, 'bme'], [/^BMIH/, 'bmi'],+ [/^N3$/, 'mic'], [/^N1$/, 'probe'],+ [/^SJ\d/, 'sj'],+ [/^(D2|D23)($|_glow|_cone)/, 'ledR'],+ [/^(D3|D4)($|_glow|_cone)/, 'ledG'],+ [/^(D\d+)($|_glow|_cone)/, 'ring'],+ [/^(U1|U2|U3|U4)$/, 'meter'],+ [/^U7$/, 'ps2sw'], [/^U13$/, 'mux'], [/^U17$/, 'efuse'], [/^U18$/, 'cc'],+ [/^(U8|U9|U10|U11|U19|U20|U22|U23|U24)$/, 'esd'],+ [/^J9$/, 'usba'], [/^J10$/, 'usbc'], [/^J8$/, 'wifi'],+ [/^(J4|J7)$/, 'cm5'], [/^J5$/, 'sd'], [/^J6$/, 'net'],+ [/^(J1|J3)$/, 'pwr'],+ [/^SW1$/, 'sw'],+ [/^(JP\d|JMP|Adom_Jumper|GNDSPACER)/, 'jmp'],+ [/^MpinContact_Large/, 'mcL'],+ [/^(WIRE_MC|MC\d|MpinContact)/, 'mc'],+ [/^(ETT|ETS)/, 'dock'],+ [/^GHOST/, 'ghost'],+ ];+ const REGIONS = [+ [0.163, 0.184, -0.105, -0.087, 'wifi'],+ [0.138, 0.159, -0.126, -0.111, 'usba'],+ [0.159, 0.177, -0.126, -0.111, 'usbc'],+ [0.105, 0.162, -0.093, -0.070, 'cm5'],+ [0.060, 0.0755, -0.1015, -0.086, 'ring'],+ [0.094, 0.108, -0.092, -0.077, 'mic'],+ [0.018, 0.051, -0.087, -0.071, 'meter'],+ [0.040, 0.090, -0.076, -0.050, 'pwr'],+ [0.246, 0.267, -0.175, -0.157, 'probe'],+ ];+ const inv = new window.BABYLON.Matrix();+ const root = scene.transformNodes.concat(scene.meshes).find(n => (n.name||'').indexOf('control-panel_workcell') === 0);+ function describe(pick) {+ let n = pick.pickedMesh; const names = [];+ while (n) { names.push(n.name || ''); n = n.parent; }+ for (const [re, key] of NAME_RULES) {+ if (names.some(nm => re.test(nm))) return T[key];+ }+ if (root && pick.pickedPoint) {+ root.getWorldMatrix().invertToRef(inv);+ const p = window.BABYLON.Vector3.TransformCoordinates(pick.pickedPoint, inv);+ for (const [x0, x1, y0, y1, key] of REGIONS) {+ if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1) return T[key];+ }+ if (p.y < -0.106) return T.dock;+ return T.board;+ }+ return null;+ }+ let raf = 0;+ wrap.addEventListener('pointermove', (ev) => {+ if (!tipsOn || raf) return;+ raf = requestAnimationFrame(() => {+ raf = 0;+ if (!tipsOn) return;+ const pick = scene.pick(scene.pointerX, scene.pointerY, m => m.isEnabled() && (m.name||'') !== 'ground');+ const d = pick && pick.hit ? describe(pick) : null;+ if (d) {+ tipT.textContent = d[0]; tipB.textContent = d[1];+ tip.style.display = 'block';+ const r = wrap.getBoundingClientRect();+ tip.style.left = Math.min(ev.clientX - r.left + 14, r.width - 310) + 'px';+ tip.style.top = Math.max(ev.clientY - r.top - 10, 46) + 'px';+ } else tip.style.display = 'none';+ });+ });+ wrap.addEventListener('pointerleave', () => { tip.style.display = 'none'; });+ } catch (e) {}+ addEventListener('message', (e) => { const d = e.data || {}; if (d.type !== 'cp-cam' || (d.board && d.board !== 'workcell')) return;