Skip to content

Commit eead505

Browse files
committed
bluez: Verify that Advertisement Monitor has been registered
1 parent b39beec commit eead505

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Added
1414
-----
1515
* Added optional hack to use Bluetooth address instead of UUID on macOS.
1616
* Added ``BleakScanner.find_device_by_name()`` class method.
17+
* Added check to verify that BlueZ Advertisement Monitor was actually registered. Solves #1136.
1718

1819
Changed
1920
-------

bleak/backends/bluezdbus/advertisement_monitor.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88

99
import logging
10-
from typing import Iterable, NamedTuple, Tuple, Union, no_type_check
10+
from typing import Iterable, NamedTuple, Tuple, Union, no_type_check, Optional
1111

1212
from dbus_fast.service import ServiceInterface, dbus_property, method, PropertyAccess
1313

@@ -60,14 +60,22 @@ def __init__(
6060
super().__init__(defs.ADVERTISEMENT_MONITOR_INTERFACE)
6161
# dbus_fast marshaling requires list instead of tuple
6262
self._or_patterns = [list(p) for p in or_patterns]
63+
self._activated: Optional[bool] = None
64+
65+
@property
66+
def active(self) -> Optional[bool]:
67+
"""``True`` if this monitor has been activated, ``False`` if released, or ``None`` if neither has occurred."""
68+
return self._activated
6369

6470
@method()
6571
def Release(self):
6672
logger.debug("Release")
73+
self._activated = False
6774

6875
@method()
6976
def Activate(self):
7077
logger.debug("Activate")
78+
self._activated = True
7179

7280
# REVISIT: mypy is broke, so we have to add redundant @no_type_check
7381
# https://github.com/python/mypy/issues/6583

bleak/backends/bluezdbus/manager.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ async def passive_scan(
448448

449449
# this should be a unique path to allow multiple python interpreters
450450
# running bleak and multiple scanners within a single interpreter
451-
monitor_path = f"/org/bleak/{os.getpid()}/{id(monitor)}"
451+
monitor_path = f"/org/bleak/{os.getpid()}/{AdvertisementMonitor.__name__}{id(monitor)}"
452452

453453
reply = await self._bus.call(
454454
Message(
@@ -471,10 +471,47 @@ async def passive_scan(
471471

472472
assert_reply(reply)
473473

474-
# It is important to export after registering, otherwise BlueZ
475-
# won't use the monitor
474+
# It is important to export AFTER registering, otherwise BlueZ won't use the monitor
476475
self._bus.export(monitor_path, monitor)
477476

477+
# During export, MGMT command "Add Advertisement Patterns Monitor (0x0052)" is executed.
478+
# If Linux kernel is too old to support this command, MGMT event "Command Status (0x0002)"
479+
# is returned with "Status: Unknown Command (0x01)" - bleak cannot detect this error and
480+
# trying to scan would result in no devices ever reported, as Adv. Monitor was not registered.
481+
# Running bluetoothd with -d flag gives the following output for 'journalctl --unit=bluetooth -f':
482+
# bluetoothd[...]: Path /org/bleak/.../AdvertisementMonitor... reserved for Adv Monitor app :...
483+
# ...
484+
# bluetoothd[...]: src/shared/mgmt.c:send_request() [0x0000] command 0x0052
485+
# bluetoothd[...]: src/shared/mgmt.c:can_read_data() [0x0000] command 0x52 status: 0x01
486+
# bluetoothd[...]: Failed to Add Adv Patterns Monitor with status 0x01
487+
# bluetoothd[...]: src/adv_monitor.c:monitor_release() Calling Release() on Adv Monitor
488+
# of owner :... at path /org/bleak/.../AdvertisementMonitor...
489+
# bluetoothd[...]: Merged_pattern not found when removing monitor
490+
# ...
491+
# Note that `monitor.Release()` cannot be invoked if control is not passed to the event loop.
492+
# Rather than using asyncio.sleep(), read property to ensure that all queued D-Bus calls are executed.
493+
reply = await self._bus.call(
494+
Message(
495+
destination=defs.BLUEZ_SERVICE,
496+
path=adapter_path,
497+
interface=defs.PROPERTIES_INTERFACE,
498+
member="Get",
499+
signature="ss",
500+
body=[
501+
defs.ADVERTISEMENT_MONITOR_MANAGER_INTERFACE,
502+
"SupportedFeatures",
503+
],
504+
)
505+
)
506+
assert_reply(reply)
507+
508+
# Monitor might not be Activate-d yet, but for sure it shall not be Release-d
509+
if monitor.active is False:
510+
raise BleakError(
511+
"Advertising Monitor (required for passive scanning) is not supported by this kernel"
512+
" (Linux kernel >= 5.10 is required)"
513+
)
514+
478515
async def stop():
479516
# need to remove callbacks first, otherwise we get TxPower
480517
# and RSSI properties removed during stop which causes

0 commit comments

Comments
 (0)