Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/v0.22.2 #1587

Merged
merged 10 commits into from
Jun 1, 2024
Merged
21 changes: 19 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0
`Unreleased`_
=============

`0.22.2`_ (2024-06-01)
======================

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
-----
* Fixed ``discovered_devices_and_advertisement_data`` returning devices that should
be filtered out by service UUIDs. Fixes #1576.
* Fixed a ``Descriptor None was not found!`` exception occurring in ``start_notify()`` on Android. Fixes #823.
* Fixed exception raised when starting ``BleakScanner`` while running in a Windows GUI app.

`0.22.1`_ (2024-05-07)
======================

Expand Down Expand Up @@ -44,7 +60,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 v0.22.2]
* Fixed filtering advertisement data by service UUID when multiple apps are scanning. Fixes #1534.

`0.21.1`_ (2023-09-08)
Expand Down Expand Up @@ -1022,7 +1038,8 @@ Fixed
* Bleak created.


.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.22.1...develop
.. _Unreleased: https://github.com/hbldh/bleak/compare/v0.22.2...develop
.. _0.22.2: https://github.com/hbldh/bleak/compare/v0.22.1...v0.22.2
.. _0.22.1: https://github.com/hbldh/bleak/compare/v0.22.0...v0.22.1
.. _0.22.0: https://github.com/hbldh/bleak/compare/v0.21.1...v0.22.0
.. _0.21.1: https://github.com/hbldh/bleak/compare/v0.21.0...v0.21.1
Expand Down
4 changes: 2 additions & 2 deletions bleak/backends/bluezdbus/characteristic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Union
from typing import Callable, List, Union
from uuid import UUID

from ..characteristic import BleakGATTCharacteristic
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion bleak/backends/bluezdbus/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion bleak/backends/bluezdbus/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,17 @@ def _handle_advertising_data(self, path: str, props: Device1) -> None:
path: The D-Bus object path of the device.
props: The D-Bus object properties of the device.
"""
_service_uuids = props.get("UUIDs", [])

if not self.is_allowed_uuid(_service_uuids):
return

# Get all the information wanted to pack in the advertisement data
_local_name = props.get("Name")
_manufacturer_data = {
k: bytes(v) for k, v in props.get("ManufacturerData", {}).items()
}
_service_data = {k: bytes(v) for k, v in props.get("ServiceData", {}).items()}
_service_uuids = props.get("UUIDs", [])

# Get tx power data
tx_power = props.get("TxPower")
Expand Down
24 changes: 21 additions & 3 deletions bleak/backends/characteristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions bleak/backends/corebluetooth/characteristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion bleak/backends/corebluetooth/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection:
services.add_characteristic(
BleakGATTCharacteristicCoreBluetooth(
characteristic,
self._peripheral.maximumWriteValueLengthForType_(
lambda: self._peripheral.maximumWriteValueLengthForType_(
CBCharacteristicWriteWithoutResponse
),
)
Expand Down
11 changes: 7 additions & 4 deletions bleak/backends/corebluetooth/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ async def start(self) -> None:

def callback(p: CBPeripheral, a: Dict[str, Any], r: int) -> None:

service_uuids = [
cb_uuid_to_str(u) for u in a.get("kCBAdvDataServiceUUIDs", [])
]

if not self.is_allowed_uuid(service_uuids):
return

# Process service data
service_data_dict_raw = a.get("kCBAdvDataServiceData", {})
service_data = {
Expand All @@ -108,10 +115,6 @@ def callback(p: CBPeripheral, a: Dict[str, Any], r: int) -> None:
manufacturer_value = bytes(manufacturer_binary_data[2:])
manufacturer_data[manufacturer_id] = manufacturer_value

service_uuids = [
cb_uuid_to_str(u) for u in a.get("kCBAdvDataServiceUUIDs", [])
]

# set tx_power data if available
tx_power = a.get("kCBAdvDataTxPowerLevel")

Expand Down
4 changes: 2 additions & 2 deletions bleak/backends/p4android/characteristic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Union
from typing import Callable, List, Union
from uuid import UUID

from ...exc import BleakError
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion bleak/backends/p4android/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion bleak/backends/p4android/defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from jnius import autoclass, cast

import bleak.exc
from bleak.uuids import normalize_uuid_16

# caching constants avoids unnecessary extra use of the jni-python interface, which can be slow

Expand Down Expand Up @@ -87,4 +88,4 @@ class ScanFailed(enum.IntEnum):
BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE: "write-without-response",
}

CLIENT_CHARACTERISTIC_CONFIGURATION_UUID = "2902"
CLIENT_CHARACTERISTIC_CONFIGURATION_UUID = normalize_uuid_16(0x2902)
3 changes: 3 additions & 0 deletions bleak/backends/p4android/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ def _handle_scan_result(self, result) -> None:
if service_uuids is not None:
service_uuids = [service_uuid.toString() for service_uuid in service_uuids]

if not self.is_allowed_uuid(service_uuids):
return

manufacturer_data = record.getManufacturerSpecificData()
manufacturer_data = {
manufacturer_data.keyAt(index): bytes(manufacturer_data.valueAt(index))
Expand Down
51 changes: 35 additions & 16 deletions bleak/backends/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,32 +197,51 @@ def remove() -> None:

return remove

def call_detection_callbacks(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
def is_allowed_uuid(self, service_uuids: Optional[List[str]]) -> bool:
"""
Calls all registered detection callbacks.
Check if the advertisement data contains any of the service UUIDs
matching the filter. If no filter is set, this will always return
``True``.

Backend implementations should call this method when an advertisement
event is received from the OS.
"""
Args:
service_uuids: The service UUIDs from the advertisement data.

Returns:
``True`` if the advertisement data should be allowed or ``False``
if the advertisement data should be filtered out.
"""
# Backends will make best effort to filter out advertisements that
# don't match the service UUIDs, but if other apps are scanning at the
# same time or something like that, we may still receive advertisements
# that don't match. So we need to do more filtering here to get the
# expected behavior.

if self._service_uuids:
if not advertisement_data.service_uuids:
return
if not self._service_uuids:
# if there is no filter, everything is allowed
return True

if not service_uuids:
# if there is a filter the advertisement data doesn't contain any
# service UUIDs, filter it out
return False

for uuid in advertisement_data.service_uuids:
if uuid in self._service_uuids:
break
else:
# if there were no matching service uuids, the don't call the callback
return
for uuid in service_uuids:
if uuid in self._service_uuids:
# match was found, keep this advertisement
return True

# there were no matching service uuids, filter this one out
return False

def call_detection_callbacks(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
"""
Calls all registered detection callbacks.

Backend implementations should call this method when an advertisement
event is received from the OS.
"""

for callback in self._ad_callbacks.values():
callback(device, advertisement_data)
Expand Down
8 changes: 6 additions & 2 deletions bleak/backends/winrt/characteristic.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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 = [
Expand Down
Loading
Loading