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>
198 lines
6.4 KiB
HTML
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>
|