chore: Bump version to v2.18.0

Bluetooth enhancements (service data inspector, appearance codes, MAC
cluster tracking, behavioral flags, IRK badges, distance estimation),
ACARS SoapySDR multi-backend support, dump1090 stale process cleanup,
GPS error state, and proximity radar/signal card UI improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-16 15:12:10 +00:00
parent 2a73318457
commit 99d52eafe7
28 changed files with 1212 additions and 169 deletions

View File

@@ -24,7 +24,9 @@ from .constants import (
)
from .dbus_scanner import DBusScanner
from .fallback_scanner import FallbackScanner
from .ubertooth_scanner import UbertoothScanner
from .heuristics import HeuristicsEngine
from .irk_extractor import get_paired_irks
from .models import BTDeviceAggregate, BTObservation, ScanStatus, SystemCapabilities
logger = logging.getLogger(__name__)
@@ -57,6 +59,7 @@ class BluetoothScanner:
# Scanner backends
self._dbus_scanner: Optional[DBusScanner] = None
self._fallback_scanner: Optional[FallbackScanner] = None
self._ubertooth_scanner: Optional[UbertoothScanner] = None
self._active_backend: Optional[str] = None
# Event queue for SSE streaming
@@ -113,6 +116,8 @@ class BluetoothScanner:
if mode == 'dbus':
started, backend_used = self._start_dbus(adapter, transport, rssi_threshold)
elif mode == 'ubertooth':
started, backend_used = self._start_ubertooth()
# Fallback: try non-DBus methods if DBus failed or wasn't requested
if not started and (original_mode == 'auto' or mode in ('bleak', 'hcitool', 'bluetoothctl')):
@@ -168,6 +173,18 @@ class BluetoothScanner:
logger.warning(f"DBus scanner failed: {e}")
return False, None
def _start_ubertooth(self) -> tuple[bool, Optional[str]]:
"""Start Ubertooth One scanner."""
try:
self._ubertooth_scanner = UbertoothScanner(
on_observation=self._handle_observation,
)
if self._ubertooth_scanner.start():
return True, 'ubertooth'
except Exception as e:
logger.warning(f"Ubertooth scanner failed: {e}")
return False, None
def _start_fallback(self, adapter: str, preferred: str) -> tuple[bool, Optional[str]]:
"""Start fallback scanner."""
try:
@@ -204,6 +221,10 @@ class BluetoothScanner:
self._fallback_scanner.stop()
self._fallback_scanner = None
if self._ubertooth_scanner:
self._ubertooth_scanner.stop()
self._ubertooth_scanner = None
# Update status
self._status.is_scanning = False
self._active_backend = None
@@ -216,6 +237,47 @@ class BluetoothScanner:
logger.info("Bluetooth scan stopped")
def _match_irk(self, device: BTDeviceAggregate) -> None:
"""Check if a device address resolves against any paired IRK."""
if device.irk_hex is not None:
return # Already matched
address = device.address
if not address or len(address.replace(':', '').replace('-', '')) not in (12, 32):
return
# Only attempt RPA resolution on 6-byte addresses
addr_clean = address.replace(':', '').replace('-', '')
if len(addr_clean) != 12:
return
try:
paired = get_paired_irks()
except Exception:
return
if not paired:
return
try:
from utils.bt_locate import resolve_rpa
except ImportError:
return
for entry in paired:
irk_hex = entry.get('irk_hex', '')
if not irk_hex or len(irk_hex) != 32:
continue
try:
irk = bytes.fromhex(irk_hex)
if resolve_rpa(irk, address):
device.irk_hex = irk_hex
device.irk_source_name = entry.get('name')
logger.debug(f"IRK match for {address}: {entry.get('name', 'unnamed')}")
return
except Exception:
continue
def _handle_observation(self, observation: BTObservation) -> None:
"""Handle incoming observation from scanner backend."""
try:
@@ -225,15 +287,27 @@ class BluetoothScanner:
# Evaluate heuristics
self._heuristics.evaluate(device)
# Check for IRK match
self._match_irk(device)
# Update device count
with self._lock:
self._status.devices_found = self._aggregator.device_count
# Build summary with MAC cluster count
summary = device.to_summary_dict()
if device.payload_fingerprint_id:
summary['mac_cluster_count'] = self._aggregator.get_fingerprint_mac_count(
device.payload_fingerprint_id
)
else:
summary['mac_cluster_count'] = 0
# Queue event
self._queue_event({
'type': 'device',
'action': 'update',
'device': device.to_summary_dict(),
'device': summary,
})
# Callbacks
@@ -398,6 +472,7 @@ class BluetoothScanner:
backend_alive = (
(self._dbus_scanner and self._dbus_scanner.is_scanning)
or (self._fallback_scanner and self._fallback_scanner.is_scanning)
or (self._ubertooth_scanner and self._ubertooth_scanner.is_scanning)
)
if not backend_alive:
self._status.is_scanning = False