From f4ff394487761ce691bac892e8b267c4e35a44e7 Mon Sep 17 00:00:00 2001 From: Frederik Leonhardt Date: Tue, 26 May 2026 08:43:25 +1200 Subject: [PATCH 1/3] Update bluetooth-adapters to v2.3.0 Drop custom adapter details extensions which have been merged upstream. --- pyproject.toml | 2 +- src/pb_ble/bluezdbus/adapters.py | 40 ++++---------------------------- tests/fixtures/bluetooth.py | 4 ++-- tests/fixtures/bluez5_mock.py | 9 ++++++- tests/test_advertisement.py | 4 ++-- tests/test_broadcaster.py | 6 ++--- tests/test_vhub.py | 4 ++-- 7 files changed, 21 insertions(+), 48 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c23de09..9fd7647 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ dependencies = [ "bleak ~= 2.0", - "bluetooth-adapters ~= 2.1", + "bluetooth-adapters ~= 2.3", "cachetools ~= 5.3", "dbus-fast ~= 4.0", "pybricks @ git+https://github.com/fkleon/pybricks-api@ble-type-fixes", diff --git a/src/pb_ble/bluezdbus/adapters.py b/src/pb_ble/bluezdbus/adapters.py index cfff430..52ba459 100644 --- a/src/pb_ble/bluezdbus/adapters.py +++ b/src/pb_ble/bluezdbus/adapters.py @@ -13,45 +13,13 @@ adapters = get_adapters() -class AdapterDetailsExt(AdapterDetails): - """ - Extended adapter details. - """ - - advertise: bool - """Whether the adapter supports advertising.""" - - powered: bool - """Whether the adapter is powered on.""" - - -async def get_all_adapter_details() -> dict[str, AdapterDetailsExt]: - await adapters.refresh() - adapters_ext = {} - - # Lookup additional details about each adapter - for adapter, details in adapters.adapters.items(): - if not (bluez_details := adapters._bluez.adapter_details.get(adapter)): # type: ignore # _bluez is a private member - # Adapter not known to BlueZ, ignore it. - continue - - advertise = "org.bluez.LEAdvertisingManager1" in bluez_details - powered = bluez_details["org.bluez.Adapter1"]["Powered"] - - adapters_ext[adapter] = AdapterDetailsExt( - **details, advertise=advertise, powered=powered - ) - - return adapters_ext - - async def get_adapter_details( adapter_name: str = adapters.default_adapter, -) -> tuple[str, AdapterDetailsExt]: - adapters = await get_all_adapter_details() - if adapter_name not in adapters: +) -> tuple[str, AdapterDetails]: + await adapters.refresh() + if adapter_name not in adapters.adapters: raise ValueError(f"Adapter '{adapter_name}' not available") - return adapter_name, adapters[adapter_name] + return adapter_name, adapters.adapters[adapter_name] async def get_adapter( diff --git a/tests/fixtures/bluetooth.py b/tests/fixtures/bluetooth.py index f370d36..9b33a00 100644 --- a/tests/fixtures/bluetooth.py +++ b/tests/fixtures/bluetooth.py @@ -1,10 +1,10 @@ import pytest from _pytest.config import Config, Parser +from bluetooth_adapters.models import AdapterDetails from dbus_fast.aio import MessageBus, ProxyObject from dbus_fast.constants import BusType from pb_ble.bluezdbus import get_adapter, get_adapter_details -from pb_ble.bluezdbus.adapters import AdapterDetailsExt def pytest_addoption(parser: Parser): @@ -34,6 +34,6 @@ async def adapter(message_bus: MessageBus, adapter_name: str) -> ProxyObject: @pytest.fixture -async def adapter_details(adapter_name: str) -> AdapterDetailsExt: +async def adapter_details(adapter_name: str) -> AdapterDetails: _, details = await get_adapter_details(adapter_name) return details diff --git a/tests/fixtures/bluez5_mock.py b/tests/fixtures/bluez5_mock.py index afc7cd2..9724af2 100644 --- a/tests/fixtures/bluez5_mock.py +++ b/tests/fixtures/bluez5_mock.py @@ -47,7 +47,14 @@ def adapter_mock(bluez_mock: ProxyObject, adapter_name: str) -> YieldFixture[str with ( unittest.mock.patch( "bluetooth_adapters.systems.linux.get_adapters_from_hci", - return_value={0: {"name": device_name, "bdaddr": "00:00:00:00:00:00"}}, + return_value={ + 0: { + "name": device_name, + "bdaddr": "00:00:00:00:00:00", + "powered": True, + "advertise": True, + } + }, ) as get_adapters_from_hci, # noqa: F841 unittest.mock.patch( "bluetooth_adapters.systems.linux.USBBluetoothDevice", diff --git a/tests/test_advertisement.py b/tests/test_advertisement.py index cd51ecf..47206f1 100644 --- a/tests/test_advertisement.py +++ b/tests/test_advertisement.py @@ -2,11 +2,11 @@ import pytest import pytest_asyncio +from bluetooth_adapters import AdapterDetails from dbus_fast.aio import ProxyObject from dbus_fast.errors import DBusError from pb_ble.bluezdbus import LEAdvertisement, LEAdvertisingManager -from pb_ble.bluezdbus.adapters import AdapterDetailsExt from pb_ble.bluezdbus.advertisement import Include, Type @@ -44,7 +44,7 @@ def test_includes(self) -> None: class TestLEAdvertisingManager: @pytest_asyncio.fixture(autouse=True) async def require_advertise( - self, adapter_details: AdapterDetailsExt, adapter_name: str + self, adapter_details: AdapterDetails, adapter_name: str ) -> None: if not adapter_details["advertise"]: pytest.skip( diff --git a/tests/test_broadcaster.py b/tests/test_broadcaster.py index 057399b..1a0d1d8 100644 --- a/tests/test_broadcaster.py +++ b/tests/test_broadcaster.py @@ -3,21 +3,19 @@ import pytest import pytest_asyncio +from bluetooth_adapters.models import AdapterDetails from dbus_fast.aio import MessageBus, ProxyObject from pb_ble.bluezdbus import ( BlueZBroadcaster, BroadcastAdvertisement, ) -from pb_ble.bluezdbus.adapters import AdapterDetailsExt lock = Lock() @pytest_asyncio.fixture(autouse=True) -async def require_advertise( - adapter_details: AdapterDetailsExt, adapter_name: str -) -> None: +async def require_advertise(adapter_details: AdapterDetails, adapter_name: str) -> None: if not adapter_details["advertise"]: pytest.skip( reason=f"Bluetooth adapter '{adapter_name}' does not support BLE advertising" diff --git a/tests/test_vhub.py b/tests/test_vhub.py index 7baca9b..2992547 100644 --- a/tests/test_vhub.py +++ b/tests/test_vhub.py @@ -1,15 +1,15 @@ import pytest import pytest_asyncio +from bluetooth_adapters.models import AdapterDetails from dbus_fast.aio import ProxyObject from pytest_mock import MockerFixture from pb_ble import get_virtual_ble -from pb_ble.bluezdbus.adapters import AdapterDetailsExt from pb_ble.bluezdbus.observer import ObservedAdvertisement @pytest_asyncio.fixture(autouse=True) -async def require_ble(adapter_details: AdapterDetailsExt, adapter_name: str) -> None: +async def require_ble(adapter_details: AdapterDetails, adapter_name: str) -> None: if not adapter_details["advertise"] or not adapter_details["passive_scan"]: pytest.skip( reason=f"Bluetooth adapter '{adapter_name}' does not support BLE capabilities" From 13583e8d14ce79d567140a63c4ce76934633082b Mon Sep 17 00:00:00 2001 From: Frederik Leonhardt Date: Tue, 26 May 2026 08:49:04 +1200 Subject: [PATCH 2/3] Update dependencies --- pyproject.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9fd7647..15c1612 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,22 +19,22 @@ classifiers = [ dependencies = [ "bleak ~= 2.0", "bluetooth-adapters ~= 2.3", - "cachetools ~= 5.3", - "dbus-fast ~= 4.0", + "cachetools ~= 7.0", + "dbus-fast ~= 5.0", "pybricks @ git+https://github.com/fkleon/pybricks-api@ble-type-fixes", ] [dependency-groups] debug = ["bluetooth-data-tools ~= 1.15"] dev = [ - "async-timer ~= 1.1.6", - "mypy ~= 1.19.1", - "pytest ~= 8.3", + "async-timer ~= 1.2.0", + "mypy ~= 2.1.0", + "pytest ~= 9.0", "pytest-asyncio ~= 1.3.0", "pytest-mock ~= 3.15.1", "python-dbusmock ~= 0.38.1", "ruff ~= 0.15.2", - "types-cachetools ~= 5.3", + "types-cachetools ~= 7.0", "types-setuptools", ] docs = ["pdoc ~= 16.0"] From 339555b5420efe9c2f2a17bdb365de590bdee686 Mon Sep 17 00:00:00 2001 From: Frederik Leonhardt Date: Tue, 26 May 2026 08:49:12 +1200 Subject: [PATCH 3/3] Update bleak to v3.0 --- pyproject.toml | 2 +- src/pb_ble/bluezdbus/observer.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 15c1612..1e1e6f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ classifiers = [ ] dependencies = [ - "bleak ~= 2.0", + "bleak ~= 3.0", "bluetooth-adapters ~= 2.3", "cachetools ~= 7.0", "dbus-fast ~= 5.0", diff --git a/src/pb_ble/bluezdbus/observer.py b/src/pb_ble/bluezdbus/observer.py index 703abe2..ab72d13 100644 --- a/src/pb_ble/bluezdbus/observer.py +++ b/src/pb_ble/bluezdbus/observer.py @@ -131,14 +131,18 @@ def __init__( rssi_threshold, ) + bluez: BlueZScannerArgs = { + "filters": filters, + "or_patterns": or_patterns, + } + + if adapter_name is not None: + bluez["adapter"] = adapter_name + self._scanner = BleakScanner( detection_callback=self._callback, scanning_mode=scanning_mode, - bluez=BlueZScannerArgs( - filters=filters, - or_patterns=or_patterns, - ), - adapter=adapter_name, + bluez=bluez, ) def _callback(self, device: BLEDevice, ad: AdvertisementData):