|
9 | 9 | import asyncio |
10 | 10 | import logging |
11 | 11 | import os |
| 12 | +import sys |
12 | 13 | from typing import ( |
13 | 14 | Any, |
14 | 15 | Callable, |
|
24 | 25 | ) |
25 | 26 | from weakref import WeakKeyDictionary |
26 | 27 |
|
| 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 | + |
27 | 33 | from dbus_fast import BusType, Message, MessageType, Variant, unpack_variants |
28 | 34 | from dbus_fast.aio.message_bus import MessageBus |
29 | 35 |
|
@@ -448,12 +454,20 @@ async def passive_scan( |
448 | 454 | ) |
449 | 455 | self._device_removed_callbacks.append(device_removed_callback_and_state) |
450 | 456 |
|
| 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 | + |
451 | 463 | try: |
452 | | - monitor = AdvertisementMonitor(filters) |
| 464 | + monitor = AdvertisementMonitor(filters, monitor_activated.put_nowait) |
453 | 465 |
|
454 | 466 | # this should be a unique path to allow multiple python interpreters |
455 | 467 | # 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 | + ) |
457 | 471 |
|
458 | 472 | reply = await self._bus.call( |
459 | 473 | Message( |
@@ -505,14 +519,32 @@ async def stop(): |
505 | 519 | ) |
506 | 520 | assert_reply(reply) |
507 | 521 |
|
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 |
509 | 533 |
|
510 | 534 | except BaseException: |
511 | 535 | # if starting scanning failed, don't leak the callbacks |
512 | 536 | self._advertisement_callbacks.remove(callback_and_state) |
513 | 537 | self._device_removed_callbacks.remove(device_removed_callback_and_state) |
514 | 538 | raise |
515 | 539 |
|
| 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 | + |
516 | 548 | def add_device_watcher( |
517 | 549 | self, |
518 | 550 | device_path: str, |
|
0 commit comments