Fix floor persistence and improve movement detection

- Store floor assignments in SQLite database for persistence
- Floor now saves correctly for live-tracked BT devices
- Add statistical movement detection (5-sample average + stddev)
- Require 1.5m + 2σ deviation to mark device as moving
- Reduces false positives from RSSI noise/fluctuations
- Add /api/device/floors endpoint for bulk floor retrieval

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
User
2026-02-01 00:34:49 +01:00
parent 0e99232582
commit dda8455813
3 changed files with 154 additions and 48 deletions

View File

@@ -21,9 +21,72 @@ let liveTrackingEnabled = false;
let liveTrackingInterval = null;
const LIVE_TRACKING_INTERVAL_MS = 4000; // 4 seconds
// Track previous distances for movement detection
let previousDistances = {};
const MOVEMENT_THRESHOLD = 0.5; // meters - consider moving if distance changed by this much
// 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
const MIN_SAMPLES_FOR_MOVEMENT = 3; // Need at least this many samples before detecting movement
// Store distance history per device: { address: { samples: [], timestamps: [] } }
let deviceDistanceHistory = {};
// Calculate mean of array
function mean(arr) {
if (arr.length === 0) return 0;
return arr.reduce((a, b) => a + b, 0) / arr.length;
}
// Calculate standard deviation
function stddev(arr) {
if (arr.length < 2) return 0;
const avg = mean(arr);
const squareDiffs = arr.map(x => Math.pow(x - avg, 2));
return Math.sqrt(mean(squareDiffs));
}
// Check if device is moving based on statistical analysis
function isDeviceMoving(address, newDistance) {
// Initialize history if needed
if (!deviceDistanceHistory[address]) {
deviceDistanceHistory[address] = { samples: [], timestamps: [] };
}
const history = deviceDistanceHistory[address];
const now = Date.now();
// Add new sample
history.samples.push(newDistance);
history.timestamps.push(now);
// Remove old samples (keep last SAMPLE_HISTORY_SIZE)
while (history.samples.length > SAMPLE_HISTORY_SIZE) {
history.samples.shift();
history.timestamps.shift();
}
// Need minimum samples to determine movement
if (history.samples.length < MIN_SAMPLES_FOR_MOVEMENT) {
return false;
}
// Calculate statistics from older samples (exclude the newest one)
const olderSamples = history.samples.slice(0, -1);
const avg = mean(olderSamples);
const sd = stddev(olderSamples);
// Movement threshold = base threshold + 2 standard deviations (95% confidence)
const dynamicThreshold = MOVEMENT_THRESHOLD + (2 * sd);
// Check if current reading deviates significantly from the average
const deviation = Math.abs(newDistance - avg);
const isMoving = deviation > dynamicThreshold;
// Debug log for significant movements
if (isMoving) {
console.log(`[Movement] ${address}: dist=${newDistance.toFixed(2)}m, avg=${avg.toFixed(2)}m, sd=${sd.toFixed(2)}, threshold=${dynamicThreshold.toFixed(2)}m`);
}
return isMoving;
}
// Device positions for hit detection (radar view)
let devicePositions = [];
@@ -1485,23 +1548,19 @@ async function performLiveBTScan() {
const existing = existingBt.find(d => d.address === newDev.address);
const newDist = newDev.estimated_distance_m;
// Check for movement
const prevDist = previousDistances[newDev.address];
const isMoving = prevDist !== undefined && Math.abs(newDist - prevDist) > MOVEMENT_THRESHOLD;
// Store current distance for next comparison
previousDistances[newDev.address] = newDist;
// Check for movement using statistical analysis
const moving = isDeviceMoving(newDev.address, newDist);
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 = isMoving;
existing.is_moving = moving;
// Preserve floor and custom_distance_m if set
} else {
// New device, add it
newDev.is_moving = isMoving;
newDev.is_moving = moving;
existingBt.push(newDev);
}
});
@@ -1510,7 +1569,8 @@ async function performLiveBTScan() {
} else {
// No existing scan data, use BT-only data
data.bluetooth_devices.forEach(dev => {
previousDistances[dev.address] = dev.estimated_distance_m;
// Initialize history with first sample, not moving yet
isDeviceMoving(dev.address, dev.estimated_distance_m);
dev.is_moving = false;
});
scanData = {