Standalone panel ui/vesc_panel.{html,js,css} with live CAN telemetry
via rosbridge. Subscribes to /vesc/left/state, /vesc/right/state
(std_msgs/String JSON) and /vesc/combined for battery voltage.
Features:
- Canvas arc gauge per motor showing RPM + direction (FWD/REV/STOP)
- Current draw bar (motor + input), duty cycle bar, temperature bars
- FET and motor temperature boxes with warn/crit colour coding
- Sparkline charts for RPM and current (last 60 s, 120 samples)
- Battery card: voltage, total draw, both RPMs, SOC progress bar
- Colour-coded health: green/amber/red at configurable thresholds
- E-stop button: publishes zero /cmd_vel + /saltybot/emergency event
- Stale detection (2 s timeout → OFFLINE state)
- Hz counter + last-stamp display in header
- Mobile-responsive layout (single-column below 640 px)
- WS URL persisted in localStorage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
230 lines
8.4 KiB
HTML
230 lines
8.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 — VESC Motor Dashboard</title>
|
||
<link rel="stylesheet" href="vesc_panel.css">
|
||
<script src="https://cdn.jsdelivr.net/npm/roslib@1.3.0/build/roslib.min.js"></script>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ── Header ── -->
|
||
<div id="header">
|
||
<div class="logo">⚡ SALTYBOT — VESC MOTORS</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="hdr-btn">CONNECT</button>
|
||
<span id="conn-label" style="color:#4b5563;font-size:10px">Not connected</span>
|
||
</div>
|
||
|
||
<div id="header-right">
|
||
<span id="hz-label" class="meta-label">— Hz</span>
|
||
<span style="color:#374151">|</span>
|
||
<span id="stamp-label" class="meta-label">No data</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Status bar ── -->
|
||
<div id="status-bar">
|
||
<span style="color:#6b7280;font-size:10px">LEFT</span>
|
||
<span class="sys-badge badge-stale" id="badge-left">OFFLINE</span>
|
||
<span style="color:#4b5563">│</span>
|
||
<span style="color:#6b7280;font-size:10px">RIGHT</span>
|
||
<span class="sys-badge badge-stale" id="badge-right">OFFLINE</span>
|
||
<span style="color:#4b5563">│</span>
|
||
<span style="color:#6b7280;font-size:10px">BATTERY</span>
|
||
<span class="sys-badge badge-stale" id="badge-batt">—</span>
|
||
<span style="color:#4b5563">│</span>
|
||
<span style="color:#6b7280;font-size:10px">TOTAL DRAW</span>
|
||
<span class="sys-badge badge-stale" id="badge-total">—</span>
|
||
|
||
<button id="btn-estop" class="estop-btn">⛔ E-STOP</button>
|
||
</div>
|
||
|
||
<!-- ── Dashboard ── -->
|
||
<div id="dashboard">
|
||
|
||
<!-- ╔═════════ LEFT MOTOR ═════════╗ -->
|
||
<div class="card motor-card" id="card-left">
|
||
<div class="card-title">
|
||
LEFT MOTOR
|
||
<span class="fault-badge" id="fault-left">OK</span>
|
||
</div>
|
||
|
||
<!-- Arc gauge + direction -->
|
||
<div class="gauge-row-top">
|
||
<div class="arc-wrap">
|
||
<canvas id="rpm-arc-left" width="140" height="100"></canvas>
|
||
<div class="arc-dir" id="dir-left">—</div>
|
||
</div>
|
||
<div class="motor-stats">
|
||
<div class="stat-row">
|
||
<span class="stat-label">CURRENT (MTR)</span>
|
||
<span class="stat-val" id="cur-left">—</span>
|
||
</div>
|
||
<div class="bar-track"><div class="bar-fill" id="cur-bar-left"></div></div>
|
||
|
||
<div class="stat-row" style="margin-top:6px">
|
||
<span class="stat-label">CURRENT (IN)</span>
|
||
<span class="stat-val" id="curin-left">—</span>
|
||
</div>
|
||
<div class="bar-track"><div class="bar-fill" id="curin-bar-left"></div></div>
|
||
|
||
<div class="stat-row" style="margin-top:6px">
|
||
<span class="stat-label">DUTY CYCLE</span>
|
||
<span class="stat-val" id="duty-left">—</span>
|
||
</div>
|
||
<div class="bar-track"><div class="bar-fill" id="duty-bar-left"></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Temperatures -->
|
||
<div class="temp-row">
|
||
<div class="temp-box" id="tbox-fet-left">
|
||
<div class="temp-label">FET TEMP</div>
|
||
<div class="temp-val" id="tfet-left">—</div>
|
||
<div class="bar-track mini"><div class="bar-fill" id="tfet-bar-left"></div></div>
|
||
</div>
|
||
<div class="temp-box" id="tbox-mot-left">
|
||
<div class="temp-label">MOTOR TEMP</div>
|
||
<div class="temp-val" id="tmot-left">—</div>
|
||
<div class="bar-track mini"><div class="bar-fill" id="tmot-bar-left"></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sparklines -->
|
||
<div class="spark-section">
|
||
<div class="spark-label">RPM · 60s</div>
|
||
<canvas class="sparkline" id="spark-rpm-left" height="40"></canvas>
|
||
<div class="spark-label" style="margin-top:4px">CURRENT · 60s</div>
|
||
<canvas class="sparkline" id="spark-cur-left" height="40"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ╔═════════ RIGHT MOTOR ═════════╗ -->
|
||
<div class="card motor-card" id="card-right">
|
||
<div class="card-title">
|
||
RIGHT MOTOR
|
||
<span class="fault-badge" id="fault-right">OK</span>
|
||
</div>
|
||
|
||
<div class="gauge-row-top">
|
||
<div class="arc-wrap">
|
||
<canvas id="rpm-arc-right" width="140" height="100"></canvas>
|
||
<div class="arc-dir" id="dir-right">—</div>
|
||
</div>
|
||
<div class="motor-stats">
|
||
<div class="stat-row">
|
||
<span class="stat-label">CURRENT (MTR)</span>
|
||
<span class="stat-val" id="cur-right">—</span>
|
||
</div>
|
||
<div class="bar-track"><div class="bar-fill" id="cur-bar-right"></div></div>
|
||
|
||
<div class="stat-row" style="margin-top:6px">
|
||
<span class="stat-label">CURRENT (IN)</span>
|
||
<span class="stat-val" id="curin-right">—</span>
|
||
</div>
|
||
<div class="bar-track"><div class="bar-fill" id="curin-bar-right"></div></div>
|
||
|
||
<div class="stat-row" style="margin-top:6px">
|
||
<span class="stat-label">DUTY CYCLE</span>
|
||
<span class="stat-val" id="duty-right">—</span>
|
||
</div>
|
||
<div class="bar-track"><div class="bar-fill" id="duty-bar-right"></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="temp-row">
|
||
<div class="temp-box" id="tbox-fet-right">
|
||
<div class="temp-label">FET TEMP</div>
|
||
<div class="temp-val" id="tfet-right">—</div>
|
||
<div class="bar-track mini"><div class="bar-fill" id="tfet-bar-right"></div></div>
|
||
</div>
|
||
<div class="temp-box" id="tbox-mot-right">
|
||
<div class="temp-label">MOTOR TEMP</div>
|
||
<div class="temp-val" id="tmot-right">—</div>
|
||
<div class="bar-track mini"><div class="bar-fill" id="tmot-bar-right"></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="spark-section">
|
||
<div class="spark-label">RPM · 60s</div>
|
||
<canvas class="sparkline" id="spark-rpm-right" height="40"></canvas>
|
||
<div class="spark-label" style="margin-top:4px">CURRENT · 60s</div>
|
||
<canvas class="sparkline" id="spark-cur-right" height="40"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ╔═════════ BATTERY / COMBINED ═════════╗ -->
|
||
<div class="card" id="card-batt">
|
||
<div class="card-title">
|
||
BATTERY — 4S LiPo (12.0–16.8 V)
|
||
<span class="meta-label" id="batt-stamp">—</span>
|
||
</div>
|
||
|
||
<div class="batt-row">
|
||
<!-- Big voltage -->
|
||
<div class="batt-metric">
|
||
<div class="batt-metric-label">VOLTAGE</div>
|
||
<div class="big-num" id="batt-voltage">—</div>
|
||
<div class="batt-unit">V</div>
|
||
</div>
|
||
<div class="batt-metric">
|
||
<div class="batt-metric-label">TOTAL DRAW</div>
|
||
<div class="big-num" id="batt-total-cur" style="color:#06b6d4">—</div>
|
||
<div class="batt-unit">A</div>
|
||
</div>
|
||
<div class="batt-metric">
|
||
<div class="batt-metric-label">LEFT RPM</div>
|
||
<div class="big-num" id="batt-rpm-l" style="color:#a855f7;font-size:20px">—</div>
|
||
</div>
|
||
<div class="batt-metric">
|
||
<div class="batt-metric-label">RIGHT RPM</div>
|
||
<div class="big-num" id="batt-rpm-r" style="color:#a855f7;font-size:20px">—</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Voltage bar -->
|
||
<div>
|
||
<div class="stat-row">
|
||
<span class="stat-label">Voltage (12.0–16.8 V)</span>
|
||
<span class="stat-val" id="batt-volt-pct">—</span>
|
||
</div>
|
||
<div class="bar-track"><div class="bar-fill" id="batt-volt-bar"></div></div>
|
||
</div>
|
||
|
||
<!-- Total current bar -->
|
||
<div>
|
||
<div class="stat-row">
|
||
<span class="stat-label">Total Current (0–120 A)</span>
|
||
<span class="stat-val" id="batt-cur-pct">—</span>
|
||
</div>
|
||
<div class="bar-track"><div class="bar-fill" id="batt-cur-bar"></div></div>
|
||
</div>
|
||
|
||
<div style="font-size:9px;color:#374151">
|
||
Voltage zones: <13.2V warn · <12.4V critical · FET >70°C warn · Motor >85°C warn
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ── Footer ── -->
|
||
<div id="footer">
|
||
<span>rosbridge: <code id="footer-ws">ws://localhost:9090</code></span>
|
||
<span>topics: /vesc/left/state · /vesc/right/state · /vesc/combined (std_msgs/String JSON)</span>
|
||
<span>vesc motor dashboard — issue #653</span>
|
||
</div>
|
||
|
||
<script src="vesc_panel.js"></script>
|
||
<script>
|
||
document.getElementById('ws-input').addEventListener('input', (e) => {
|
||
document.getElementById('footer-ws').textContent = e.target.value;
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|