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>
314 lines
11 KiB
HTML
314 lines
11 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 — Settings</title>
|
|
<link rel="stylesheet" href="settings_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 — SETTINGS</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="save-indicator" class="save-ind hidden">✓ SAVED</div>
|
|
</div>
|
|
|
|
<!-- ── Tab bar ── -->
|
|
<div id="tab-bar">
|
|
<button class="tab-btn active" data-tab="pid">PID</button>
|
|
<button class="tab-btn" data-tab="speed">SPEED</button>
|
|
<button class="tab-btn" data-tab="safety">SAFETY</button>
|
|
<button class="tab-btn" data-tab="sensors">SENSORS</button>
|
|
<button class="tab-btn" data-tab="system">SYSTEM</button>
|
|
</div>
|
|
|
|
<!-- ── Main ── -->
|
|
<div id="main">
|
|
|
|
<!-- ═══ PID TAB ═══ -->
|
|
<div class="tab-panel active" id="tab-pid">
|
|
<div class="panel-col">
|
|
|
|
<!-- Balance PID -->
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Balance Controller PID</div>
|
|
<div class="sec-node">/balance_controller</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('balance_pid')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('balance_pid')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="balance_pid-fields"></div>
|
|
<div class="sec-status" id="balance_pid-status"></div>
|
|
</div>
|
|
|
|
<!-- Adaptive PID -->
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Adaptive PID — Empty Load</div>
|
|
<div class="sec-node">/adaptive_pid</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('adaptive_pid_empty')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('adaptive_pid_empty')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="adaptive_pid_empty-fields"></div>
|
|
<div class="sec-status" id="adaptive_pid_empty-status"></div>
|
|
</div>
|
|
|
|
<!-- Adaptive PID bounds -->
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Adaptive PID — Bounds</div>
|
|
<div class="sec-node">/adaptive_pid</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('adaptive_pid_bounds')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('adaptive_pid_bounds')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="adaptive_pid_bounds-fields"></div>
|
|
<div class="sec-status" id="adaptive_pid_bounds-status"></div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ SPEED TAB ═══ -->
|
|
<div class="tab-panel" id="tab-speed">
|
|
<div class="panel-col">
|
|
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Tank Driver Limits</div>
|
|
<div class="sec-node">/tank_driver</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('tank_limits')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('tank_limits')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="tank_limits-fields"></div>
|
|
<div class="sec-status" id="tank_limits-status"></div>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Smooth Velocity Controller</div>
|
|
<div class="sec-node">/smooth_velocity_controller</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('smooth_vel')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('smooth_vel')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="smooth_vel-fields"></div>
|
|
<div class="sec-status" id="smooth_vel-status"></div>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Battery Speed Limiter</div>
|
|
<div class="sec-node">/battery_speed_limiter</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('batt_speed')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('batt_speed')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="batt_speed-fields"></div>
|
|
<div class="sec-status" id="batt_speed-status"></div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ SAFETY TAB ═══ -->
|
|
<div class="tab-panel" id="tab-safety">
|
|
<div class="panel-col">
|
|
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Safety Zone</div>
|
|
<div class="sec-node">/safety_zone</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('safety_zone')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('safety_zone')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="safety_zone-fields"></div>
|
|
<div class="sec-status" id="safety_zone-status"></div>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Power Supervisor</div>
|
|
<div class="sec-node">/power_supervisor_node</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('power_sup')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('power_sup')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="power_sup-fields"></div>
|
|
<div class="sec-status" id="power_sup-status"></div>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">LIDAR Avoidance</div>
|
|
<div class="sec-node">/lidar_avoidance</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('lidar_avoid')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('lidar_avoid')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="lidar_avoid-fields"></div>
|
|
<div class="sec-status" id="lidar_avoid-status"></div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ SENSORS TAB ═══ -->
|
|
<div class="tab-panel" id="tab-sensors">
|
|
<div class="panel-col">
|
|
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Sensor Enable / Disable</div>
|
|
<div class="sec-node">various nodes</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('sensor_toggles')">↓ LOAD ALL</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('sensor_toggles')">↑ APPLY ALL</button>
|
|
</div>
|
|
</div>
|
|
<div id="sensor_toggles-fields"></div>
|
|
<div class="sec-status" id="sensor_toggles-status"></div>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">IMU / Odometry Fusion</div>
|
|
<div class="sec-node">/uwb_imu_fusion</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadSection('imu_fusion')">↓ LOAD</button>
|
|
<button class="hbtn apply-btn" onclick="applySection('imu_fusion')">↑ APPLY</button>
|
|
</div>
|
|
</div>
|
|
<div id="imu_fusion-fields"></div>
|
|
<div class="sec-status" id="imu_fusion-status"></div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══ SYSTEM TAB ═══ -->
|
|
<div class="tab-panel" id="tab-system">
|
|
<div class="panel-col">
|
|
|
|
<!-- Live diagnostics -->
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">System Diagnostics</div>
|
|
<div class="sec-node">/diagnostics · auto-refresh 2 s</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" id="btn-refresh-diag" onclick="refreshDiag()">⟳ REFRESH</button>
|
|
</div>
|
|
</div>
|
|
<div id="diag-grid">
|
|
<div class="diag-placeholder">Connect to rosbridge to view diagnostics.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- WiFi / network -->
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Network</div>
|
|
<div class="sec-node">/diagnostics (wifi keys)</div>
|
|
</div>
|
|
</div>
|
|
<div id="net-grid">
|
|
<div class="diag-placeholder">Waiting for network data…</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Node list -->
|
|
<div class="section-card">
|
|
<div class="sec-header">
|
|
<div>
|
|
<div class="sec-title">Active ROS2 Nodes</div>
|
|
<div class="sec-node">rosbridge /rosapi/nodes</div>
|
|
</div>
|
|
<div class="sec-actions">
|
|
<button class="hbtn" onclick="loadNodeList()">⟳ REFRESH</button>
|
|
</div>
|
|
</div>
|
|
<div id="node-list-wrap">
|
|
<div class="diag-placeholder">Click refresh to list active nodes.</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /#main -->
|
|
|
|
<!-- ── Preset bar ── -->
|
|
<div id="preset-bar">
|
|
<span class="plbl">PRESETS:</span>
|
|
<select id="preset-select">
|
|
<option value="">— select —</option>
|
|
</select>
|
|
<button class="hbtn" onclick="loadPreset()">↓ LOAD</button>
|
|
<input id="preset-name" type="text" placeholder="Preset name…" />
|
|
<button class="hbtn" onclick="savePreset()">↑ SAVE</button>
|
|
<button class="hbtn del-btn" onclick="deletePreset()">✕ DELETE</button>
|
|
<div style="flex:1"></div>
|
|
<button class="hbtn" onclick="resetAllToDefaults()">⟲ DEFAULTS</button>
|
|
</div>
|
|
|
|
<!-- ── Footer ── -->
|
|
<div id="footer">
|
|
<span>ROS2 param services · rcl_interfaces/srv/GetParameters · SetParameters</span>
|
|
<span>rosbridge: <code id="footer-ws">ws://localhost:9090</code></span>
|
|
<span>settings panel — issue #614</span>
|
|
</div>
|
|
|
|
<script src="settings_panel.js"></script>
|
|
<script>
|
|
document.getElementById('ws-input').addEventListener('input', (e) => {
|
|
document.getElementById('footer-ws').textContent = e.target.value;
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|