Skip to content

Commit

Permalink
backends/winrt: add connection check to BleakClient methods (#973)
Browse files Browse the repository at this point in the history
This adds a connection check to all methods that require a connection in the WinRT backend similar to what is already done in the BlueZ backend.

This fixes an issue where we would get an AttribueError in the connect method while getting services caused by the device disconnecting after connecting but before getting the services. Now a more informative exception is raised instead.
  • Loading branch information
nv-h authored Sep 5, 2022
1 parent 995899a commit 9c2b34d
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 53 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0
`Unreleased`_
=============

Fixed
-----
* ``BleakClient`` methods now raise ``BleakError`` if called when not connected in WinRT backend.

`0.16.0`_ (2022-08-31)
======================

Expand Down
127 changes: 74 additions & 53 deletions bleak/backends/winrt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,73 +499,76 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection:
A :py:class:`bleak.backends.service.BleakGATTServiceCollection` with this device's services tree.
"""
if not self.is_connected:
raise BleakError("Not connected")

# Return the Service Collection.
if self._services_resolved:
return self.services
else:
logger.debug("Get Services...")

# Each of the get_serv/char/desc_async() methods has two forms, one
# with no args and one with a cache_mode argument
args = []

# If the os-specific use_cached_services arg was given when BleakClient
# was created, the we use the second form with explicit cache mode.
# Otherwise we use the first form with no explicit cache mode which
# allows the OS Bluetooth stack to decide what is best.
if self._use_cached_services is not None:
args.append(
BluetoothCacheMode.CACHED
if self._use_cached_services
else BluetoothCacheMode.UNCACHED
)

services: Sequence[GattDeviceService] = _ensure_success(
await self._requester.get_gatt_services_async(*args),
"services",
"Could not get GATT services",
logger.debug("Get Services...")

# Each of the get_serv/char/desc_async() methods has two forms, one
# with no args and one with a cache_mode argument
args = []

# If the os-specific use_cached_services arg was given when BleakClient
# was created, the we use the second form with explicit cache mode.
# Otherwise we use the first form with no explicit cache mode which
# allows the OS Bluetooth stack to decide what is best.
if self._use_cached_services is not None:
args.append(
BluetoothCacheMode.CACHED
if self._use_cached_services
else BluetoothCacheMode.UNCACHED
)

for service in services:
# Windows returns an ACCESS_DENIED error when trying to enumerate
# characteristics of services used by the OS, like the HID service
# so we have to exclude those services.
if service.uuid in _ACCESS_DENIED_SERVICES:
continue
services: Sequence[GattDeviceService] = _ensure_success(
await self._requester.get_gatt_services_async(*args),
"services",
"Could not get GATT services",
)

self.services.add_service(BleakGATTServiceWinRT(service))
for service in services:
# Windows returns an ACCESS_DENIED error when trying to enumerate
# characteristics of services used by the OS, like the HID service
# so we have to exclude those services.
if service.uuid in _ACCESS_DENIED_SERVICES:
continue

characteristics: Sequence[GattCharacteristic] = _ensure_success(
await service.get_characteristics_async(*args),
"characteristics",
f"Could not get GATT characteristics for {service}",
)
self.services.add_service(BleakGATTServiceWinRT(service))

for characteristic in characteristics:
self.services.add_characteristic(
BleakGATTCharacteristicWinRT(
characteristic, self._session.max_pdu_size - 3
)
)
characteristics: Sequence[GattCharacteristic] = _ensure_success(
await service.get_characteristics_async(*args),
"characteristics",
f"Could not get GATT characteristics for {service}",
)

descriptors: Sequence[GattDescriptor] = _ensure_success(
await characteristic.get_descriptors_async(*args),
"descriptors",
f"Could not get GATT descriptors for {service}",
for characteristic in characteristics:
self.services.add_characteristic(
BleakGATTCharacteristicWinRT(
characteristic, self._session.max_pdu_size - 3
)
)

descriptors: Sequence[GattDescriptor] = _ensure_success(
await characteristic.get_descriptors_async(*args),
"descriptors",
f"Could not get GATT descriptors for {service}",
)

for descriptor in descriptors:
self.services.add_descriptor(
BleakGATTDescriptorWinRT(
descriptor,
str(characteristic.uuid),
characteristic.attribute_handle,
)
for descriptor in descriptors:
self.services.add_descriptor(
BleakGATTDescriptorWinRT(
descriptor,
str(characteristic.uuid),
characteristic.attribute_handle,
)
)

logger.info("Services resolved for %s", str(self))
self._services_resolved = True
return self.services
logger.info("Services resolved for %s", str(self))
self._services_resolved = True
return self.services

# I/O methods

Expand All @@ -589,6 +592,9 @@ async def read_gatt_char(
(bytearray) The read data.
"""
if not self.is_connected:
raise BleakError("Not connected")

use_cached = kwargs.get("use_cached", False)

if not isinstance(char_specifier, BleakGATTCharacteristic):
Expand Down Expand Up @@ -628,6 +634,9 @@ async def read_gatt_descriptor(self, handle: int, **kwargs) -> bytearray:
(bytearray) The read data.
"""
if not self.is_connected:
raise BleakError("Not connected")

use_cached = kwargs.get("use_cached", False)

descriptor = self.services.get_descriptor(handle)
Expand Down Expand Up @@ -666,6 +675,9 @@ async def write_gatt_char(
response (bool): If write-with-response operation should be done. Defaults to `False`.
"""
if not self.is_connected:
raise BleakError("Not connected")

if not isinstance(char_specifier, BleakGATTCharacteristic):
characteristic = self.services.get_characteristic(char_specifier)
else:
Expand Down Expand Up @@ -698,6 +710,9 @@ async def write_gatt_descriptor(
data (bytes or bytearray): The data to send.
"""
if not self.is_connected:
raise BleakError("Not connected")

descriptor = self.services.get_descriptor(handle)
if not descriptor:
raise BleakError("Descriptor with handle {0} was not found!".format(handle))
Expand Down Expand Up @@ -742,6 +757,9 @@ def callback(sender, data):
notification request, given that the characteristic supports notifications as well as indications.
"""
if not self.is_connected:
raise BleakError("Not connected")

if inspect.iscoroutinefunction(callback):

def bleak_callback(s, d):
Expand Down Expand Up @@ -813,6 +831,9 @@ async def stop_notify(
directly by the BleakGATTCharacteristic object representing it.
"""
if not self.is_connected:
raise BleakError("Not connected")

if not isinstance(char_specifier, BleakGATTCharacteristic):
characteristic = self.services.get_characteristic(char_specifier)
else:
Expand Down

0 comments on commit 9c2b34d

Please sign in to comment.