Skip to content

Commit e56cc25

Browse files
committed
bluezdbus/scanner: Detect AdvertisementMonitor not registering
1 parent 6da7322 commit e56cc25

File tree

3 files changed

+47
-6
lines changed

3 files changed

+47
-6
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Added
1717
* Added optional command line argument to use debug log level to all applicable examples.
1818
* Make sure the disconnect monitor task is properly cancelled on the BlueZ client.
1919
* Added ``BleakNoPassiveScanError`` exception.
20+
* Make sure that BlueZ Advertisement Monitor is actually registered when passive scanning. Solves #1136.
2021

2122
Changed
2223
-------

bleak/backends/bluezdbus/advertisement_monitor.py

Lines changed: 11 additions & 3 deletions
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 Callable, Iterable, NamedTuple, Tuple, Union, no_type_check
1111

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

@@ -34,6 +34,9 @@ class OrPattern(NamedTuple):
3434
OrPatternLike = Union[OrPattern, Tuple[int, AdvertisementDataType, bytes]]
3535

3636

37+
StatusCallback = Callable[[bool], None]
38+
39+
3740
class AdvertisementMonitor(ServiceInterface):
3841
"""
3942
Implementation of the org.bluez.AdvertisementMonitor1 D-Bus interface.
@@ -49,25 +52,30 @@ class AdvertisementMonitor(ServiceInterface):
4952
"""
5053

5154
def __init__(
52-
self,
53-
or_patterns: Iterable[OrPatternLike],
55+
self, or_patterns: Iterable[OrPatternLike], status_callback: StatusCallback
5456
):
5557
"""
5658
Args:
5759
or_patterns:
5860
List of or patterns that will be returned by the ``Patterns`` property.
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.
5964
"""
6065
super().__init__(defs.ADVERTISEMENT_MONITOR_INTERFACE)
6166
# dbus_fast marshaling requires list instead of tuple
6267
self._or_patterns = [list(p) for p in or_patterns]
68+
self._status_callback = status_callback
6369

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

6875
@method()
6976
def Activate(self):
7077
logger.debug("Activate")
78+
self._status_callback(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: 35 additions & 3 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

@@ -448,12 +454,20 @@ async def passive_scan(
448454
)
449455
self._device_removed_callbacks.append(device_removed_callback_and_state)
450456

457+
# If advertisement monitor is released before the scanning is stopped, it means that the
458+
# kernel does not support passive scanning and error was returned when trying to execute
459+
# MGMT command "Add Adv Patterns Monitor" (see #1136). Otherwise, monitor is activated
460+
# and starts to receive advertisement packets.
461+
monitor_activated = asyncio.Queue()
462+
451463
try:
452-
monitor = AdvertisementMonitor(filters)
464+
monitor = AdvertisementMonitor(filters, monitor_activated.put_nowait)
453465

454466
# this should be a unique path to allow multiple python interpreters
455467
# running bleak and multiple scanners within a single interpreter
456-
monitor_path = f"/org/bleak/{os.getpid()}/{id(monitor)}"
468+
monitor_path = (
469+
f"/org/bleak/{os.getpid()}/{type(monitor).__name__}_{id(monitor)}"
470+
)
457471

458472
reply = await self._bus.call(
459473
Message(
@@ -505,14 +519,32 @@ async def stop():
505519
)
506520
assert_reply(reply)
507521

508-
return stop
522+
try:
523+
# Advertising Monitor will be "immediately" activated or released
524+
async with async_timeout(1):
525+
if await monitor_activated.get():
526+
# Advertising Monitor has been activated
527+
return stop
528+
529+
except asyncio.TimeoutError:
530+
pass
531+
532+
# Do not call await stop() here as the bus is already locked
509533

510534
except BaseException:
511535
# if starting scanning failed, don't leak the callbacks
512536
self._advertisement_callbacks.remove(callback_and_state)
513537
self._device_removed_callbacks.remove(device_removed_callback_and_state)
514538
raise
515539

540+
# Reaching here means that the Advertising Monitor has not been successfully activated
541+
await stop()
542+
543+
raise BleakNoPassiveScanError(
544+
"Advertising Monitor (required for passive scanning) is not supported by this kernel"
545+
" (Linux kernel >= 5.10 is required)"
546+
)
547+
516548
def add_device_watcher(
517549
self,
518550
device_path: str,

0 commit comments

Comments
 (0)