Skip to content

Commit

Permalink
Merge pull request #1587 from hbldh/release/v0.22.2
Browse files Browse the repository at this point in the history
Release/v0.22.2
  • Loading branch information
dlech authored Jun 1, 2024
2 parents 181467f + c746071 commit ce48c7f
Show file tree
Hide file tree
Showing 23 changed files with 501 additions and 163 deletions.
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

0 comments on commit ce48c7f

Please sign in to comment.