diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 016426d53..4fd274738 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Added ----- * Added support for Python 3.11. * Added better error message for Bluetooth not authorized on macOS. +* ``BleakDeviceNotFoundError`` which should be raised if a device can not be found by ``connect``, ``pair`` and ``unpair`` Fixed ----- diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 3d40779bf..93e5d170c 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -11,12 +11,12 @@ import async_timeout from dbus_fast.aio import MessageBus -from dbus_fast.constants import BusType, ErrorType +from dbus_fast.constants import BusType, ErrorType, MessageType from dbus_fast.message import Message from dbus_fast.signature import Variant from ... import BleakScanner -from ...exc import BleakDBusError, BleakError +from ...exc import BleakDBusError, BleakError, BleakDeviceNotFoundError from ..characteristic import BleakGATTCharacteristic from ..client import BaseBleakClient, NotifyCallback from ..device import BLEDevice @@ -116,8 +116,9 @@ async def connect(self, dangerous_use_bleak_cache: bool = False, **kwargs) -> bo self._device_info = device.details.get("props") self._device_path = device.details["path"] else: - raise BleakError( - "Device with address {0} was not found.".format(self.address) + raise BleakDeviceNotFoundError( + BLEDevice(self.address), + f"Device with address {self.address} was not found.", ) manager = await get_global_bluez_manager() @@ -176,6 +177,15 @@ def on_value_changed(char_path: str, value: bytes) -> None: member="Connect", ) ) + if ( + reply is not None + and reply.message_type == MessageType.ERROR + and reply.error_name == ErrorType.UNKNOWN_OBJECT + ): + raise BleakDeviceNotFoundError( + BLEDevice(self.address), + f"Device with address {self.address} was not found.", + ) assert_reply(reply) self._is_connected = True diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index 9b066acb5..059499ce3 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -17,7 +17,7 @@ from Foundation import NSArray, NSData from ... import BleakScanner -from ...exc import BleakError +from ...exc import BleakError, BleakDeviceNotFoundError from ..characteristic import BleakGATTCharacteristic from ..client import BaseBleakClient, NotifyCallback from ..device import BLEDevice @@ -80,8 +80,9 @@ async def connect(self, **kwargs) -> bool: self._peripheral = device.details self._central_manager_delegate = device.metadata["delegate"] else: - raise BleakError( - "Device with address {} was not found".format(self.address) + raise BleakDeviceNotFoundError( + BLEDevice(self.address), + f"Device with address {self.address} was not found", ) if self._delegate is None: @@ -112,7 +113,15 @@ def disconnect_callback(): manager = self._central_manager_delegate logger.debug("CentralManagerDelegate at {}".format(manager)) logger.debug("Connecting to BLE device @ {}".format(self.address)) - await manager.connect(self._peripheral, disconnect_callback, timeout=timeout) + try: + await manager.connect( + self._peripheral, disconnect_callback, timeout=timeout + ) + except asyncio.TimeoutError as error: + raise BleakDeviceNotFoundError( + BLEDevice(self.address), + f"Device with address {self.address} was not found", + ) from error # Now get services await self.get_services() diff --git a/bleak/backends/device.py b/bleak/backends/device.py index d4e5c40ed..31b3d5d3e 100644 --- a/bleak/backends/device.py +++ b/bleak/backends/device.py @@ -23,7 +23,7 @@ class BLEDevice: - When using macOS backend, ``details`` attribute will be a CBPeripheral object. """ - def __init__(self, address, name, details=None, rssi=0, **kwargs): + def __init__(self, address, name=None, details=None, rssi=0, **kwargs): #: The Bluetooth address of the device on this machine. self.address = address #: The advertised name of the device. diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index 06cb29aed..446da7cdf 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -48,7 +48,7 @@ from bleak_winrt.windows.storage.streams import Buffer from ... import BleakScanner -from ...exc import PROTOCOL_ERROR_CODES, BleakError +from ...exc import PROTOCOL_ERROR_CODES, BleakError, BleakDeviceNotFoundError from ..characteristic import BleakGATTCharacteristic from ..client import BaseBleakClient, NotifyCallback from ..device import BLEDevice @@ -197,7 +197,7 @@ def __str__(self): # Connectivity methods - def _create_requester(self, bluetooth_address: int): + def _create_requester(self, bluetooth_address: int) -> BluetoothLEDevice: args = [ bluetooth_address, ] @@ -207,7 +207,13 @@ def _create_requester(self, bluetooth_address: int): if self._address_type == "public" else BluetoothAddressType.RANDOM ) - return BluetoothLEDevice.from_bluetooth_address_async(*args) + requester = BluetoothLEDevice.from_bluetooth_address_async(*args) + if requester is None: + raise BleakDeviceNotFoundError( + BLEDevice(self.address), + f"Device with address {self.address} was not found.", + ) + return requester async def connect(self, **kwargs) -> bool: """Connect to the specified GATT server. @@ -226,10 +232,13 @@ async def connect(self, **kwargs) -> bool: self.address, timeout=timeout, backend=BleakScannerWinRT ) - if device: - self._device_info = device.details.adv.bluetooth_address - else: - raise BleakError(f"Device with address {self.address} was not found.") + if device is None: + raise BleakDeviceNotFoundError( + BLEDevice(self.address), + f"Device with address {self.address} was not found.", + ) + + self._device_info = device.details.adv.bluetooth_address logger.debug("Connecting to BLE device @ %s", self.address) @@ -476,9 +485,6 @@ async def unpair(self) -> bool: else _address_to_int(self.address) ) - if device is None: - raise BleakError(f"Device with address {self.address} was not found.") - try: unpairing_result = await device.device_information.pairing.unpair_async() if unpairing_result.status not in ( diff --git a/bleak/exc.py b/bleak/exc.py index 1eba5fc43..8b04fc1b4 100644 --- a/bleak/exc.py +++ b/bleak/exc.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from typing import Optional +from bleak.backends.device import BLEDevice + class BleakError(Exception): """Base Exception for bleak.""" @@ -8,6 +10,23 @@ class BleakError(Exception): pass +class BleakDeviceNotFoundError(BleakError): + """ + Exception which is raised if a device can not be found by ``connect``, ``pair`` and ``unpair``. + This is the case if the OS Bluetooth stack has never seen this device or it was removed and forgotten. + """ + + ble_device: BLEDevice + + def __init__(self, ble_device: BLEDevice, *args: object) -> None: + """ + Args: + ble_device (BLEDevice): device object which contains details about the device which was not found + """ + super().__init__(*args) + self.ble_device = ble_device + + class BleakDBusError(BleakError): """Specialized exception type for D-Bus errors."""