diff --git a/src/rf_mapper/scanner.py b/src/rf_mapper/scanner.py index 4a17eb0..dc5d73a 100644 --- a/src/rf_mapper/scanner.py +++ b/src/rf_mapper/scanner.py @@ -4,12 +4,19 @@ import subprocess import re import json import asyncio +import os from datetime import datetime from pathlib import Path from dataclasses import dataclass, asdict from bleak import BleakScanner + +def is_termux() -> bool: + """Detect if running in Termux (Android).""" + return os.environ.get('TERMUX_VERSION') is not None or \ + os.path.exists('/data/data/com.termux') + from .oui import OUILookup from .bluetooth_class import BluetoothClassDecoder from .distance import estimate_distance, rssi_to_quality, rssi_bar @@ -184,91 +191,97 @@ class RFScanner: """ devices = [] - # Classic Bluetooth scan - try: - print(f"Scanning Classic Bluetooth ({timeout} seconds)...") - result = subprocess.run( - ['sudo', 'hcitool', 'inq', '--flush'], - capture_output=True, - text=True, - timeout=timeout + 10 - ) - - for line in result.stdout.split('\n'): - match = re.match( - r'\s*([0-9A-Fa-f:]+)\s+clock offset:\s*\S+\s+class:\s*(\S+)', - line - ) - if match: - addr = match.group(1) - device_class = match.group(2) - name = self._get_bt_name(addr) - rssi = self._get_bt_rssi(addr) - dev_type, dev_subtype = self.bt_decoder.decode(device_class) - - devices.append(BluetoothDevice( - address=addr, - name=name, - rssi=rssi, - device_class=device_class, - device_type=f"{dev_type}" + (f" ({dev_subtype})" if dev_subtype else ""), - manufacturer=self.oui_lookup.lookup(addr) - )) - except Exception as e: - print(f"Classic BT scan error: {e}") - - # BLE scan using bleak - try: - print(f"Scanning BLE devices ({timeout} seconds)...") - - async def _ble_scan(): - return await BleakScanner.discover( - timeout=timeout, - return_adv=True + # Classic Bluetooth scan (not supported on Termux/Android) + if is_termux(): + print("Skipping Classic BT scan (not supported on Termux)") + else: + try: + print(f"Scanning Classic Bluetooth ({timeout} seconds)...") + result = subprocess.run( + ['sudo', 'hcitool', 'inq', '--flush'], + capture_output=True, + text=True, + timeout=timeout + 10 ) - ble_results = asyncio.run(_ble_scan()) + for line in result.stdout.split('\n'): + match = re.match( + r'\s*([0-9A-Fa-f:]+)\s+clock offset:\s*\S+\s+class:\s*(\S+)', + line + ) + if match: + addr = match.group(1) + device_class = match.group(2) + name = self._get_bt_name(addr) + rssi = self._get_bt_rssi(addr) + dev_type, dev_subtype = self.bt_decoder.decode(device_class) - seen_addrs = {d.address for d in devices} - for device, adv_data in ble_results.values(): - addr = device.address - if addr not in seen_addrs and addr != 'LE': - seen_addrs.add(addr) + devices.append(BluetoothDevice( + address=addr, + name=name, + rssi=rssi, + device_class=device_class, + device_type=f"{dev_type}" + (f" ({dev_subtype})" if dev_subtype else ""), + manufacturer=self.oui_lookup.lookup(addr) + )) + except Exception as e: + print(f"Classic BT scan error: {e}") - name = device.name or adv_data.local_name or '' - rssi = adv_data.rssi # Real RSSI from advertisement - manufacturer = self.oui_lookup.lookup(addr) + # BLE scan using bleak (not supported on Termux/Android) + if is_termux(): + print("Skipping BLE scan (not supported on Termux)") + else: + try: + print(f"Scanning BLE devices ({timeout} seconds)...") - # Extract manufacturer data if available - if adv_data.manufacturer_data and not manufacturer: - # First 2 bytes are company ID - for company_id in adv_data.manufacturer_data.keys(): - manufacturer = f"Company ID: {company_id}" - break + async def _ble_scan(): + return await BleakScanner.discover( + timeout=timeout, + return_adv=True + ) - # Infer device type - inferred_type = infer_device_type_from_name(name) - if not inferred_type: - inferred_type = infer_device_type_from_manufacturer(manufacturer) + ble_results = asyncio.run(_ble_scan()) - if not inferred_type: - if is_random_mac(addr): - device_type = "BLE Device (Random MAC)" + seen_addrs = {d.address for d in devices} + for device, adv_data in ble_results.values(): + addr = device.address + if addr not in seen_addrs and addr != 'LE': + seen_addrs.add(addr) + + name = device.name or adv_data.local_name or '' + rssi = adv_data.rssi # Real RSSI from advertisement + manufacturer = self.oui_lookup.lookup(addr) + + # Extract manufacturer data if available + if adv_data.manufacturer_data and not manufacturer: + # First 2 bytes are company ID + for company_id in adv_data.manufacturer_data.keys(): + manufacturer = f"Company ID: {company_id}" + break + + # Infer device type + inferred_type = infer_device_type_from_name(name) + if not inferred_type: + inferred_type = infer_device_type_from_manufacturer(manufacturer) + + if not inferred_type: + if is_random_mac(addr): + device_type = "BLE Device (Random MAC)" + else: + device_type = "Low Energy Device" else: - device_type = "Low Energy Device" - else: - device_type = inferred_type + device_type = inferred_type - devices.append(BluetoothDevice( - address=addr, - name=name, - rssi=rssi, # Real RSSI instead of -70 - device_class="BLE", - device_type=device_type, - manufacturer=manufacturer - )) - except Exception as e: - print(f"BLE scan error: {e}") + devices.append(BluetoothDevice( + address=addr, + name=name, + rssi=rssi, # Real RSSI instead of -70 + device_class="BLE", + device_type=device_type, + manufacturer=manufacturer + )) + except Exception as e: + print(f"BLE scan error: {e}") # Auto-identify unknown devices if auto_identify and devices: