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>
184 lines
6.4 KiB
HTML
184 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 — Gimbal Control</title>
|
|
<link rel="stylesheet" href="gimbal_panel.css">
|
|
<!-- roslib from CDN -->
|
|
<script src="https://cdn.jsdelivr.net/npm/roslib@1.4.0/build/roslib.min.js"></script>
|
|
<style>
|
|
/* Cam button active state (can't use CSS-only with JS-toggled class without Tailwind) */
|
|
.cam-btn { padding: 3px 10px; border-radius: 4px; border: 1px solid #1e3a5f;
|
|
background: #070712; color: #4b5563; font-family: monospace;
|
|
font-size: 10px; font-weight: bold; cursor: pointer; transition: all 0.15s; }
|
|
.cam-btn:hover { color: #d1d5db; }
|
|
.cam-btn.active { background: #083344; border-color: #155e75; color: #67e8f9; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ── Header ── -->
|
|
<div id="header">
|
|
<div class="logo">⚡ SALTYBOT — GIMBAL</div>
|
|
|
|
<div id="conn-bar">
|
|
<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="btn btn-cyan">CONNECT</button>
|
|
<span id="conn-status" style="color:#4b5563;font-size:10px;">Not connected</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Main ── -->
|
|
<div id="main">
|
|
|
|
<!-- ── Left: Camera feed ── -->
|
|
<section id="camera-section">
|
|
|
|
<!-- Camera selector toolbar -->
|
|
<div id="cam-toolbar">
|
|
<button class="cam-btn active" data-cam="front">FRONT</button>
|
|
<button class="cam-btn" data-cam="rear" >REAR</button>
|
|
<button class="cam-btn" data-cam="left" >LEFT</button>
|
|
<button class="cam-btn" data-cam="right">RIGHT</button>
|
|
<span id="cam-topic">/camera/front/image_raw/compressed</span>
|
|
</div>
|
|
|
|
<!-- Video frame -->
|
|
<div id="camera-frame">
|
|
<img id="camera-img" alt="gimbal camera feed" />
|
|
<div id="no-signal">
|
|
<div class="icon">📷</div>
|
|
<div>NO SIGNAL</div>
|
|
</div>
|
|
<div id="fps-badge">— fps</div>
|
|
<div id="angle-overlay">PAN 0.0° TILT 0.0°</div>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<!-- ── Right: Controls ── -->
|
|
<aside id="controls">
|
|
|
|
<!-- Pan/Tilt pad -->
|
|
<div class="card">
|
|
<div class="card-title">PAN / TILT CONTROL</div>
|
|
<div id="pad-wrap">
|
|
<div class="pad-labels">
|
|
<span>← PAN</span>
|
|
<span style="color:#155e75">DRAG</span>
|
|
<span>PAN →</span>
|
|
</div>
|
|
<canvas id="gimbal-pad" width="200" height="150"></canvas>
|
|
<div style="font-size:9px;color:#374151;text-align:center">
|
|
↑ TILT UP | ↓ TILT DOWN
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Current angles -->
|
|
<div class="card">
|
|
<div class="card-title">CURRENT ANGLES (feedback)</div>
|
|
<div id="angle-display">
|
|
<div class="angle-box">
|
|
<div class="angle-label">PAN</div>
|
|
<div class="angle-value" id="pan-val">0.0</div>
|
|
<div class="angle-unit">degrees</div>
|
|
</div>
|
|
<div class="angle-box">
|
|
<div class="angle-label">TILT</div>
|
|
<div class="angle-value" id="tilt-val">0.0</div>
|
|
<div class="angle-unit">degrees</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Preset positions -->
|
|
<div class="card">
|
|
<div class="card-title">PRESET POSITIONS</div>
|
|
<div id="presets">
|
|
<button class="btn btn-cyan" data-preset="0">
|
|
<span class="preset-name">CENTER</span>
|
|
<span class="preset-val">0° / 0°</span>
|
|
</button>
|
|
<button class="btn btn-cyan" data-preset="1">
|
|
<span class="preset-name">LEFT</span>
|
|
<span class="preset-val">-45° / 0°</span>
|
|
</button>
|
|
<button class="btn btn-cyan" data-preset="2">
|
|
<span class="preset-name">RIGHT</span>
|
|
<span class="preset-val">+45° / 0°</span>
|
|
</button>
|
|
<button class="btn btn-cyan" data-preset="3">
|
|
<span class="preset-name">UP</span>
|
|
<span class="preset-val">0° / +45°</span>
|
|
</button>
|
|
<button class="btn btn-cyan" data-preset="4">
|
|
<span class="preset-name">DOWN</span>
|
|
<span class="preset-val">0° / -30°</span>
|
|
</button>
|
|
</div>
|
|
<button id="btn-home" style="margin-top:8px">⌂ HOME (0° / 0°)</button>
|
|
</div>
|
|
|
|
<!-- Person tracking -->
|
|
<div class="card">
|
|
<div class="card-title">PERSON TRACKING</div>
|
|
<div id="tracking-row">
|
|
<span class="toggle-label" id="tracking-label">OFF — manual control</span>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" id="tracking-toggle">
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
<div style="font-size:9px;color:#4b5563;margin-top:6px">
|
|
Publishes to <code>/gimbal/tracking_enabled</code>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Topic info -->
|
|
<div class="card" style="font-size:9px;color:#4b5563;line-height:1.8">
|
|
<div class="card-title">ROS TOPICS</div>
|
|
<div>PUB <code style="color:#374151">/gimbal/cmd</code> → geometry_msgs/Vector3</div>
|
|
<div>SUB <code style="color:#374151">/gimbal/state</code> → geometry_msgs/Vector3</div>
|
|
<div>PUB <code style="color:#374151">/gimbal/tracking_enabled</code> → std_msgs/Bool</div>
|
|
<div>SUB <code style="color:#374151">/camera/*/image_raw/compressed</code></div>
|
|
</div>
|
|
|
|
</aside>
|
|
</div>
|
|
|
|
<!-- ── Footer ── -->
|
|
<div id="footer">
|
|
<span>rosbridge: <code id="footer-ws">ws://localhost:9090</code></span>
|
|
<span>gimbal control panel — issue #551</span>
|
|
</div>
|
|
|
|
<script src="gimbal_panel.js"></script>
|
|
<script>
|
|
// Sync footer ws URL
|
|
document.getElementById('ws-input').addEventListener('input', (e) => {
|
|
document.getElementById('footer-ws').textContent = e.target.value;
|
|
});
|
|
|
|
// Tracking label update
|
|
document.getElementById('tracking-toggle').addEventListener('change', (e) => {
|
|
document.getElementById('tracking-label').textContent =
|
|
e.target.checked ? 'ON — following person' : 'OFF — manual control';
|
|
});
|
|
|
|
// Camera topic label update
|
|
document.querySelectorAll('.cam-btn').forEach((btn) => {
|
|
btn.addEventListener('click', () => {
|
|
const cam = btn.dataset.cam;
|
|
document.getElementById('cam-topic').textContent =
|
|
`/camera/${cam}/image_raw/compressed`;
|
|
document.getElementById('footer-ws').textContent =
|
|
document.getElementById('ws-input').value;
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|