Skip to content

Commit 8ab27d6

Browse files
committed
bluezdbus/scanner: Detect AdvertisementMonitor not registering
1 parent 6afd00c commit 8ab27d6

File tree

3 files changed

+51
-11
lines changed

3 files changed

+51
-11
lines changed

bleak/backends/bluezdbus/advertisement_monitor.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class OrPattern(NamedTuple):
3434
OrPatternLike = Union[OrPattern, Tuple[int, AdvertisementDataType, bytes]]
3535

3636

37-
ReleasedCallback = Callable[[], None]
37+
StatusCallback = Callable[[bool], None]
3838

3939

4040
class AdvertisementMonitor(ServiceInterface):
@@ -52,28 +52,30 @@ class AdvertisementMonitor(ServiceInterface):
5252
"""
5353

5454
def __init__(
55-
self, or_patterns: Iterable[OrPatternLike], released_callback: ReleasedCallback
55+
self, or_patterns: Iterable[OrPatternLike], status_callback: StatusCallback
5656
):
5757
"""
5858
Args:
5959
or_patterns:
6060
List of or patterns that will be returned by the ``Patterns`` property.
61-
released_callback:
62-
A callback that is called when the D-bus "Release" method is called.
61+
status_callback:
62+
A callback that is called with argument ``True`` when the D-bus "Activate"
63+
method is called, or with ``False`` when "Release" is called.
6364
"""
6465
super().__init__(defs.ADVERTISEMENT_MONITOR_INTERFACE)
6566
# dbus_fast marshaling requires list instead of tuple
6667
self._or_patterns = [list(p) for p in or_patterns]
67-
self._released_callback = released_callback
68+
self._status_callback = status_callback
6869

6970
@method()
7071
def Release(self):
7172
logger.debug("Release")
72-
self._released_callback()
73+
self._status_callback(False)
7374

7475
@method()
7576
def Activate(self):
7677
logger.debug("Activate")
78+
self._status_callback(True)
7779

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

bleak/backends/bluezdbus/manager.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import asyncio
1010
import logging
1111
import os
12+
import sys
1213
from typing import (
1314
Any,
1415
Callable,
@@ -24,6 +25,11 @@
2425
)
2526
from weakref import WeakKeyDictionary
2627

28+
if sys.version_info < (3, 11):
29+
from async_timeout import timeout as async_timeout
30+
else:
31+
from asyncio import timeout as async_timeout
32+
2733
from dbus_fast import BusType, Message, MessageType, Variant, unpack_variants
2834
from dbus_fast.aio.message_bus import MessageBus
2935

@@ -441,7 +447,6 @@ async def passive_scan(
441447
filters: List[OrPatternLike],
442448
advertisement_callback: AdvertisementCallback,
443449
device_removed_callback: DeviceRemovedCallback,
444-
discovery_stopped_callback: DiscoveryStoppedCallback,
445450
) -> Callable[[], Coroutine]:
446451
"""
447452
Configures the advertisement data filters and starts scanning.
@@ -472,12 +477,28 @@ async def passive_scan(
472477
)
473478
self._device_removed_callbacks.append(device_removed_callback_and_state)
474479

480+
# Once a monitoring job is activated by BlueZ, the client can expect to get notified
481+
# on the targeted advertisements no matter if there is an ongoing discovery session
482+
# (started/stopped with org.bluez.Adapter1.StartDiscovery/StopDiscovery, as done when
483+
# using active scanning).
484+
# That is why discovery_stopped_callback is not added to self._discovery_stopped_callbacks
485+
# at this point, as org.bluez.Adapter1.Discovering status is not relevant for passive
486+
# scanning.
487+
488+
# If advertisement monitor is released before the scanning is stopped, it means that the
489+
# kernel does not support passive scanning and error was returned when trying to execute
490+
# MGMT command "Add Adv Patterns Monitor" (see https://github.com/hbldh/bleak/issues/1136).
491+
# Otherwise, monitor will be activated and start to receive advertisement packets.
492+
monitor_processed = asyncio.Queue()
493+
475494
try:
476-
monitor = AdvertisementMonitor(filters, discovery_stopped_callback)
495+
monitor = AdvertisementMonitor(filters, monitor_processed.put_nowait)
477496

478497
# this should be a unique path to allow multiple python interpreters
479498
# running bleak and multiple scanners within a single interpreter
480-
monitor_path = f"/org/bleak/{os.getpid()}/{id(monitor)}"
499+
monitor_path = (
500+
f"/org/bleak/{os.getpid()}/{type(monitor).__name__}_{id(monitor)}"
501+
)
481502

482503
reply = await self._bus.call(
483504
Message(
@@ -529,14 +550,32 @@ async def stop():
529550
)
530551
assert_reply(reply)
531552

532-
return stop
553+
try:
554+
# Advertising Monitor will be "immediately" activated or released
555+
async with async_timeout(1):
556+
if await monitor_processed.get():
557+
# Advertising Monitor has been activated
558+
return stop
559+
560+
except asyncio.TimeoutError:
561+
pass
562+
563+
# Do not call await stop() here as the bus is already locked
533564

534565
except BaseException:
535566
# if starting scanning failed, don't leak the callbacks
536567
self._advertisement_callbacks.remove(callback_and_state)
537568
self._device_removed_callbacks.remove(device_removed_callback_and_state)
538569
raise
539570

571+
# Reaching here means that the Advertising Monitor has not been successfully activated
572+
await stop()
573+
574+
raise BleakNoPassiveScanError(
575+
"Advertising Monitor (required for passive scanning) is not supported by this kernel"
576+
" (Linux kernel >= 5.10 is required)"
577+
)
578+
540579
def add_device_watcher(
541580
self,
542581
device_path: str,

bleak/backends/bluezdbus/scanner.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ async def start(self):
186186
self._or_patterns,
187187
self._handle_advertising_data,
188188
self._handle_device_removed,
189-
self.handle_early_stop,
190189
)
191190
else:
192191
self._stop = await manager.active_scan(

0 commit comments

Comments
 (0)