Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,24 @@ classifiers = [
]

dependencies = [
"bleak ~= 2.0",
"bluetooth-adapters ~= 2.1",
"cachetools ~= 5.3",
"dbus-fast ~= 4.0",
"bleak ~= 3.0",
"bluetooth-adapters ~= 2.3",
"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"]
Expand Down
40 changes: 4 additions & 36 deletions src/pb_ble/bluezdbus/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
14 changes: 9 additions & 5 deletions src/pb_ble/bluezdbus/observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions tests/fixtures/bluetooth.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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
9 changes: 8 additions & 1 deletion tests/fixtures/bluez5_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_advertisement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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(
Expand Down
6 changes: 2 additions & 4 deletions tests/test_broadcaster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions tests/test_vhub.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Loading