feat: filter scanner Bluetooth devices from display
- Add bt_mac field to scanner config for identifying scanner BT adapters - Store bt_mac in peers table for peer scanners - Filter out devices matching scanner BT MACs from all views - Prevents scanners from appearing as devices in device lists/maps Config: scanner.bt_mac = "XX:XX:XX:XX:XX:XX" API: /api/peers/register accepts bt_mac field Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,12 +6,13 @@ web:
|
||||
port: 5000
|
||||
debug: false
|
||||
scanner:
|
||||
id: ''
|
||||
name: ''
|
||||
id: rpios
|
||||
name: rpios
|
||||
latitude: null
|
||||
longitude: null
|
||||
floor: null
|
||||
is_master: true
|
||||
bt_mac: '2C:CF:67:6F:66:AC'
|
||||
wifi_interface: wlan0
|
||||
bt_scan_timeout: 10
|
||||
path_loss_exponent: 2.5
|
||||
|
||||
@@ -29,6 +29,7 @@ class ScannerConfig:
|
||||
longitude: float | None = None # Scanner position (falls back to gps.longitude)
|
||||
floor: int | None = None # Scanner's floor (falls back to building.current_floor)
|
||||
is_master: bool = False # Master node can view other nodes' data in dashboard
|
||||
bt_mac: str = "" # Scanner's Bluetooth MAC address (for filtering from device lists)
|
||||
|
||||
# Scanning configuration
|
||||
wifi_interface: str = "wlan0"
|
||||
@@ -181,6 +182,7 @@ class Config:
|
||||
longitude=data["scanner"].get("longitude", config.scanner.longitude),
|
||||
floor=data["scanner"].get("floor", config.scanner.floor),
|
||||
is_master=data["scanner"].get("is_master", config.scanner.is_master),
|
||||
bt_mac=data["scanner"].get("bt_mac", config.scanner.bt_mac),
|
||||
# Scanning configuration
|
||||
wifi_interface=data["scanner"].get("wifi_interface", config.scanner.wifi_interface),
|
||||
bt_scan_timeout=data["scanner"].get("bt_scan_timeout", config.scanner.bt_scan_timeout),
|
||||
@@ -297,6 +299,7 @@ class Config:
|
||||
- latitude: Scanner position (from scanner config or gps config)
|
||||
- longitude: Scanner position (from scanner config or gps config)
|
||||
- floor: Scanner's floor (from scanner config or building config)
|
||||
- bt_mac: Bluetooth MAC address (for filtering from device lists)
|
||||
"""
|
||||
import socket
|
||||
|
||||
@@ -306,7 +309,8 @@ class Config:
|
||||
"name": self.scanner.name or scanner_id,
|
||||
"latitude": self.scanner.latitude if self.scanner.latitude is not None else self.gps.latitude,
|
||||
"longitude": self.scanner.longitude if self.scanner.longitude is not None else self.gps.longitude,
|
||||
"floor": self.scanner.floor if self.scanner.floor is not None else self.building.current_floor
|
||||
"floor": self.scanner.floor if self.scanner.floor is not None else self.building.current_floor,
|
||||
"bt_mac": self.scanner.bt_mac or None
|
||||
}
|
||||
|
||||
def save(self, path: Path | None = None):
|
||||
|
||||
@@ -210,6 +210,12 @@ class DeviceDatabase:
|
||||
)
|
||||
""")
|
||||
|
||||
# Add bt_mac column to peers table if missing (for scanner BT filtering)
|
||||
try:
|
||||
cursor.execute("ALTER TABLE peers ADD COLUMN bt_mac TEXT")
|
||||
except sqlite3.OperationalError:
|
||||
pass # Column already exists
|
||||
|
||||
# Add notes column to devices table if missing (for sync)
|
||||
try:
|
||||
cursor.execute("ALTER TABLE devices ADD COLUMN notes TEXT")
|
||||
@@ -941,7 +947,7 @@ class DeviceDatabase:
|
||||
|
||||
def register_peer(self, scanner_id: str, name: str, url: str,
|
||||
floor: Optional[int] = None, latitude: Optional[float] = None,
|
||||
longitude: Optional[float] = None) -> bool:
|
||||
longitude: Optional[float] = None, bt_mac: Optional[str] = None) -> bool:
|
||||
"""Register a peer scanner.
|
||||
|
||||
Args:
|
||||
@@ -951,6 +957,7 @@ class DeviceDatabase:
|
||||
floor: Floor where peer scanner is located
|
||||
latitude: GPS latitude of peer
|
||||
longitude: GPS longitude of peer
|
||||
bt_mac: Bluetooth MAC address of the scanner (for filtering from device lists)
|
||||
|
||||
Returns:
|
||||
True if newly registered, False if updated existing
|
||||
@@ -964,16 +971,17 @@ class DeviceDatabase:
|
||||
exists = cursor.fetchone() is not None
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO peers (scanner_id, name, url, floor, latitude, longitude, last_seen, registered_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO peers (scanner_id, name, url, floor, latitude, longitude, bt_mac, last_seen, registered_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(scanner_id) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
url = excluded.url,
|
||||
floor = excluded.floor,
|
||||
latitude = excluded.latitude,
|
||||
longitude = excluded.longitude,
|
||||
bt_mac = COALESCE(excluded.bt_mac, peers.bt_mac),
|
||||
last_seen = excluded.last_seen
|
||||
""", (scanner_id, name, url, floor, latitude, longitude, timestamp, timestamp))
|
||||
""", (scanner_id, name, url, floor, latitude, longitude, bt_mac, timestamp, timestamp))
|
||||
|
||||
conn.commit()
|
||||
return not exists
|
||||
|
||||
@@ -1562,7 +1562,8 @@ def create_app(config: Config | None = None) -> Flask:
|
||||
url=peer_url,
|
||||
floor=data.get("floor"),
|
||||
latitude=data.get("latitude"),
|
||||
longitude=data.get("longitude")
|
||||
longitude=data.get("longitude"),
|
||||
bt_mac=data.get("bt_mac")
|
||||
)
|
||||
|
||||
action = "registered" if is_new else "updated"
|
||||
|
||||
@@ -24,6 +24,9 @@ let deviceSources = {}; // { deviceId: { scanner_id, lat, lon } }
|
||||
// Peer scanner positions - loaded from /api/peers (live positions)
|
||||
let peerScanners = {}; // { scanner_id: { lat, lon, floor, name } }
|
||||
|
||||
// Scanner Bluetooth MACs - for filtering scanners from device lists
|
||||
let scannerBtMacs = new Set(); // Set of BT MAC addresses belonging to scanners
|
||||
|
||||
// Trilateration state - positions calculated from multiple scanner RSSI
|
||||
let trilateratedPositions = {}; // { deviceId: { lat, lon, confidence, scanners, method } }
|
||||
let trilaterationEnabled = true;
|
||||
@@ -120,6 +123,25 @@ function isDeviceMoving(address, newDistance) {
|
||||
return isMoving;
|
||||
}
|
||||
|
||||
// Filter out scanner Bluetooth devices from scan data
|
||||
// Removes devices whose address matches a known scanner's BT MAC
|
||||
function filterScannerDevices(data) {
|
||||
if (!data || scannerBtMacs.size === 0) return data;
|
||||
|
||||
if (data.bluetooth_devices) {
|
||||
const before = data.bluetooth_devices.length;
|
||||
data.bluetooth_devices = data.bluetooth_devices.filter(dev => {
|
||||
const addr = (dev.address || '').toUpperCase();
|
||||
return !scannerBtMacs.has(addr);
|
||||
});
|
||||
const filtered = before - data.bluetooth_devices.length;
|
||||
if (filtered > 0) {
|
||||
console.log(`[Filter] Removed ${filtered} scanner BT device(s)`);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Device positions for hit detection (radar view)
|
||||
let devicePositions = [];
|
||||
|
||||
@@ -313,7 +335,7 @@ async function switchNode(nodeId) {
|
||||
]);
|
||||
|
||||
if (latestResp.ok) {
|
||||
scanData = await latestResp.json();
|
||||
scanData = filterScannerDevices(await latestResp.json());
|
||||
if (floorsResp.ok) {
|
||||
const floorsData = await floorsResp.json();
|
||||
updateDeviceFloors(floorsData);
|
||||
@@ -390,7 +412,11 @@ function handleWebSocketScanUpdate(data) {
|
||||
|
||||
// Handle Bluetooth scan results
|
||||
if (data.type === 'bluetooth' && data.devices) {
|
||||
const newBt = data.devices;
|
||||
// Filter out scanner Bluetooth devices
|
||||
const newBt = data.devices.filter(dev => {
|
||||
const addr = (dev.address || '').toUpperCase();
|
||||
return !scannerBtMacs.has(addr);
|
||||
});
|
||||
|
||||
// Track which devices were detected in this scan
|
||||
const detectedAddresses = new Set(newBt.map(d => d.address));
|
||||
@@ -588,7 +614,7 @@ async function loadLatestScan() {
|
||||
try {
|
||||
const response = await fetch('/api/latest');
|
||||
if (response.ok) {
|
||||
scanData = await response.json();
|
||||
scanData = filterScannerDevices(await response.json());
|
||||
updateUI();
|
||||
} else {
|
||||
document.getElementById('wifi-list').innerHTML = '<div style="color:#888;padding:1rem;">No scans yet. Click "New Scan" to start.</div>';
|
||||
@@ -624,7 +650,7 @@ async function triggerScan() {
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
scanData = await response.json();
|
||||
scanData = filterScannerDevices(await response.json());
|
||||
updateUI();
|
||||
status.textContent = `Scanned at ${new Date().toLocaleTimeString()}`;
|
||||
} else {
|
||||
@@ -1435,11 +1461,18 @@ async function loadDevicePositions() {
|
||||
}
|
||||
}
|
||||
|
||||
// Load peer scanner positions (live/current positions)
|
||||
// Load peer scanner positions (live/current positions) and BT MACs
|
||||
const peersResponse = await fetch('/api/peers');
|
||||
if (peersResponse.ok) {
|
||||
const peersData = await peersResponse.json();
|
||||
peerScanners = {};
|
||||
scannerBtMacs = new Set();
|
||||
|
||||
// Add local scanner's BT MAC if available
|
||||
if (peersData.this_scanner?.bt_mac) {
|
||||
scannerBtMacs.add(peersData.this_scanner.bt_mac.toUpperCase());
|
||||
}
|
||||
|
||||
(peersData.peers || []).forEach(peer => {
|
||||
peerScanners[peer.scanner_id] = {
|
||||
lat: peer.latitude,
|
||||
@@ -1447,8 +1480,12 @@ async function loadDevicePositions() {
|
||||
floor: peer.floor,
|
||||
name: peer.name
|
||||
};
|
||||
// Collect peer BT MACs for filtering
|
||||
if (peer.bt_mac) {
|
||||
scannerBtMacs.add(peer.bt_mac.toUpperCase());
|
||||
}
|
||||
});
|
||||
console.log('[Peers] Loaded', Object.keys(peerScanners).length, 'peer positions');
|
||||
console.log('[Peers] Loaded', Object.keys(peerScanners).length, 'peer positions,', scannerBtMacs.size, 'scanner BT MACs');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading device positions:', error);
|
||||
@@ -2391,7 +2428,11 @@ async function performLiveBTScan() {
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const newBt = data.bluetooth_devices || [];
|
||||
// Filter out scanner Bluetooth devices
|
||||
const newBt = (data.bluetooth_devices || []).filter(dev => {
|
||||
const addr = (dev.address || '').toUpperCase();
|
||||
return !scannerBtMacs.has(addr);
|
||||
});
|
||||
|
||||
// Track which devices were detected in this scan
|
||||
const detectedAddresses = new Set(newBt.map(d => d.address));
|
||||
@@ -2453,8 +2494,8 @@ async function performLiveBTScan() {
|
||||
|
||||
scanData.bluetooth_devices = filteredBt;
|
||||
} else {
|
||||
// No existing scan data, use BT-only data
|
||||
data.bluetooth_devices.forEach(dev => {
|
||||
// No existing scan data, use BT-only data (already filtered above)
|
||||
newBt.forEach(dev => {
|
||||
// Initialize history with first sample, not moving yet
|
||||
isDeviceMoving(dev.address, dev.estimated_distance_m);
|
||||
dev.is_moving = false;
|
||||
@@ -2462,7 +2503,7 @@ async function performLiveBTScan() {
|
||||
});
|
||||
scanData = {
|
||||
wifi_networks: [],
|
||||
bluetooth_devices: data.bluetooth_devices,
|
||||
bluetooth_devices: newBt,
|
||||
timestamp: data.timestamp
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user