From 3fa4d5742a8458be388a9d4d15382bdd1ea7ee31 Mon Sep 17 00:00:00 2001 From: infinition Date: Mon, 16 Mar 2026 22:09:51 +0100 Subject: [PATCH] Update BluetoothUtils: enhance scanning duration, improve service checks, and add adapter reset functionality --- web_utils/bluetooth_utils.py | 48 ++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/web_utils/bluetooth_utils.py b/web_utils/bluetooth_utils.py index f44643b..f6cf219 100644 --- a/web_utils/bluetooth_utils.py +++ b/web_utils/bluetooth_utils.py @@ -19,7 +19,7 @@ from logger import Logger logger = Logger(name="bluetooth_utils.py", level=logging.DEBUG) # Constants -BT_SCAN_DURATION_S = 3 +BT_SCAN_DURATION_S = 6 BT_PAIR_TIMEOUT_S = 60 BT_CONNECT_SETTLE_S = 2 BT_CONFIG_PATH = "/home/bjorn/.settings_bjorn/bt.json" @@ -42,8 +42,14 @@ class BluetoothUtils: self._config_lock = threading.Lock() def _ensure_bluetooth_service(self): - """Check if bluetooth service is running, if not start and enable it.""" + """Check if bluetooth service is running, unblock rfkill, start if needed.""" try: + # Unblock Bluetooth via rfkill (handles soft-block that causes "Failed" errors) + subprocess.run( + ["sudo", "rfkill", "unblock", "bluetooth"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + res = subprocess.run( ["systemctl", "is-active", "bluetooth"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True @@ -148,6 +154,25 @@ class BluetoothUtils: else: self.logger.info("auto_bt_connect service restarted successfully") + def _reset_adapter(self): + """Power-cycle the Bluetooth adapter to recover from error states.""" + try: + self.logger.info("Resetting Bluetooth adapter (power cycle)...") + self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(False)) + time.sleep(1) + # Re-unblock in case rfkill re-blocked on power off + subprocess.run( + ["sudo", "rfkill", "unblock", "bluetooth"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(True)) + time.sleep(0.5) + self.logger.info("Bluetooth adapter reset complete.") + except Exception as e: + self.logger.error(f"Adapter reset failed: {e}") + # Force full re-init on next call + self.bluetooth_initialized = False + def scan_bluetooth(self, handler): """Scan for Bluetooth devices.""" try: @@ -175,7 +200,14 @@ class BluetoothUtils: self.adapter_methods.StartDiscovery() discovery_started = True except dbus.exceptions.DBusException as e2: - self.logger.warning(f"Retry also failed ({e2}), returning cached devices") + # Last resort: full adapter reset + self.logger.warning(f"Retry failed ({e2}), attempting adapter reset") + try: + self._reset_adapter() + self.adapter_methods.StartDiscovery() + discovery_started = True + except dbus.exceptions.DBusException as e3: + self.logger.warning(f"All retries failed ({e3}), returning cached devices") if discovery_started: time.sleep(BT_SCAN_DURATION_S) @@ -190,9 +222,15 @@ class BluetoothUtils: rssi = int(rssi) if rssi is not None else -999 except (ValueError, TypeError): rssi = -999 + # Prefer Alias (cached friendly name) over Name (advertised only) + address = str(dev.get("Address", "")) + name = str(dev.get("Alias", "")) or str(dev.get("Name", "")) + # If Alias is just the MAC address, BlueZ hasn't resolved the name + if not name or name == address: + name = "Unknown" devices.append({ - "name": str(dev.get("Name", "Unknown")), - "address": str(dev.get("Address", "")), + "name": name, + "address": address, "paired": bool(dev.get("Paired", False)), "trusted": bool(dev.get("Trusted", False)), "connected": bool(dev.get("Connected", False)),