mirror of
https://github.com/smittix/intercept.git
synced 2026-07-19 06:48:11 -07:00
Fix BT/WiFi run-state health and BT Locate tracking continuity
This commit is contained in:
@@ -671,10 +671,66 @@ def _get_dmr_active() -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _get_bluetooth_health() -> tuple[bool, int]:
|
||||||
|
"""Return Bluetooth active state and best-effort device count."""
|
||||||
|
legacy_running = bt_process is not None and (bt_process.poll() is None if bt_process else False)
|
||||||
|
scanner_running = False
|
||||||
|
scanner_count = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
from utils.bluetooth.scanner import _scanner_instance as bt_scanner
|
||||||
|
if bt_scanner is not None:
|
||||||
|
scanner_running = bool(bt_scanner.is_scanning)
|
||||||
|
scanner_count = int(bt_scanner.device_count)
|
||||||
|
except Exception:
|
||||||
|
scanner_running = False
|
||||||
|
scanner_count = 0
|
||||||
|
|
||||||
|
locate_running = False
|
||||||
|
try:
|
||||||
|
from utils.bt_locate import get_locate_session
|
||||||
|
session = get_locate_session()
|
||||||
|
if session and getattr(session, 'active', False):
|
||||||
|
scanner = getattr(session, '_scanner', None)
|
||||||
|
locate_running = bool(scanner and scanner.is_scanning)
|
||||||
|
except Exception:
|
||||||
|
locate_running = False
|
||||||
|
|
||||||
|
return (legacy_running or scanner_running or locate_running), max(len(bt_devices), scanner_count)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_wifi_health() -> tuple[bool, int, int]:
|
||||||
|
"""Return WiFi active state and best-effort network/client counts."""
|
||||||
|
legacy_running = wifi_process is not None and (wifi_process.poll() is None if wifi_process else False)
|
||||||
|
scanner_running = False
|
||||||
|
scanner_networks = 0
|
||||||
|
scanner_clients = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
from utils.wifi.scanner import _scanner_instance as wifi_scanner
|
||||||
|
if wifi_scanner is not None:
|
||||||
|
status = wifi_scanner.get_status()
|
||||||
|
scanner_running = bool(status.is_scanning)
|
||||||
|
scanner_networks = int(status.networks_found or 0)
|
||||||
|
scanner_clients = int(status.clients_found or 0)
|
||||||
|
except Exception:
|
||||||
|
scanner_running = False
|
||||||
|
scanner_networks = 0
|
||||||
|
scanner_clients = 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
legacy_running or scanner_running,
|
||||||
|
max(len(wifi_networks), scanner_networks),
|
||||||
|
max(len(wifi_clients), scanner_clients),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/health')
|
@app.route('/health')
|
||||||
def health_check() -> Response:
|
def health_check() -> Response:
|
||||||
"""Health check endpoint for monitoring."""
|
"""Health check endpoint for monitoring."""
|
||||||
import time
|
import time
|
||||||
|
bt_active, bt_device_count = _get_bluetooth_health()
|
||||||
|
wifi_active, wifi_network_count, wifi_client_count = _get_wifi_health()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'healthy',
|
'status': 'healthy',
|
||||||
'version': VERSION,
|
'version': VERSION,
|
||||||
@@ -687,8 +743,8 @@ def health_check() -> Response:
|
|||||||
'acars': acars_process is not None and (acars_process.poll() is None if acars_process else False),
|
'acars': acars_process is not None and (acars_process.poll() is None if acars_process else False),
|
||||||
'vdl2': vdl2_process is not None and (vdl2_process.poll() is None if vdl2_process else False),
|
'vdl2': vdl2_process is not None and (vdl2_process.poll() is None if vdl2_process else False),
|
||||||
'aprs': aprs_process is not None and (aprs_process.poll() is None if aprs_process else False),
|
'aprs': aprs_process is not None and (aprs_process.poll() is None if aprs_process else False),
|
||||||
'wifi': wifi_process is not None and (wifi_process.poll() is None if wifi_process else False),
|
'wifi': wifi_active,
|
||||||
'bluetooth': bt_process is not None and (bt_process.poll() is None if bt_process else False),
|
'bluetooth': bt_active,
|
||||||
'dsc': dsc_process is not None and (dsc_process.poll() is None if dsc_process else False),
|
'dsc': dsc_process is not None and (dsc_process.poll() is None if dsc_process else False),
|
||||||
'dmr': _get_dmr_active(),
|
'dmr': _get_dmr_active(),
|
||||||
'subghz': _get_subghz_active(),
|
'subghz': _get_subghz_active(),
|
||||||
@@ -696,9 +752,9 @@ def health_check() -> Response:
|
|||||||
'data': {
|
'data': {
|
||||||
'aircraft_count': len(adsb_aircraft),
|
'aircraft_count': len(adsb_aircraft),
|
||||||
'vessel_count': len(ais_vessels),
|
'vessel_count': len(ais_vessels),
|
||||||
'wifi_networks_count': len(wifi_networks),
|
'wifi_networks_count': wifi_network_count,
|
||||||
'wifi_clients_count': len(wifi_clients),
|
'wifi_clients_count': wifi_client_count,
|
||||||
'bt_devices_count': len(bt_devices),
|
'bt_devices_count': bt_device_count,
|
||||||
'dsc_messages_count': len(dsc_messages),
|
'dsc_messages_count': len(dsc_messages),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+18
-2
@@ -38,6 +38,8 @@ def start_session():
|
|||||||
- name_pattern: Target name substring (optional)
|
- name_pattern: Target name substring (optional)
|
||||||
- irk_hex: Identity Resolving Key hex string (optional)
|
- irk_hex: Identity Resolving Key hex string (optional)
|
||||||
- device_id: Device ID from Bluetooth scanner (optional)
|
- device_id: Device ID from Bluetooth scanner (optional)
|
||||||
|
- device_key: Stable device key from Bluetooth scanner (optional)
|
||||||
|
- fingerprint_id: Payload fingerprint ID from Bluetooth scanner (optional)
|
||||||
- known_name: Hand-off device name (optional)
|
- known_name: Hand-off device name (optional)
|
||||||
- known_manufacturer: Hand-off manufacturer (optional)
|
- known_manufacturer: Hand-off manufacturer (optional)
|
||||||
- last_known_rssi: Hand-off last RSSI (optional)
|
- last_known_rssi: Hand-off last RSSI (optional)
|
||||||
@@ -55,14 +57,28 @@ def start_session():
|
|||||||
name_pattern=data.get('name_pattern'),
|
name_pattern=data.get('name_pattern'),
|
||||||
irk_hex=data.get('irk_hex'),
|
irk_hex=data.get('irk_hex'),
|
||||||
device_id=data.get('device_id'),
|
device_id=data.get('device_id'),
|
||||||
|
device_key=data.get('device_key'),
|
||||||
|
fingerprint_id=data.get('fingerprint_id'),
|
||||||
known_name=data.get('known_name'),
|
known_name=data.get('known_name'),
|
||||||
known_manufacturer=data.get('known_manufacturer'),
|
known_manufacturer=data.get('known_manufacturer'),
|
||||||
last_known_rssi=data.get('last_known_rssi'),
|
last_known_rssi=data.get('last_known_rssi'),
|
||||||
)
|
)
|
||||||
|
|
||||||
# At least one identifier required
|
# At least one identifier required
|
||||||
if not any([target.mac_address, target.name_pattern, target.irk_hex, target.device_id]):
|
if not any([
|
||||||
return jsonify({'error': 'At least one target identifier required (mac_address, name_pattern, irk_hex, or device_id)'}), 400
|
target.mac_address,
|
||||||
|
target.name_pattern,
|
||||||
|
target.irk_hex,
|
||||||
|
target.device_id,
|
||||||
|
target.device_key,
|
||||||
|
target.fingerprint_id,
|
||||||
|
]):
|
||||||
|
return jsonify({
|
||||||
|
'error': (
|
||||||
|
'At least one target identifier required '
|
||||||
|
'(mac_address, name_pattern, irk_hex, device_id, device_key, or fingerprint_id)'
|
||||||
|
)
|
||||||
|
}), 400
|
||||||
|
|
||||||
# Parse environment
|
# Parse environment
|
||||||
env_str = data.get('environment', 'OUTDOOR').upper()
|
env_str = data.get('environment', 'OUTDOOR').upper()
|
||||||
|
|||||||
@@ -1621,6 +1621,7 @@ const BluetoothMode = (function() {
|
|||||||
if (typeof BtLocate !== 'undefined') {
|
if (typeof BtLocate !== 'undefined') {
|
||||||
BtLocate.handoff({
|
BtLocate.handoff({
|
||||||
device_id: device.device_id,
|
device_id: device.device_id,
|
||||||
|
device_key: device.device_key || null,
|
||||||
mac_address: device.address,
|
mac_address: device.address,
|
||||||
address_type: device.address_type || null,
|
address_type: device.address_type || null,
|
||||||
irk_hex: device.irk_hex || null,
|
irk_hex: device.irk_hex || null,
|
||||||
@@ -1629,7 +1630,7 @@ const BluetoothMode = (function() {
|
|||||||
last_known_rssi: device.rssi_current,
|
last_known_rssi: device.rssi_current,
|
||||||
tx_power: device.tx_power || null,
|
tx_power: device.tx_power || null,
|
||||||
appearance_name: device.appearance_name || null,
|
appearance_name: device.appearance_name || null,
|
||||||
fingerprint_id: device.fingerprint_id || null,
|
fingerprint_id: device.fingerprint_id || device.fingerprint?.id || null,
|
||||||
mac_cluster_count: device.mac_cluster_count || 0
|
mac_cluster_count: device.mac_cluster_count || 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ const BtLocate = (function() {
|
|||||||
if (namePattern) body.name_pattern = namePattern;
|
if (namePattern) body.name_pattern = namePattern;
|
||||||
if (irk) body.irk_hex = irk;
|
if (irk) body.irk_hex = irk;
|
||||||
if (handoffData?.device_id) body.device_id = handoffData.device_id;
|
if (handoffData?.device_id) body.device_id = handoffData.device_id;
|
||||||
|
if (handoffData?.device_key) body.device_key = handoffData.device_key;
|
||||||
|
if (handoffData?.fingerprint_id) body.fingerprint_id = handoffData.fingerprint_id;
|
||||||
if (handoffData?.known_name) body.known_name = handoffData.known_name;
|
if (handoffData?.known_name) body.known_name = handoffData.known_name;
|
||||||
if (handoffData?.known_manufacturer) body.known_manufacturer = handoffData.known_manufacturer;
|
if (handoffData?.known_manufacturer) body.known_manufacturer = handoffData.known_manufacturer;
|
||||||
if (handoffData?.last_known_rssi) body.last_known_rssi = handoffData.last_known_rssi;
|
if (handoffData?.last_known_rssi) body.last_known_rssi = handoffData.last_known_rssi;
|
||||||
@@ -159,8 +161,9 @@ const BtLocate = (function() {
|
|||||||
|
|
||||||
console.log('[BtLocate] Starting with body:', body);
|
console.log('[BtLocate] Starting with body:', body);
|
||||||
|
|
||||||
if (!body.mac_address && !body.name_pattern && !body.irk_hex && !body.device_id) {
|
if (!body.mac_address && !body.name_pattern && !body.irk_hex &&
|
||||||
alert('Please provide at least a MAC address, name pattern, IRK, or use hand-off from Bluetooth mode.');
|
!body.device_id && !body.device_key && !body.fingerprint_id) {
|
||||||
|
alert('Please provide at least one target identifier or use hand-off from Bluetooth mode.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +258,9 @@ const BtLocate = (function() {
|
|||||||
|
|
||||||
eventSource.onerror = function() {
|
eventSource.onerror = function() {
|
||||||
console.warn('[BtLocate] SSE error, polling fallback active');
|
console.warn('[BtLocate] SSE error, polling fallback active');
|
||||||
|
if (eventSource && eventSource.readyState === EventSource.CLOSED) {
|
||||||
|
eventSource = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start polling fallback (catches data even if SSE fails)
|
// Start polling fallback (catches data even if SSE fails)
|
||||||
@@ -318,6 +324,11 @@ const BtLocate = (function() {
|
|||||||
updateScanStatus(data);
|
updateScanStatus(data);
|
||||||
updateHudInfo(data);
|
updateHudInfo(data);
|
||||||
|
|
||||||
|
// Recover live stream if browser closed SSE connection.
|
||||||
|
if (!eventSource || eventSource.readyState === EventSource.CLOSED) {
|
||||||
|
connectSSE();
|
||||||
|
}
|
||||||
|
|
||||||
// Show diagnostics
|
// Show diagnostics
|
||||||
const diagEl = document.getElementById('btLocateDiag');
|
const diagEl = document.getElementById('btLocateDiag');
|
||||||
if (diagEl) {
|
if (diagEl) {
|
||||||
|
|||||||
@@ -3910,7 +3910,10 @@
|
|||||||
const btScanActive = (typeof BluetoothMode !== 'undefined' &&
|
const btScanActive = (typeof BluetoothMode !== 'undefined' &&
|
||||||
typeof BluetoothMode.isScanning === 'function' &&
|
typeof BluetoothMode.isScanning === 'function' &&
|
||||||
BluetoothMode.isScanning()) || isBtRunning;
|
BluetoothMode.isScanning()) || isBtRunning;
|
||||||
if (btScanActive && typeof stopBtScan === 'function') stopBtScan();
|
const isBtModeTransition =
|
||||||
|
(currentMode === 'bluetooth' && mode === 'bt_locate') ||
|
||||||
|
(currentMode === 'bt_locate' && mode === 'bluetooth');
|
||||||
|
if (btScanActive && !isBtModeTransition && typeof stopBtScan === 'function') stopBtScan();
|
||||||
if (isAprsRunning) stopAprs();
|
if (isAprsRunning) stopAprs();
|
||||||
if (isTscmRunning) stopTscmSweep();
|
if (isTscmRunning) stopTscmSweep();
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-2
@@ -88,6 +88,8 @@ class LocateTarget:
|
|||||||
name_pattern: str | None = None
|
name_pattern: str | None = None
|
||||||
irk_hex: str | None = None
|
irk_hex: str | None = None
|
||||||
device_id: str | None = None
|
device_id: str | None = None
|
||||||
|
device_key: str | None = None
|
||||||
|
fingerprint_id: str | None = None
|
||||||
# Hand-off metadata from Bluetooth mode
|
# Hand-off metadata from Bluetooth mode
|
||||||
known_name: str | None = None
|
known_name: str | None = None
|
||||||
known_manufacturer: str | None = None
|
known_manufacturer: str | None = None
|
||||||
@@ -95,6 +97,10 @@ class LocateTarget:
|
|||||||
|
|
||||||
def matches(self, device: BTDeviceAggregate) -> bool:
|
def matches(self, device: BTDeviceAggregate) -> bool:
|
||||||
"""Check if a device matches this target."""
|
"""Check if a device matches this target."""
|
||||||
|
# Match by stable device key (survives MAC randomization for many devices)
|
||||||
|
if self.device_key and getattr(device, 'device_key', None) == self.device_key:
|
||||||
|
return True
|
||||||
|
|
||||||
# Match by device_id (exact)
|
# Match by device_id (exact)
|
||||||
if self.device_id and device.device_id == self.device_id:
|
if self.device_id and device.device_id == self.device_id:
|
||||||
return True
|
return True
|
||||||
@@ -113,6 +119,13 @@ class LocateTarget:
|
|||||||
if dev_addr == target_addr:
|
if dev_addr == target_addr:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Match by payload fingerprint (guard against low-stability generic fingerprints)
|
||||||
|
if self.fingerprint_id:
|
||||||
|
dev_fp = getattr(device, 'payload_fingerprint_id', None)
|
||||||
|
dev_fp_stability = getattr(device, 'payload_fingerprint_stability', 0.0) or 0.0
|
||||||
|
if dev_fp and dev_fp == self.fingerprint_id and dev_fp_stability >= 0.35:
|
||||||
|
return True
|
||||||
|
|
||||||
# Match by RPA resolution
|
# Match by RPA resolution
|
||||||
if self.irk_hex:
|
if self.irk_hex:
|
||||||
try:
|
try:
|
||||||
@@ -126,8 +139,18 @@ class LocateTarget:
|
|||||||
if self.name_pattern and device.name and self.name_pattern.lower() in device.name.lower():
|
if self.name_pattern and device.name and self.name_pattern.lower() in device.name.lower():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Match by known_name from handoff (exact name match)
|
# Match by known_name from handoff (exact or loose normalized match)
|
||||||
return bool(self.known_name and device.name and self.known_name.lower() == device.name.lower())
|
if self.known_name and device.name:
|
||||||
|
target_name = self.known_name.strip().lower()
|
||||||
|
device_name = device.name.strip().lower()
|
||||||
|
if target_name and (
|
||||||
|
target_name == device_name
|
||||||
|
or target_name in device_name
|
||||||
|
or device_name in target_name
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return {
|
return {
|
||||||
@@ -135,6 +158,8 @@ class LocateTarget:
|
|||||||
'name_pattern': self.name_pattern,
|
'name_pattern': self.name_pattern,
|
||||||
'irk_hex': self.irk_hex,
|
'irk_hex': self.irk_hex,
|
||||||
'device_id': self.device_id,
|
'device_id': self.device_id,
|
||||||
|
'device_key': self.device_key,
|
||||||
|
'fingerprint_id': self.fingerprint_id,
|
||||||
'known_name': self.known_name,
|
'known_name': self.known_name,
|
||||||
'known_manufacturer': self.known_manufacturer,
|
'known_manufacturer': self.known_manufacturer,
|
||||||
'last_known_rssi': self.last_known_rssi,
|
'last_known_rssi': self.last_known_rssi,
|
||||||
|
|||||||
Reference in New Issue
Block a user