From b783faea31b458e90bbfd979b2d87e1a7a5a001b Mon Sep 17 00:00:00 2001 From: Cody Scott Date: Wed, 14 Feb 2024 08:36:37 -0500 Subject: [PATCH 1/2] Improve error handling when a device is not present in BlueZ Fixes #1507 --- CHANGELOG.rst | 1 + bleak/backends/bluezdbus/manager.py | 35 ++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index df3317c0..deabfcbf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,7 @@ Fixed ----- * Fixed BlueZ version in passive scanning error message. Fixes #1433. * Fixed mypy requiring ``Unpack[ExtraArgs]`` that were intended to be optional. Fixes #1487. +* Fixed ``KeyError`` in BlueZ ``is_connected()`` and ``get_global_bluez_manager()`` when device is not present. Fixes #1507. `0.21.1`_ (2023-09-08) ====================== diff --git a/bleak/backends/bluezdbus/manager.py b/bleak/backends/bluezdbus/manager.py index e6b5fffe..43841dc1 100644 --- a/bleak/backends/bluezdbus/manager.py +++ b/bleak/backends/bluezdbus/manager.py @@ -216,6 +216,28 @@ def _check_device(self, device_path: str) -> None: if device_path not in self._properties: raise BleakError(f"device '{device_path.split('/')[-1]}' not found") + def _get_device_property( + self, device_path: str, interface: str, property_name: str + ) -> Any: + self._check_device(device_path) + device_properties = self._properties[device_path] + + try: + interface_properties = device_properties[interface] + except KeyError: + raise BleakError( + f"Interface {interface} not found for device '{device_path}'" + ) + + try: + value = interface_properties[property_name] + except KeyError: + raise BleakError( + f"Property '{property_name}' not found for '{interface}' in '{device_path}'" + ) + + return value + async def async_init(self): """ Connects to the D-Bus message bus and begins monitoring signals. @@ -711,9 +733,7 @@ def get_device_name(self, device_path: str) -> str: Raises: BleakError: if the device is not present in BlueZ """ - self._check_device(device_path) - - return self._properties[device_path][defs.DEVICE_INTERFACE]["Name"] + return self._get_device_property(device_path, defs.DEVICE_INTERFACE, "Name") def is_connected(self, device_path: str) -> bool: """ @@ -820,12 +840,11 @@ async def _wait_condition( Raises: BleakError: if the device is not present in BlueZ """ - self._check_device(device_path) + value = self._get_device_property( + device_path, defs.DEVICE_INTERFACE, property_name + ) - if ( - self._properties[device_path][defs.DEVICE_INTERFACE][property_name] - == property_value - ): + if value == property_value: return event = asyncio.Event() From 4c69c23d72d6e78acb3015196a74eb6bf13152b9 Mon Sep 17 00:00:00 2001 From: Cody Scott Date: Wed, 14 Feb 2024 18:58:41 -0500 Subject: [PATCH 2/2] Remove empty interfaces from BlueZManager._properties --- CHANGELOG.rst | 1 + bleak/backends/bluezdbus/manager.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index deabfcbf..c0917c6e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,7 @@ Changed ------- * Updated PyObjC dependency on macOS to v10.x. * Updated missing Bluetooth SIG characteristics and service UUIDs. +* Updated ``BlueZManager`` to remove empty interfaces from `_properties` during InterfacesRemoved message. Fixed ----- diff --git a/bleak/backends/bluezdbus/manager.py b/bleak/backends/bluezdbus/manager.py index 43841dc1..488dbb7b 100644 --- a/bleak/backends/bluezdbus/manager.py +++ b/bleak/backends/bluezdbus/manager.py @@ -961,6 +961,12 @@ def _parse_msg(self, message: Message): del self._descriptor_map[obj_path] except KeyError: pass + + # Remove empty properties when all interfaces have been removed. + # This avoids wasting memory for people who have noisy devices + # with private addresses that change frequently. + if obj_path in self._properties and not self._properties[obj_path]: + del self._properties[obj_path] elif message.member == "PropertiesChanged": interface, changed, invalidated = message.body message_path = message.path