From 5e7d1911d72b25adce84cb1e3d720a3e741eccee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bojan=20Poto=C4=8Dnik?= Date: Tue, 22 Nov 2022 12:52:30 +0100 Subject: [PATCH] Instantiate BaseBleakClient.services collection after service discovery --- CHANGELOG.rst | 2 ++ bleak/__init__.py | 6 ++++++ bleak/backends/bluezdbus/client.py | 6 ++---- bleak/backends/client.py | 4 +--- bleak/backends/corebluetooth/client.py | 25 +++++++++++-------------- bleak/backends/p4android/client.py | 15 ++++++++------- bleak/backends/winrt/client.py | 11 +++++------ 7 files changed, 35 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b33977b3..cf0dc24f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,8 @@ Changed * Dropped ``async-timeout`` dependency on Python >= 3.11. * Deprecated ``BLEDevice.rssi`` and ``BLEDevice.metadata``. Fixes #1025. * ``BLEDevice`` now uses ``__slots__`` to reduce memory usage. +* ``BaseBleakClient.services`` is now ``None`` instead of empty service collection + until services are discovered. Fixed ----- diff --git a/bleak/__init__.py b/bleak/__init__.py index c2d340c4..2ebace99 100644 --- a/bleak/__init__.py +++ b/bleak/__init__.py @@ -576,7 +576,13 @@ def services(self) -> BleakGATTServiceCollection: Gets the collection of GATT services available on the device. The returned value is only valid as long as the device is connected. + + Raises: + BleakError: if service discovery has not been performed yet during this connection. """ + if not self._backend.services: + raise BleakError("Service Discovery has not been performed yet") + return self._backend.services # I/O methods diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 6c419f22..8bf88ff0 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -309,8 +309,7 @@ def _cleanup_all(self) -> None: self._bus = None # Reset all stored services. - self.services = BleakGATTServiceCollection() - self._services_resolved = False + self.services = None async def disconnect(self) -> bool: """Disconnect from the specified GATT server. @@ -587,7 +586,7 @@ async def get_services( if not self.is_connected: raise BleakError("Not connected") - if self._services_resolved: + if self.services is not None: return self.services manager = await get_global_bluez_manager() @@ -595,7 +594,6 @@ async def get_services( self.services = await manager.get_services( self._device_path, dangerous_use_bleak_cache ) - self._services_resolved = True return self.services diff --git a/bleak/backends/client.py b/bleak/backends/client.py index 6bcdf760..d2bf6323 100644 --- a/bleak/backends/client.py +++ b/bleak/backends/client.py @@ -42,9 +42,7 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): else: self.address = address_or_ble_device - self.services = BleakGATTServiceCollection() - - self._services_resolved = False + self.services: Optional[BleakGATTServiceCollection] = None self._timeout = kwargs.get("timeout", 10.0) self._disconnected_callback = kwargs.get("disconnected_callback") diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index fb91b93c..4fd38c3d 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -14,7 +14,7 @@ CBPeripheral, CBPeripheralStateConnected, ) -from Foundation import NSArray, NSData +from Foundation import NSData from ... import BleakScanner from ...exc import BleakError, BleakDeviceNotFoundError @@ -57,8 +57,6 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs): self._central_manager_delegate, ) = address_or_ble_device.details - self._services: Optional[NSArray] = None - def __str__(self): return "BleakClientCoreBluetooth ({})".format(self.address) @@ -91,11 +89,9 @@ async def connect(self, **kwargs) -> bool: ) def disconnect_callback(): - self.services = BleakGATTServiceCollection() # Ensure that `get_services` retrieves services again, rather # than using the cached object - self._services_resolved = False - self._services = None + self.services = None # If there are any pending futures waiting for delegate callbacks, we # need to raise an exception since the callback will no longer be @@ -190,20 +186,22 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: A :py:class:`bleak.backends.service.BleakGATTServiceCollection` with this device's services tree. """ - if self._services is not None: + if self.services is not None: return self.services + services = BleakGATTServiceCollection() + logger.debug("Retrieving services...") - services = await self._delegate.discover_services() + cb_services = await self._delegate.discover_services() - for service in services: + for service in cb_services: serviceUUID = service.UUID().UUIDString() logger.debug( "Retrieving characteristics for service {}".format(serviceUUID) ) characteristics = await self._delegate.discover_characteristics(service) - self.services.add_service(BleakGATTServiceCoreBluetooth(service)) + services.add_service(BleakGATTServiceCoreBluetooth(service)) for characteristic in characteristics: cUUID = characteristic.UUID().UUIDString() @@ -212,7 +210,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: ) descriptors = await self._delegate.discover_descriptors(characteristic) - self.services.add_characteristic( + services.add_characteristic( BleakGATTCharacteristicCoreBluetooth( characteristic, self._peripheral.maximumWriteValueLengthForType_( @@ -221,7 +219,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: ) ) for descriptor in descriptors: - self.services.add_descriptor( + services.add_descriptor( BleakGATTDescriptorCoreBluetooth( descriptor, cb_uuid_to_str(characteristic.UUID()), @@ -229,8 +227,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: ) ) logger.debug("Services resolved for %s", str(self)) - self._services_resolved = True - self._services = services + self.services = services return self.services async def read_gatt_char( diff --git a/bleak/backends/p4android/client.py b/bleak/backends/p4android/client.py index 3a19a183..24e5eae9 100644 --- a/bleak/backends/p4android/client.py +++ b/bleak/backends/p4android/client.py @@ -151,8 +151,7 @@ async def disconnect(self) -> bool: self.__callbacks = None # Reset all stored services. - self.services = BleakGATTServiceCollection() - self._services_resolved = False + self.services = None return True @@ -246,14 +245,16 @@ async def get_services(self) -> BleakGATTServiceCollection: A :py:class:`bleak.backends.service.BleakGATTServiceCollection` with this device's services tree. """ - if self._services_resolved: + if self.services is not None: return self.services + services = BleakGATTServiceCollection() + logger.debug("Get Services...") for java_service in self.__gatt.getServices(): service = BleakGATTServiceP4Android(java_service) - self.services.add_service(service) + services.add_service(service) for java_characteristic in java_service.getCharacteristics(): @@ -263,7 +264,7 @@ async def get_services(self) -> BleakGATTServiceCollection: service.handle, self.__mtu - 3, ) - self.services.add_characteristic(characteristic) + services.add_characteristic(characteristic) for descriptor_index, java_descriptor in enumerate( java_characteristic.getDescriptors() @@ -275,9 +276,9 @@ async def get_services(self) -> BleakGATTServiceCollection: characteristic.handle, descriptor_index, ) - self.services.add_descriptor(descriptor) + services.add_descriptor(descriptor) - self._services_resolved = True + self.services = services return self.services # IO methods diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index f5ab41cf..5241eec8 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -414,7 +414,6 @@ def max_pdu_size_changed_handler(sender: GattSession, args): # services did not change while getting services, # so this is the final result self.services = get_services_task.result() - self._services_resolved = True break logger.debug( @@ -463,10 +462,10 @@ async def disconnect(self) -> bool: self._notification_callbacks.clear() # Dispose all service components that we have requested and created. - for service in self.services: - service.obj.close() - self.services = BleakGATTServiceCollection() - self._services_resolved = False + if self.services: + for service in self.services: + service.obj.close() + self.services = None # Without this, disposing the BluetoothLEDevice won't disconnect it if self._session: @@ -616,7 +615,7 @@ async def get_services( """ # Return the Service Collection. - if self._services_resolved: + if self.services is not None: return self.services logger.debug(