diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3050b0e7..5861d9ad 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Changed ------- * Retrieve the BLE address required by ``BleakClientWinRT`` from scan response if advertising is None (WinRT). * Changed type hint for ``adv`` attribute of ``bleak.backends.winrt.scanner._RawAdvData``. +* ``BleakGATTCharacteristic.max_write_without_response_size`` is now dynamic. Fixed ----- @@ -56,7 +57,7 @@ Fixed * Fixed scanning silently failing on Windows when Bluetooth is off. Fixes #1535. * Fixed using wrong value for ``tx_power`` in Android backend. Fixes #1532. * Fixed 4-character UUIDs not working on ``BleakClient.*_gatt_char`` methods. Fixes #1498. -* Fixed race condition with getting max PDU size on Windows. Fixes #1497. +* Fixed race condition with getting max PDU size on Windows. Fixes #1497. [REVERTED in unreleased] * Fixed filtering advertisement data by service UUID when multiple apps are scanning. Fixes #1534. `0.21.1`_ (2023-09-08) diff --git a/bleak/backends/bluezdbus/characteristic.py b/bleak/backends/bluezdbus/characteristic.py index 3b41147b..ab707827 100644 --- a/bleak/backends/bluezdbus/characteristic.py +++ b/bleak/backends/bluezdbus/characteristic.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import Callable, List, Union from uuid import UUID from ..characteristic import BleakGATTCharacteristic @@ -36,7 +36,7 @@ def __init__( object_path: str, service_uuid: str, service_handle: int, - max_write_without_response_size: int, + max_write_without_response_size: Callable[[], int], ): super(BleakGATTCharacteristicBlueZDBus, self).__init__( obj, max_write_without_response_size diff --git a/bleak/backends/bluezdbus/manager.py b/bleak/backends/bluezdbus/manager.py index 3953fc18..1d5d16b5 100644 --- a/bleak/backends/bluezdbus/manager.py +++ b/bleak/backends/bluezdbus/manager.py @@ -696,7 +696,7 @@ async def get_services( service.handle, # "MTU" property was added in BlueZ 5.62, otherwise fall # back to minimum MTU according to Bluetooth spec. - char_props.get("MTU", 23) - 3, + lambda: char_props.get("MTU", 23) - 3, ) services.add_characteristic(char) diff --git a/bleak/backends/characteristic.py b/bleak/backends/characteristic.py index f83917a8..eca52d5e 100644 --- a/bleak/backends/characteristic.py +++ b/bleak/backends/characteristic.py @@ -7,7 +7,7 @@ """ import abc import enum -from typing import Any, List, Union +from typing import Any, Callable, List, Union from uuid import UUID from ..uuids import uuidstr_to_str @@ -30,7 +30,7 @@ class GattCharacteristicsFlags(enum.Enum): class BleakGATTCharacteristic(abc.ABC): """Interface for the Bleak representation of a GATT Characteristic""" - def __init__(self, obj: Any, max_write_without_response_size: int): + def __init__(self, obj: Any, max_write_without_response_size: Callable[[], int]): """ Args: obj: @@ -86,12 +86,30 @@ def max_write_without_response_size(self) -> int: Gets the maximum size in bytes that can be used for the *data* argument of :meth:`BleakClient.write_gatt_char()` when ``response=False``. + In rare cases, a device may take a long time to update this value, so + reading this property may return the default value of ``20`` and reading + it again after a some time may return the expected higher value. + + If you *really* need to wait for a higher value, you can do something + like this: + + .. code-block:: python + + async with asyncio.timeout(10): + while char.max_write_without_response_size == 20: + await asyncio.sleep(0.5) + .. warning:: Linux quirk: For BlueZ versions < 5.62, this property will always return ``20``. .. versionadded:: 0.16 """ - return self._max_write_without_response_size + + # for backwards compatibility + if isinstance(self._max_write_without_response_size, int): + return self._max_write_without_response_size + + return self._max_write_without_response_size() @property @abc.abstractmethod diff --git a/bleak/backends/corebluetooth/characteristic.py b/bleak/backends/corebluetooth/characteristic.py index 116c7267..4bf61711 100644 --- a/bleak/backends/corebluetooth/characteristic.py +++ b/bleak/backends/corebluetooth/characteristic.py @@ -6,7 +6,7 @@ """ from enum import Enum -from typing import Dict, List, Optional, Tuple, Union +from typing import Callable, Dict, List, Optional, Tuple, Union from CoreBluetooth import CBCharacteristic @@ -59,7 +59,9 @@ class CBCharacteristicProperties(Enum): class BleakGATTCharacteristicCoreBluetooth(BleakGATTCharacteristic): """GATT Characteristic implementation for the CoreBluetooth backend""" - def __init__(self, obj: CBCharacteristic, max_write_without_response_size: int): + def __init__( + self, obj: CBCharacteristic, max_write_without_response_size: Callable[[], int] + ): super().__init__(obj, max_write_without_response_size) self.__descriptors: List[BleakGATTDescriptorCoreBluetooth] = [] # self.__props = obj.properties() diff --git a/bleak/backends/corebluetooth/client.py b/bleak/backends/corebluetooth/client.py index 470fc983..a682dadb 100644 --- a/bleak/backends/corebluetooth/client.py +++ b/bleak/backends/corebluetooth/client.py @@ -237,7 +237,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection: services.add_characteristic( BleakGATTCharacteristicCoreBluetooth( characteristic, - self._peripheral.maximumWriteValueLengthForType_( + lambda: self._peripheral.maximumWriteValueLengthForType_( CBCharacteristicWriteWithoutResponse ), ) diff --git a/bleak/backends/p4android/characteristic.py b/bleak/backends/p4android/characteristic.py index 562eb6ea..d9f6f191 100644 --- a/bleak/backends/p4android/characteristic.py +++ b/bleak/backends/p4android/characteristic.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import Callable, List, Union from uuid import UUID from ...exc import BleakError @@ -15,7 +15,7 @@ def __init__( java, service_uuid: str, service_handle: int, - max_write_without_response_size: int, + max_write_without_response_size: Callable[[], int], ): super(BleakGATTCharacteristicP4Android, self).__init__( java, max_write_without_response_size diff --git a/bleak/backends/p4android/client.py b/bleak/backends/p4android/client.py index 92d51075..f1bca4dd 100644 --- a/bleak/backends/p4android/client.py +++ b/bleak/backends/p4android/client.py @@ -273,7 +273,7 @@ async def get_services(self) -> BleakGATTServiceCollection: java_characteristic, service.uuid, service.handle, - self.__mtu - 3, + lambda: self.__mtu - 3, ) services.add_characteristic(characteristic) diff --git a/bleak/backends/winrt/characteristic.py b/bleak/backends/winrt/characteristic.py index f72e9c67..6a576bf6 100644 --- a/bleak/backends/winrt/characteristic.py +++ b/bleak/backends/winrt/characteristic.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- import sys -from typing import List, Union +from typing import Callable, List, Union from uuid import UUID if sys.version_info >= (3, 12): @@ -68,7 +68,11 @@ class BleakGATTCharacteristicWinRT(BleakGATTCharacteristic): """GATT Characteristic implementation for the .NET backend, implemented with WinRT""" - def __init__(self, obj: GattCharacteristic, max_write_without_response_size: int): + def __init__( + self, + obj: GattCharacteristic, + max_write_without_response_size: Callable[[], int], + ): super().__init__(obj, max_write_without_response_size) self.__descriptors = [] self.__props = [ diff --git a/bleak/backends/winrt/client.py b/bleak/backends/winrt/client.py index 76aed574..a04ec7c5 100644 --- a/bleak/backends/winrt/client.py +++ b/bleak/backends/winrt/client.py @@ -383,8 +383,6 @@ def session_status_changed_event_handler( ) loop.call_soon_threadsafe(handle_session_status_changed, args) - pdu_size_event = asyncio.Event() - def max_pdu_size_changed_handler(sender: GattSession, args): try: max_pdu_size = sender.max_pdu_size @@ -395,7 +393,6 @@ def max_pdu_size_changed_handler(sender: GattSession, args): return logger.debug("max_pdu_size_changed_handler: %d", max_pdu_size) - pdu_size_event.set() # Start a GATT Session to connect event = asyncio.Event() @@ -492,29 +489,6 @@ def max_pdu_size_changed_handler(sender: GattSession, args): cache_mode=cache_mode, ) - # There is a race condition where the max_pdu_size_changed event - # might not be received before the get_services() call completes. - # We could put this wait before getting services, but that would - # make the connection time longer. So we put it here instead and - # fix up the characteristics if necessary. - if not pdu_size_event.is_set(): - try: - # REVISIT: Devices that don't support > default PDU size - # may be punished by this timeout with a slow connection - # time. We may want to consider an option to ignore this - # timeout for such devices. - async with async_timeout(1): - await pdu_size_event.wait() - except asyncio.TimeoutError: - logger.debug( - "max_pdu_size_changed event not received, using default" - ) - - for char in self.services.characteristics.values(): - char._max_write_without_response_size = ( - self._session.max_pdu_size - 3 - ) - # a connection may not be made until we request info from the # device, so we have to get services before the GATT session # is set to active @@ -791,10 +765,10 @@ def dispose_on_cancel(future): f"Could not get GATT descriptors for characteristic {characteristic.uuid} ({characteristic.attribute_handle})", ) - # NB: max_pdu_size might not be valid at this time so we - # start with default size and will update later new_services.add_characteristic( - BleakGATTCharacteristicWinRT(characteristic, 20) + BleakGATTCharacteristicWinRT( + characteristic, lambda: self._session.max_pdu_size - 3 + ) ) for descriptor in descriptors: