From eff69b203731374add7fa7e27d2664b6c513cd2d Mon Sep 17 00:00:00 2001 From: sl-webui Date: Wed, 4 Mar 2026 22:57:10 -0500 Subject: [PATCH] feat(hud): add sensor feeds (GPS, LIDAR, RealSense) (Issue #413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tabbed sensor feed interface to the HUD center viewport: GPS Map Panel: - Fetches location data from /gps HTTP endpoint on port 8888 - Renders OpenStreetMap with real-time location marker - Displays: coordinates, altitude, accuracy LIDAR Point Cloud Visualization: - Subscribes to /scan topic via rosbridge WebSocket - 2D polar plot with grid, cardinal directions, forward indicator - Real-time point cloud rendering with range statistics - Displays: point count, max range (0-30m) RealSense Dual Stream: - Subscribes to /camera/color/image_raw/compressed (RGB) - Subscribes to /camera/depth/image_rect_raw/compressed (Depth) - Side-by-side canvas rendering with independent scaling - FPS counter and resolution display Tab System: - 4-way view switching: 3D Model ↔ GPS ↔ LIDAR ↔ RealSense - Persistent tab state, lazy initialization on demand - Dark theme with cyan/orange accent colors - Status indicators for each sensor (loading/error/ready) Architecture: - Browser native canvas for LIDAR visualization - WebSocket rosbridge integration for sensor subscriptions - Fetch API for HTTP GPS data (localhost:8888) - Leaflet.js for OSM map rendering (CDN) - 2s polling interval for GPS updates Rosbridge Endpoints (assumes localhost:9090): - /scan (sensor_msgs/LaserScan) — 1Hz LIDAR - /camera/color/image_raw/compressed — RGB stream - /camera/depth/image_rect_raw/compressed — Depth stream HTTP Endpoints (assumes localhost:8888): - GET /gps → { lat, lon, alt, accuracy, timestamp } Integration: - Preserves existing 3D HUD viewport and controls - Left/right sidebars remain unchanged - Bottom PID control bar operational - Tab switching preserves center panel size/aspect ratio Co-Authored-By: Claude Haiku 4.5 --- ui/index.html | 337 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 335 insertions(+), 2 deletions(-) diff --git a/ui/index.html b/ui/index.html index 0df21e3..a9c1998 100644 --- a/ui/index.html +++ b/ui/index.html @@ -115,8 +115,63 @@ input[type=range] { cursor: pointer; }
PKT/s: --
- -
+ +
+ +
+ + + + +
+ + +
+ + + + + + + + + +
@@ -838,6 +893,284 @@ async function readLoop() { } } catch(e) { setStatus('Read err: ' + e.message); } } + +// ── Sensor feed tab switching ────────────────────────────────────────────────── +window.switchSensorView = function(view) { + // Hide all views + document.querySelectorAll('.sensor-view').forEach(el => el.classList.add('hidden')); + // Show selected view + document.getElementById(view + '-view').classList.remove('hidden'); + // Update tab buttons + document.querySelectorAll('.sensor-tab-btn').forEach(btn => { + btn.classList.add('bg-gray-900', 'border-gray-700', 'text-gray-400'); + btn.classList.remove('bg-cyan-950', 'border-cyan-800', 'text-cyan-300'); + }); + event.target.classList.remove('bg-gray-900', 'border-gray-700', 'text-gray-400'); + event.target.classList.add('bg-cyan-950', 'border-cyan-800', 'text-cyan-300'); + + // Initialize sensor if needed + if (view === 'gps') initGPS(); + else if (view === 'lidar') initLIDAR(); + else if (view === 'realsense') initRealSense(); +}; + +// ── GPS Map fetcher ─────────────────────────────────────────────────────────── +let gpsInitialized = false; +function initGPS() { + if (gpsInitialized) return; + gpsInitialized = true; + + async function fetchGPS() { + try { + const resp = await fetch('http://localhost:8888/gps', { mode: 'cors', cache: 'no-store' }); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + const data = await resp.json(); + // data: { lat, lon, alt, accuracy, timestamp } + if (data.lat !== undefined && data.lon !== undefined) { + document.getElementById('gps-loading').classList.add('hidden'); + document.getElementById('gps-status').textContent = 'LIVE'; + document.getElementById('gps-coords').textContent = `${data.lat.toFixed(6)}, ${data.lon.toFixed(6)}`; + if (data.alt !== undefined) { + document.getElementById('gps-alt').textContent = `Alt: ${data.alt.toFixed(1)}m`; + } + + // Render simple map iframe (OSM + marker) + const mapHtml = ` + + +