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 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,10 @@ let liveTrackingEnabled = false;
|
|||||||
let liveTrackingInterval = null;
|
let liveTrackingInterval = null;
|
||||||
const LIVE_TRACKING_INTERVAL_MS = 4000; // 4 seconds
|
const LIVE_TRACKING_INTERVAL_MS = 4000; // 4 seconds
|
||||||
|
|
||||||
|
// WebSocket state
|
||||||
|
let wsEnabled = true; // Try WebSocket first
|
||||||
|
let wsConnected = false;
|
||||||
|
|
||||||
// Statistical movement detection
|
// Statistical movement detection
|
||||||
const SAMPLE_HISTORY_SIZE = 5; // Number of samples to keep for averaging
|
const SAMPLE_HISTORY_SIZE = 5; // Number of samples to keep for averaging
|
||||||
const MOVEMENT_THRESHOLD = 1.5; // meters - movement must exceed this + stddev margin
|
const MOVEMENT_THRESHOLD = 1.5; // meters - movement must exceed this + stddev margin
|
||||||
@@ -134,6 +138,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
map3dInitialized = true;
|
map3dInitialized = true;
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
// Initialize WebSocket connection
|
||||||
|
initWebSocket();
|
||||||
|
|
||||||
// Start BT live tracking by default after a short delay
|
// Start BT live tracking by default after a short delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
startLiveTracking();
|
startLiveTracking();
|
||||||
@@ -141,6 +148,152 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}, 2000);
|
}, 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
|
// Toggle filter
|
||||||
function toggleFilter(type) {
|
function toggleFilter(type) {
|
||||||
filters[type] = !filters[type];
|
filters[type] = !filters[type];
|
||||||
@@ -1925,17 +2078,23 @@ function toggleLiveTracking() {
|
|||||||
function startLiveTracking() {
|
function startLiveTracking() {
|
||||||
if (liveTrackingInterval) {
|
if (liveTrackingInterval) {
|
||||||
clearInterval(liveTrackingInterval);
|
clearInterval(liveTrackingInterval);
|
||||||
|
liveTrackingInterval = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
liveTrackingEnabled = true;
|
liveTrackingEnabled = true;
|
||||||
updateLiveTrackingUI();
|
updateLiveTrackingUI();
|
||||||
console.log('Live BT tracking started');
|
|
||||||
|
|
||||||
// Do initial scan
|
if (wsConnected) {
|
||||||
performLiveBTScan();
|
// WebSocket mode - updates come automatically via 'scanUpdate' events
|
||||||
|
// Still need to trigger initial scan
|
||||||
// Set up interval
|
performLiveBTScan();
|
||||||
liveTrackingInterval = setInterval(performLiveBTScan, LIVE_TRACKING_INTERVAL_MS);
|
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
|
// Stop live BT tracking
|
||||||
|
|||||||
@@ -166,5 +166,10 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
|
<!-- Socket.IO client -->
|
||||||
|
<script src="{{ url_for('static', filename='js/vendor/socket.io.min.js') }}"></script>
|
||||||
|
<!-- WebSocket client module -->
|
||||||
|
<script src="{{ url_for('static', filename='js/websocket.js') }}"></script>
|
||||||
|
<!-- Main application -->
|
||||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user