From 671bf3808334bfaacb554eea71892cfcd3984a29 Mon Sep 17 00:00:00 2001 From: Smittix Date: Tue, 17 Feb 2026 13:48:56 +0000 Subject: [PATCH] fix: Read WiFi/BT data from v2 scanners in analytics dashboard The analytics summary, health, and export were only reading from legacy DataStores (app_module.wifi_networks, bt_devices) which the v2 WiFi and Bluetooth scanners don't populate. Now checks v2 scanner singletons first and falls back to legacy stores. Co-Authored-By: Claude Opus 4.6 --- routes/analytics.py | 41 +++++++++++++++++++++++++------- utils/analytics.py | 57 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/routes/analytics.py b/routes/analytics.py index 6a5fe2b..39f3ca3 100644 --- a/routes/analytics.py +++ b/routes/analytics.py @@ -89,14 +89,39 @@ def analytics_export(mode: str): return jsonify({'status': 'error', 'message': f'Unknown mode: {mode}'}), 400 all_items: list[dict] = [] - for store_name in store_names: - store = getattr(app_module, store_name, None) - if store is None: - continue - for key, value in store.items(): - item = dict(value) if isinstance(value, dict) else {'id': key, 'value': value} - item.setdefault('_store', store_name) - all_items.append(item) + + # Try v2 scanners first for wifi/bluetooth + if mode == 'wifi': + try: + from utils.wifi.scanner import _scanner_instance as wifi_scanner + if wifi_scanner is not None: + for ap in wifi_scanner.access_points: + all_items.append(ap.to_dict()) + for client in wifi_scanner.clients: + item = client.to_dict() + item['_store'] = 'wifi_clients' + all_items.append(item) + except Exception: + pass + elif mode == 'bluetooth': + try: + from utils.bluetooth.scanner import _scanner_instance as bt_scanner + if bt_scanner is not None: + for dev in bt_scanner.get_devices(): + all_items.append(dev.to_dict()) + except Exception: + pass + + # Fall back to legacy DataStores if v2 scanners yielded nothing + if not all_items: + for store_name in store_names: + store = getattr(app_module, store_name, None) + if store is None: + continue + for key, value in store.items(): + item = dict(value) if isinstance(value, dict) else {'id': key, 'value': value} + item.setdefault('_store', store_name) + all_items.append(item) if fmt == 'csv': if not all_items: diff --git a/utils/analytics.py b/utils/analytics.py index 6f47e7c..fb3dc32 100644 --- a/utils/analytics.py +++ b/utils/analytics.py @@ -2,6 +2,7 @@ from __future__ import annotations +import contextlib import time from collections import deque from typing import Any @@ -54,7 +55,7 @@ def get_activity_tracker() -> ModeActivityTracker: def _get_mode_counts() -> dict[str, int]: - """Read current entity counts from app_module DataStores.""" + """Read current entity counts from DataStores and v2 scanners.""" counts: dict[str, int] = {} try: counts['adsb'] = len(app_module.adsb_aircraft) @@ -64,14 +65,33 @@ def _get_mode_counts() -> dict[str, int]: counts['ais'] = len(app_module.ais_vessels) except Exception: counts['ais'] = 0 + + # WiFi: prefer v2 scanner, fall back to legacy DataStore + wifi_count = 0 try: - counts['wifi'] = len(app_module.wifi_networks) + from utils.wifi.scanner import _scanner_instance as wifi_scanner + if wifi_scanner is not None: + wifi_count = len(wifi_scanner.access_points) except Exception: - counts['wifi'] = 0 + pass + if wifi_count == 0: + with contextlib.suppress(Exception): + wifi_count = len(app_module.wifi_networks) + counts['wifi'] = wifi_count + + # Bluetooth: prefer v2 scanner, fall back to legacy DataStore + bt_count = 0 try: - counts['bluetooth'] = len(app_module.bt_devices) + from utils.bluetooth.scanner import _scanner_instance as bt_scanner + if bt_scanner is not None: + bt_count = len(bt_scanner.get_devices()) except Exception: - counts['bluetooth'] = 0 + pass + if bt_count == 0: + with contextlib.suppress(Exception): + bt_count = len(app_module.bt_devices) + counts['bluetooth'] = bt_count + try: counts['dsc'] = len(app_module.dsc_messages) except Exception: @@ -80,12 +100,19 @@ def _get_mode_counts() -> dict[str, int]: def get_cross_mode_summary() -> dict[str, Any]: - """Return counts dict for all active DataStores.""" + """Return counts dict for all active DataStores and v2 scanners.""" counts = _get_mode_counts() + wifi_clients_count = 0 try: - counts['wifi_clients'] = len(app_module.wifi_clients) + from utils.wifi.scanner import _scanner_instance as wifi_scanner + if wifi_scanner is not None: + wifi_clients_count = len(wifi_scanner.clients) except Exception: - counts['wifi_clients'] = 0 + pass + if wifi_clients_count == 0: + with contextlib.suppress(Exception): + wifi_clients_count = len(app_module.wifi_clients) + counts['wifi_clients'] = wifi_clients_count return counts @@ -111,6 +138,20 @@ def get_mode_health() -> dict[str, dict]: running = proc is not None and (proc.poll() is None if proc else False) health[mode] = {'running': running} + # Override WiFi/BT health with v2 scanner status if available + try: + from utils.wifi.scanner import _scanner_instance as wifi_scanner + if wifi_scanner is not None and wifi_scanner.is_scanning: + health['wifi'] = {'running': True} + except Exception: + pass + try: + from utils.bluetooth.scanner import _scanner_instance as bt_scanner + if bt_scanner is not None and bt_scanner.is_scanning: + health['bluetooth'] = {'running': True} + except Exception: + pass + try: sdr_status = app_module.get_sdr_device_status() health['sdr_devices'] = {str(k): v for k, v in sdr_status.items()}