feat: auto-remove stale devices after 60s timeout
- Add deviceLastSeen tracking for all WiFi and BT devices - Add cleanupStaleDevices() that runs every 10 seconds - Remove devices not seen for 60 seconds (STALE_DEVICE_TIMEOUT_MS) - Works regardless of live tracking state - Updates lastSeen in all scan paths: manual, WS, polling, node switch Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,11 @@ let deviceDistanceHistory = {};
|
||||
let deviceMissCount = {};
|
||||
const MAX_MISSED_SCANS = 5; // Remove device after this many consecutive misses (~20s with 4s interval)
|
||||
|
||||
// Track last seen timestamp per device for timeout-based removal
|
||||
let deviceLastSeen = {}; // { address: timestamp_ms }
|
||||
const STALE_DEVICE_TIMEOUT_MS = 60000; // Remove devices not seen for 60 seconds
|
||||
let staleDeviceCleanupInterval = null;
|
||||
|
||||
// Calculate mean of array
|
||||
function mean(arr) {
|
||||
if (arr.length === 0) return 0;
|
||||
@@ -142,6 +147,81 @@ function filterScannerDevices(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Update last seen timestamp for a device
|
||||
function updateDeviceLastSeen(address) {
|
||||
deviceLastSeen[address] = Date.now();
|
||||
}
|
||||
|
||||
// Clean up stale devices that haven't been seen recently
|
||||
function cleanupStaleDevices() {
|
||||
if (!scanData) return;
|
||||
|
||||
const now = Date.now();
|
||||
let removedCount = 0;
|
||||
|
||||
// Clean up Bluetooth devices
|
||||
if (scanData.bluetooth_devices) {
|
||||
const before = scanData.bluetooth_devices.length;
|
||||
scanData.bluetooth_devices = scanData.bluetooth_devices.filter(dev => {
|
||||
const lastSeen = deviceLastSeen[dev.address];
|
||||
if (!lastSeen || (now - lastSeen) > STALE_DEVICE_TIMEOUT_MS) {
|
||||
// Clean up tracking data
|
||||
delete deviceMissCount[dev.address];
|
||||
delete deviceDistanceHistory[dev.address];
|
||||
delete deviceLastSeen[dev.address];
|
||||
if (deviceTrails[dev.address]) {
|
||||
clearDeviceTrail(dev.address);
|
||||
}
|
||||
console.log(`[Stale] Removed BT ${dev.name || dev.address} (timeout)`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
removedCount += before - scanData.bluetooth_devices.length;
|
||||
}
|
||||
|
||||
// Clean up WiFi networks
|
||||
if (scanData.wifi_networks) {
|
||||
const before = scanData.wifi_networks.length;
|
||||
scanData.wifi_networks = scanData.wifi_networks.filter(net => {
|
||||
const id = net.bssid || net.ssid;
|
||||
const lastSeen = deviceLastSeen[id];
|
||||
if (!lastSeen || (now - lastSeen) > STALE_DEVICE_TIMEOUT_MS) {
|
||||
delete deviceLastSeen[id];
|
||||
console.log(`[Stale] Removed WiFi ${net.ssid || net.bssid} (timeout)`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
removedCount += before - scanData.wifi_networks.length;
|
||||
}
|
||||
|
||||
// Update UI if devices were removed
|
||||
if (removedCount > 0) {
|
||||
document.getElementById('wifi-count').textContent = scanData.wifi_networks?.length || 0;
|
||||
document.getElementById('bt-count').textContent = scanData.bluetooth_devices?.length || 0;
|
||||
document.getElementById('bt-list-count').textContent = scanData.bluetooth_devices?.length || 0;
|
||||
drawRadar();
|
||||
update3DMarkers();
|
||||
updateMapMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
// Start stale device cleanup interval
|
||||
function startStaleDeviceCleanup() {
|
||||
if (staleDeviceCleanupInterval) return;
|
||||
staleDeviceCleanupInterval = setInterval(cleanupStaleDevices, 10000); // Check every 10 seconds
|
||||
console.log('[Cleanup] Stale device cleanup started (timeout: ' + (STALE_DEVICE_TIMEOUT_MS / 1000) + 's)');
|
||||
}
|
||||
|
||||
// Stop stale device cleanup interval
|
||||
function stopStaleDeviceCleanup() {
|
||||
if (staleDeviceCleanupInterval) {
|
||||
clearInterval(staleDeviceCleanupInterval);
|
||||
staleDeviceCleanupInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Device positions for hit detection (radar view)
|
||||
let devicePositions = [];
|
||||
|
||||
@@ -170,6 +250,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
loadAutoScanStatus();
|
||||
loadDevicePositions(); // Load saved manual positions
|
||||
loadTrilateratedPositions(); // Load multi-scanner trilaterated positions
|
||||
startStaleDeviceCleanup(); // Auto-remove devices not seen for 60s
|
||||
|
||||
// Initialize 3D map as default view
|
||||
setTimeout(() => {
|
||||
@@ -336,6 +417,7 @@ async function switchNode(nodeId) {
|
||||
|
||||
if (latestResp.ok) {
|
||||
scanData = filterScannerDevices(await latestResp.json());
|
||||
markAllDevicesSeen();
|
||||
if (floorsResp.ok) {
|
||||
const floorsData = await floorsResp.json();
|
||||
updateDeviceFloors(floorsData);
|
||||
@@ -434,6 +516,7 @@ function handleWebSocketScanUpdate(data) {
|
||||
|
||||
// Reset miss count - device was detected
|
||||
deviceMissCount[newDev.address] = 0;
|
||||
updateDeviceLastSeen(newDev.address);
|
||||
|
||||
if (existing) {
|
||||
// Update RSSI and estimated distance, preserve custom values
|
||||
@@ -465,6 +548,7 @@ function handleWebSocketScanUpdate(data) {
|
||||
// Clean up tracking data for removed device
|
||||
delete deviceMissCount[dev.address];
|
||||
delete deviceDistanceHistory[dev.address];
|
||||
delete deviceLastSeen[dev.address];
|
||||
// Clear trail if showing
|
||||
if (deviceTrails[dev.address]) {
|
||||
clearDeviceTrail(dev.address);
|
||||
@@ -483,6 +567,7 @@ function handleWebSocketScanUpdate(data) {
|
||||
isDeviceMoving(dev.address, dev.estimated_distance_m);
|
||||
dev.is_moving = false;
|
||||
deviceMissCount[dev.address] = 0;
|
||||
updateDeviceLastSeen(dev.address);
|
||||
});
|
||||
scanData = {
|
||||
wifi_networks: [],
|
||||
@@ -609,12 +694,25 @@ function setView(view) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all devices in scanData as seen now
|
||||
function markAllDevicesSeen() {
|
||||
if (!scanData) return;
|
||||
const now = Date.now();
|
||||
(scanData.wifi_networks || []).forEach(net => {
|
||||
deviceLastSeen[net.bssid || net.ssid] = now;
|
||||
});
|
||||
(scanData.bluetooth_devices || []).forEach(dev => {
|
||||
deviceLastSeen[dev.address] = now;
|
||||
});
|
||||
}
|
||||
|
||||
// Load latest scan
|
||||
async function loadLatestScan() {
|
||||
try {
|
||||
const response = await fetch('/api/latest');
|
||||
if (response.ok) {
|
||||
scanData = filterScannerDevices(await response.json());
|
||||
markAllDevicesSeen();
|
||||
updateUI();
|
||||
} else {
|
||||
document.getElementById('wifi-list').innerHTML = '<div style="color:#888;padding:1rem;">No scans yet. Click "New Scan" to start.</div>';
|
||||
@@ -651,6 +749,7 @@ async function triggerScan() {
|
||||
|
||||
if (response.ok) {
|
||||
scanData = filterScannerDevices(await response.json());
|
||||
markAllDevicesSeen();
|
||||
updateUI();
|
||||
status.textContent = `Scanned at ${new Date().toLocaleTimeString()}`;
|
||||
} else {
|
||||
@@ -2448,6 +2547,7 @@ async function performLiveBTScan() {
|
||||
|
||||
// Reset miss count - device was detected
|
||||
deviceMissCount[newDev.address] = 0;
|
||||
updateDeviceLastSeen(newDev.address);
|
||||
|
||||
if (existing) {
|
||||
// Update RSSI and estimated distance, preserve custom values
|
||||
@@ -2479,6 +2579,7 @@ async function performLiveBTScan() {
|
||||
// Clean up tracking data for removed device
|
||||
delete deviceMissCount[dev.address];
|
||||
delete deviceDistanceHistory[dev.address];
|
||||
delete deviceLastSeen[dev.address];
|
||||
// Clear trail if showing
|
||||
if (deviceTrails[dev.address]) {
|
||||
clearDeviceTrail(dev.address);
|
||||
@@ -2497,6 +2598,7 @@ async function performLiveBTScan() {
|
||||
isDeviceMoving(dev.address, dev.estimated_distance_m);
|
||||
dev.is_moving = false;
|
||||
deviceMissCount[dev.address] = 0;
|
||||
updateDeviceLastSeen(dev.address);
|
||||
});
|
||||
scanData = {
|
||||
wifi_networks: [],
|
||||
|
||||
Reference in New Issue
Block a user