From 5fbf096a040252c93d0dc4c05cc96753db1f4506 Mon Sep 17 00:00:00 2001 From: User Date: Sun, 1 Feb 2026 04:56:10 +0100 Subject: [PATCH] feat: integrate WebSocket with live tracking - Add WebSocket state variables (wsEnabled, wsConnected) - Add initWebSocket() function with connection and event handling - Add handleWebSocketScanUpdate() for processing WS scan events - Modify startLiveTracking() to use WS mode with HTTP fallback - Update index.html to load socket.io and websocket.js scripts - Show [WS] indicator in status when using WebSocket mode Co-Authored-By: Claude Opus 4.5 --- src/rf_mapper/web/static/js/app.js | 171 ++++++++++++++++++++++++- src/rf_mapper/web/templates/index.html | 5 + 2 files changed, 170 insertions(+), 6 deletions(-) diff --git a/src/rf_mapper/web/static/js/app.js b/src/rf_mapper/web/static/js/app.js index d68ff4a..53fe68a 100644 --- a/src/rf_mapper/web/static/js/app.js +++ b/src/rf_mapper/web/static/js/app.js @@ -33,6 +33,10 @@ let liveTrackingEnabled = false; let liveTrackingInterval = null; const LIVE_TRACKING_INTERVAL_MS = 4000; // 4 seconds +// WebSocket state +let wsEnabled = true; // Try WebSocket first +let wsConnected = false; + // Statistical movement detection const SAMPLE_HISTORY_SIZE = 5; // Number of samples to keep for averaging const MOVEMENT_THRESHOLD = 1.5; // meters - movement must exceed this + stddev margin @@ -134,6 +138,9 @@ document.addEventListener('DOMContentLoaded', () => { map3dInitialized = true; }, 100); + // Initialize WebSocket connection + initWebSocket(); + // Start BT live tracking by default after a short delay setTimeout(() => { startLiveTracking(); @@ -141,6 +148,152 @@ document.addEventListener('DOMContentLoaded', () => { }, 2000); }); +// Initialize WebSocket connection for real-time updates +function initWebSocket() { + if (!wsEnabled) return; + + // Check if rfMapperWS is available (websocket.js loaded) + if (typeof rfMapperWS === 'undefined') { + console.log('[App] WebSocket module not loaded, using HTTP polling'); + return; + } + + const connected = rfMapperWS.connect(); + if (!connected) { + console.log('[App] WebSocket not available, using HTTP polling'); + return; + } + + rfMapperWS.on('connected', () => { + wsConnected = true; + console.log('[App] WebSocket connected'); + + // Stop HTTP polling if running (WS will handle updates) + if (liveTrackingInterval) { + clearInterval(liveTrackingInterval); + liveTrackingInterval = null; + console.log('[App] Stopped HTTP polling (using WebSocket)'); + } + }); + + rfMapperWS.on('disconnected', (data) => { + wsConnected = false; + console.log('[App] WebSocket disconnected:', data?.reason); + + // Resume HTTP polling if live tracking is enabled + if (liveTrackingEnabled && !liveTrackingInterval) { + liveTrackingInterval = setInterval(performLiveBTScan, LIVE_TRACKING_INTERVAL_MS); + console.log('[App] Resumed HTTP polling'); + } + }); + + rfMapperWS.on('scanUpdate', (data) => { + handleWebSocketScanUpdate(data); + }); +} + +// Handle scan updates received via WebSocket +function handleWebSocketScanUpdate(data) { + if (!liveTrackingEnabled) return; + + console.log('[WS] Scan update:', data.type, data.devices?.length, 'devices'); + + // Handle Bluetooth scan results + if (data.type === 'bluetooth' && data.devices) { + const newBt = data.devices; + + // Track which devices were detected in this scan + const detectedAddresses = new Set(newBt.map(d => d.address)); + + if (scanData) { + const existingBt = scanData.bluetooth_devices || []; + + // Update existing devices with new RSSI, add new devices + newBt.forEach(newDev => { + const existing = existingBt.find(d => d.address === newDev.address); + const newDist = newDev.estimated_distance_m; + + // Check for movement using statistical analysis + const moving = isDeviceMoving(newDev.address, newDist); + + // Reset miss count - device was detected + deviceMissCount[newDev.address] = 0; + + if (existing) { + // Update RSSI and estimated distance, preserve custom values + existing.rssi = newDev.rssi; + existing.estimated_distance_m = newDev.estimated_distance_m; + existing.signal_quality = newDev.signal_quality; + existing.is_moving = moving; + existing.miss_count = 0; + // Preserve floor and custom_distance_m if set + } else { + // New device, add it + newDev.is_moving = moving; + existingBt.push(newDev); + } + }); + + // Increment miss count for devices not detected in this scan + existingBt.forEach(dev => { + if (!detectedAddresses.has(dev.address)) { + deviceMissCount[dev.address] = (deviceMissCount[dev.address] || 0) + 1; + dev.miss_count = deviceMissCount[dev.address]; + } + }); + + // Filter out devices that have been missed too many times + const filteredBt = existingBt.filter(dev => { + const missCount = deviceMissCount[dev.address] || 0; + if (missCount >= MAX_MISSED_SCANS) { + // Clean up tracking data for removed device + delete deviceMissCount[dev.address]; + delete deviceDistanceHistory[dev.address]; + // Clear trail if showing + if (deviceTrails[dev.address]) { + clearDeviceTrail(dev.address); + } + console.log(`[WS] Removed ${dev.name} (missed ${missCount} scans)`); + return false; + } + return true; + }); + + scanData.bluetooth_devices = filteredBt; + } else { + // No existing scan data, use BT-only data + newBt.forEach(dev => { + // Initialize history with first sample, not moving yet + isDeviceMoving(dev.address, dev.estimated_distance_m); + dev.is_moving = false; + deviceMissCount[dev.address] = 0; + }); + scanData = { + wifi_networks: [], + bluetooth_devices: newBt, + timestamp: data.timestamp + }; + } + + // Update visualizations + const status = document.getElementById('scan-status'); + if (status) { + const movingCount = scanData.bluetooth_devices.filter(d => d.is_moving).length; + const wsIndicator = wsConnected ? '[WS]' : ''; + status.textContent = `Live${wsIndicator}: ${scanData.bluetooth_devices.length} BT (${movingCount} moving) @ ${new Date().toLocaleTimeString()}`; + } + + // Update BT count + document.getElementById('bt-count').textContent = scanData.bluetooth_devices.length; + document.getElementById('bt-list-count').textContent = scanData.bluetooth_devices.length; + + // Refresh views + drawRadar(); + update3DMarkers(); + updateMapMarkers(); + } +} + // Toggle filter function toggleFilter(type) { filters[type] = !filters[type]; @@ -1925,17 +2078,23 @@ function toggleLiveTracking() { function startLiveTracking() { if (liveTrackingInterval) { clearInterval(liveTrackingInterval); + liveTrackingInterval = null; } liveTrackingEnabled = true; updateLiveTrackingUI(); - console.log('Live BT tracking started'); - // Do initial scan - performLiveBTScan(); - - // Set up interval - liveTrackingInterval = setInterval(performLiveBTScan, LIVE_TRACKING_INTERVAL_MS); + if (wsConnected) { + // WebSocket mode - updates come automatically via 'scanUpdate' events + // Still need to trigger initial scan + performLiveBTScan(); + console.log('[Live] Started (WebSocket mode)'); + } else { + // HTTP polling fallback + liveTrackingInterval = setInterval(performLiveBTScan, LIVE_TRACKING_INTERVAL_MS); + performLiveBTScan(); + console.log('[Live] Started (HTTP polling mode)'); + } } // Stop live BT tracking diff --git a/src/rf_mapper/web/templates/index.html b/src/rf_mapper/web/templates/index.html index 24d7bee..9f7954e 100644 --- a/src/rf_mapper/web/templates/index.html +++ b/src/rf_mapper/web/templates/index.html @@ -166,5 +166,10 @@ {% endblock %} {% block extra_js %} + + + + + {% endblock %}