saltylab-firmware/ui/gamepad_panel.html
sl-webui f71dad5344 feat(arch): migrate all STM32/Mamba/BlackPill refs to ESP32 BALANCE/IO + fix roslib@1.4.0
Architecture change (2026-04-03): Mamba F722S (STM32F722) and BlackPill
replaced by ESP32 BALANCE (PID loop) and ESP32 IO (motors/sensors/comms).

- Update CLAUDE.md, docs, chassis BOM/ASSEMBLY, pinout, power-budget,
  wiring-diagram, TEAM.md, AUTONOMOUS_ARMING.md, docker-compose
- Update all ROS2 package comments, config labels, launch args
  (stm32_port→esp32_port, /dev/stm32-bridge→/dev/esp32-bridge)
- Update WebUI: stm32Mode→esp32Mode, stm32Version→esp32Version,
  "STM32 State/Mode" labels → "ESP32 State/Mode" (ControlMode, SettingsPanel)
- Add TODO(esp32-migration) markers on stm32_protocol.py and mamba_protocol.py
  binary frame layouts — pending ESP32 protocol spec from max
- Fix roslib CDN 1.3.0→1.4.0 in all 11 HTML panels (fixes ROS2 Humble
  rosbridge "Received a message without an op" incompatibility)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 08:25:24 -04:00

198 lines
6.4 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>Saltybot — Gamepad Teleop</title>
<link rel="stylesheet" href="gamepad_panel.css">
<script src="https://cdn.jsdelivr.net/npm/roslib@1.4.0/build/roslib.min.js"></script>
</head>
<body>
<!-- ── Header ── -->
<div id="header">
<div class="logo">⚡ SALTYBOT — GAMEPAD TELEOP</div>
<div id="conn-dot"></div>
<input id="ws-input" type="text" value="ws://localhost:9090" placeholder="ws://robot-ip:9090" />
<button id="btn-connect" class="hbtn">CONNECT</button>
<span id="conn-label" style="color:#4b5563;font-size:10px">Not connected</span>
<div style="flex:1"></div>
<div id="gp-indicator">
<div id="gp-dot"></div>
<span id="gp-label">No gamepad</span>
</div>
</div>
<!-- ── Toolbar ── -->
<div id="toolbar">
<!-- Speed limiter -->
<span class="tlbl">LINEAR</span>
<input id="slider-linear" type="range" min="10" max="100" value="50" class="tslider">
<span id="val-linear" class="tval">0.50 m/s</span>
<div class="tsep"></div>
<span class="tlbl">ANGULAR</span>
<input id="slider-angular" type="range" min="10" max="100" value="50" class="tslider">
<span id="val-angular" class="tval">1.00 rad/s</span>
<div class="tsep"></div>
<span class="tlbl">DEADZONE</span>
<input id="slider-dz" type="range" min="0" max="40" value="10" class="tslider" style="width:70px">
<span id="val-dz" class="tval">10%</span>
<div class="tsep"></div>
<button id="btn-estop" class="hbtn estop-btn">⛔ E-STOP</button>
<button id="btn-resume" class="hbtn resume-btn" style="display:none">▶ RESUME</button>
</div>
<!-- ── Main ── -->
<div id="main">
<!-- Virtual joystick area -->
<div id="joystick-area">
<div class="stick-wrap" id="left-wrap">
<div class="stick-label">LEFT — DRIVE</div>
<canvas id="left-stick" width="200" height="200"></canvas>
<div class="stick-vals" id="left-vals">↕ 0.00 m/s</div>
</div>
<!-- Center info -->
<div id="center-panel">
<div class="cp-block">
<div class="cp-title">COMMAND</div>
<div class="cp-row">
<span class="cp-lbl">Linear</span>
<span class="cp-val" id="disp-linear">0.00 m/s</span>
</div>
<div class="cp-row">
<span class="cp-lbl">Angular</span>
<span class="cp-val" id="disp-angular">0.00 rad/s</span>
</div>
</div>
<div class="cp-block">
<div class="cp-title">LIMITS</div>
<div class="cp-row">
<span class="cp-lbl">Max lin</span>
<span class="cp-val" id="lim-linear">0.50 m/s</span>
</div>
<div class="cp-row">
<span class="cp-lbl">Max ang</span>
<span class="cp-val" id="lim-angular">1.00 rad/s</span>
</div>
<div class="cp-row">
<span class="cp-lbl">Deadzone</span>
<span class="cp-val" id="lim-dz">10%</span>
</div>
</div>
<div class="cp-block">
<div class="cp-title">KEYBOARD</div>
<div class="key-grid">
<div></div>
<div class="key" id="key-w">W</div>
<div></div>
<div class="key" id="key-a">A</div>
<div class="key" id="key-s">S</div>
<div class="key" id="key-d">D</div>
<div></div>
<div class="key key-wide" id="key-space">SPC</div>
<div></div>
</div>
<div style="font-size:9px;color:#4b5563;text-align:center;margin-top:4px">
W/S=fwd/rev · A/D=turn · SPC=stop
</div>
</div>
<!-- Gamepad layout diagram -->
<div class="cp-block">
<div class="cp-title">GAMEPAD MAPPING</div>
<div style="font-size:9px;color:#6b7280;line-height:1.8">
<div>L-stick ↕ → linear vel</div>
<div>R-stick ↔ → angular vel</div>
<div>LT/RT → fine speed ctrl</div>
<div>B/Circle → E-stop toggle</div>
<div>Start → Resume</div>
</div>
</div>
<div id="estop-panel" style="display:none">
<div class="estop-text">⛔ E-STOP</div>
<div class="estop-sub">ACTIVE</div>
</div>
</div>
<div class="stick-wrap" id="right-wrap">
<div class="stick-label">RIGHT — STEER</div>
<canvas id="right-stick" width="200" height="200"></canvas>
<div class="stick-vals" id="right-vals">↔ 0.00 rad/s</div>
</div>
</div>
<!-- Sidebar -->
<aside id="sidebar">
<!-- Status -->
<div class="sb-card">
<div class="sb-title">Status</div>
<div class="sb-row">
<span class="sb-lbl">E-stop</span>
<span class="sb-val" id="sb-estop" style="color:#6b7280">OFF</span>
</div>
<div class="sb-row">
<span class="sb-lbl">Input</span>
<span class="sb-val" id="sb-input"></span>
</div>
<div class="sb-row">
<span class="sb-lbl">Pub rate</span>
<span class="sb-val" id="sb-rate"></span>
</div>
<div class="sb-row">
<span class="sb-lbl">Topic</span>
<span class="sb-val" style="font-size:9px">/cmd_vel</span>
</div>
</div>
<!-- Gamepad raw -->
<div class="sb-card">
<div class="sb-title">Gamepad Raw</div>
<div id="gp-raw">
<div style="color:#374151;font-size:10px;text-align:center;padding:12px 0">
No gamepad connected.<br>Press any button to activate.
</div>
</div>
</div>
<!-- Topics -->
<div class="sb-card">
<div class="sb-title">Topics</div>
<div style="font-size:9px;color:#374151;line-height:1.9">
<div>PUB <code style="color:#4b5563">/cmd_vel</code></div>
<div style="color:#1e3a5f;padding-left:8px">geometry_msgs/Twist</div>
<div style="margin-top:4px;color:#6b7280">20 Hz publish rate</div>
<div style="margin-top:4px;color:#6b7280">Sends zero on E-stop</div>
</div>
</div>
</aside>
</div>
<!-- ── Footer ── -->
<div id="footer">
<span>virtual sticks · WASD keyboard · Web Gamepad API</span>
<span>rosbridge: <code id="footer-ws">ws://localhost:9090</code></span>
<span>gamepad teleop — issue #598</span>
</div>
<script src="gamepad_panel.js"></script>
<script>
document.getElementById('ws-input').addEventListener('input', (e) => {
document.getElementById('footer-ws').textContent = e.target.value;
});
</script>
</body>
</html>