saltylab-firmware/ui/index.html
sl-webui f384cc4810 feat: Robot GPS live map panel (Issue #709 companion)
Adds gps_map_panel.html/css/js — standalone dashboard panel:

- Leaflet.js + OpenStreetMap with dark CSS filter (matches dashboard theme)
- Heading-aware SVG robot marker (orange arrow shows direction of travel)
- Orange breadcrumb trail polyline (up to 2000 pts, CLEAR button)
- FOLLOW mode auto-pan; drag map to switch to FREE mode
- Sidebar: speed (km/h, color-coded), altitude, heading compass rose,
  fix status (0=NO FIX…4=RTK), fix count, lat/lon, trail log
- Exponential backoff auto-reconnect (2s→30s cap)
- Stale detection at 5s for fix + velocity badges

Subscribes via rosbridge to:
  saltybot/gps/fix  std_msgs/String JSON — {lat, lon, alt, stat, t}
  saltybot/gps/vel  std_msgs/String JSON — {spd, hdg, t}

index.html: new GPS MAP card (🛰️, #709) before CAN MONITOR
dashboard.js: gpsWatch subscription + 'gps' panel entry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 22:28:44 -04:00

313 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 — Dashboard</title>
<link rel="stylesheet" href="dashboard.css">
<script src="https://cdn.jsdelivr.net/npm/roslib@1.3.0/build/roslib.min.js"></script>
</head>
<body>
<!-- ══════════════════════════════════════════════════════ TOP BAR ══ -->
<div id="topbar">
<div id="topbar-left">
<div class="logo">⚡ SALTYBOT</div>
<div id="robot-name">ROBOT-01</div>
</div>
<div id="topbar-status">
<!-- Battery -->
<div class="stat-block" id="stat-battery">
<div class="stat-icon">🔋</div>
<div class="stat-body">
<div class="stat-label">BATTERY</div>
<div class="stat-val" id="val-battery"></div>
</div>
<div class="batt-bar"><div id="batt-fill"></div></div>
</div>
<div class="stat-sep"></div>
<!-- Safety -->
<div class="stat-block">
<div class="stat-icon">🛡️</div>
<div class="stat-body">
<div class="stat-label">SAFETY</div>
<div class="stat-val" id="val-safety"></div>
</div>
</div>
<div class="stat-sep"></div>
<!-- E-stop -->
<div class="stat-block">
<div class="stat-icon"></div>
<div class="stat-body">
<div class="stat-label">E-STOP</div>
<div class="stat-val" id="val-estop" style="color:#6b7280">OFF</div>
</div>
</div>
<div class="stat-sep"></div>
<!-- Drive mode -->
<div class="stat-block">
<div class="stat-icon">🚗</div>
<div class="stat-body">
<div class="stat-label">MODE</div>
<div class="stat-val" id="val-mode"></div>
</div>
</div>
<div class="stat-sep"></div>
<!-- Uptime -->
<div class="stat-block">
<div class="stat-icon"></div>
<div class="stat-body">
<div class="stat-label">UPTIME</div>
<div class="stat-val" id="val-uptime"></div>
</div>
</div>
</div>
<div id="topbar-right">
<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">Not connected</span>
</div>
</div>
<!-- ══════════════════════════════════════════════ PANEL GRID ══ -->
<div id="main">
<!-- Auto-detect bar -->
<div id="detect-bar">
<span id="detect-status">🔍 Auto-detecting rosbridge…</span>
<div id="detect-dots"></div>
</div>
<!-- Panel cards grid -->
<div id="grid">
<a class="panel-card" href="map_panel.html" data-panel="map">
<div class="card-header">
<div class="card-icon" style="color:#22c55e">🗺️</div>
<div>
<div class="card-title">MAP VIEW</div>
<div class="card-sub">#587</div>
</div>
<div class="card-dot" id="dot-map"></div>
</div>
<div class="card-desc">RPLIDAR scan overlay · robot position · UWB anchors · safety zones · breadcrumb trail</div>
<div class="card-topics">
<code>/saltybot/pose/fused</code>
<code>/scan</code>
<code>/saltybot/safety_zone/status</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-map">OFFLINE</span>
<span class="card-msg" id="msg-map">No data</span>
</div>
</a>
<a class="panel-card" href="gamepad_panel.html" data-panel="gamepad">
<div class="card-header">
<div class="card-icon" style="color:#06b6d4">🎮</div>
<div>
<div class="card-title">GAMEPAD TELEOP</div>
<div class="card-sub">#598</div>
</div>
<div class="card-dot" id="dot-gamepad"></div>
</div>
<div class="card-desc">Web Gamepad API · virtual dual sticks · WASD keyboard · speed limits · deadzone config</div>
<div class="card-topics">
<code>/cmd_vel</code>
<code>geometry_msgs/Twist</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-gamepad">OFFLINE</span>
<span class="card-msg" id="msg-gamepad">No data</span>
</div>
</a>
<a class="panel-card" href="diagnostics_panel.html" data-panel="diag">
<div class="card-header">
<div class="card-icon" style="color:#f59e0b">📊</div>
<div>
<div class="card-title">DIAGNOSTICS</div>
<div class="card-sub">#562</div>
</div>
<div class="card-dot" id="dot-diag"></div>
</div>
<div class="card-desc">Battery · CPU/GPU/motor temps · RAM/disk · WiFi signal · ROS2 node health</div>
<div class="card-topics">
<code>/diagnostics</code>
<code>diagnostic_msgs/DiagnosticArray</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-diag">OFFLINE</span>
<span class="card-msg" id="msg-diag">No data</span>
</div>
</a>
<a class="panel-card" href="event_log_panel.html" data-panel="events">
<div class="card-header">
<div class="card-icon" style="color:#a78bfa">📋</div>
<div>
<div class="card-title">EVENT LOG</div>
<div class="card-sub">#576</div>
</div>
<div class="card-dot" id="dot-events"></div>
</div>
<div class="card-desc">Filterable real-time feed · severity badges · node filter · text search · CSV export</div>
<div class="card-topics">
<code>/rosout</code>
<code>/saltybot/events</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-events">OFFLINE</span>
<span class="card-msg" id="msg-events">No data</span>
</div>
</a>
<a class="panel-card" href="settings_panel.html" data-panel="settings">
<div class="card-header">
<div class="card-icon" style="color:#9ca3af">⚙️</div>
<div>
<div class="card-title">SETTINGS</div>
<div class="card-sub">#614</div>
</div>
<div class="card-dot" id="dot-settings" style="background:#374151"></div>
</div>
<div class="card-desc">PID tuning · speed limits · safety thresholds · sensor toggles · named presets</div>
<div class="card-topics">
<code>rcl_interfaces/srv/GetParameters</code>
<code>SetParameters</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-settings" style="color:#6b7280">CONFIG</span>
<span class="card-msg" id="msg-settings">Parameter service</span>
</div>
</a>
<a class="panel-card" href="gps_map_panel.html" data-panel="gps">
<div class="card-header">
<div class="card-icon" style="color:#22c55e">🛰️</div>
<div>
<div class="card-title">GPS MAP</div>
<div class="card-sub">#709</div>
</div>
<div class="card-dot" id="dot-gps"></div>
</div>
<div class="card-desc">Live robot position · breadcrumb trail · speed · heading compass · fix quality</div>
<div class="card-topics">
<code>saltybot/gps/fix</code>
<code>saltybot/gps/vel</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-gps">OFFLINE</span>
<span class="card-msg" id="msg-gps">No data</span>
</div>
</a>
<a class="panel-card" href="can_monitor_panel.html" data-panel="can">
<div class="card-header">
<div class="card-icon" style="color:#06b6d4">📡</div>
<div>
<div class="card-title">CAN MONITOR</div>
<div class="card-sub">#681</div>
</div>
<div class="card-dot" id="dot-can"></div>
</div>
<div class="card-desc">VESC L/R RPM · current · temps · voltage · IMU pitch/roll/yaw · balance PID · barometer</div>
<div class="card-topics">
<code>/vesc/left/state</code>
<code>/vesc/right/state</code>
<code>/saltybot/imu</code>
<code>/saltybot/balance_state</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-can">OFFLINE</span>
<span class="card-msg" id="msg-can">No data</span>
</div>
</a>
<a class="panel-card" href="gimbal_panel.html" data-panel="gimbal">
<div class="card-header">
<div class="card-icon" style="color:#f97316">🎥</div>
<div>
<div class="card-title">GIMBAL</div>
<div class="card-sub">#551</div>
</div>
<div class="card-dot" id="dot-gimbal"></div>
</div>
<div class="card-desc">Pan/tilt joystick · live camera feed · preset positions · person-tracking toggle</div>
<div class="card-topics">
<code>/gimbal/cmd</code>
<code>/gimbal/state</code>
</div>
<div class="card-footer">
<span class="card-status" id="status-gimbal">OFFLINE</span>
<span class="card-msg" id="msg-gimbal">No data</span>
</div>
</a>
</div><!-- /#grid -->
<!-- Connection info strip -->
<div id="info-strip">
<div class="info-item">
<span class="info-lbl">ROSBRIDGE</span>
<span class="info-val" id="info-ws"></span>
</div>
<div class="info-item">
<span class="info-lbl">MSG RATE</span>
<span class="info-val" id="info-rate"></span>
</div>
<div class="info-item">
<span class="info-lbl">LATENCY</span>
<span class="info-val" id="info-latency"></span>
</div>
<div class="info-item">
<span class="info-lbl">ROBOT IP</span>
<span class="info-val" id="info-ip"></span>
</div>
</div>
</div><!-- /#main -->
<!-- ══════════════════════════════════════════════ BOTTOM BAR ══ -->
<div id="bottombar">
<div id="bottombar-left">
<button id="btn-estop" class="estop-btn">⛔ E-STOP</button>
<button id="btn-resume" class="resume-btn" style="display:none">▶ RESUME</button>
</div>
<div id="bottombar-center">
<span class="bb-lbl">DRIVE MODE</span>
<div id="mode-btns">
<button class="mode-btn active" data-mode="MANUAL">MANUAL</button>
<button class="mode-btn" data-mode="AUTO">AUTO</button>
<button class="mode-btn" data-mode="FOLLOW">FOLLOW</button>
<button class="mode-btn" data-mode="DOCK">DOCK</button>
</div>
</div>
<div id="bottombar-right">
<div id="uptime-display">
<span class="bb-lbl">SESSION</span>
<span id="session-time">00:00:00</span>
</div>
<div style="color:#374151;font-size:9px">issue #630</div>
</div>
</div>
<script src="dashboard.js"></script>
</body>
</html>