Skip to content

Commit

Permalink
Merge pull request #903 from hbldh/bluez-passive-scan-fixes
Browse files Browse the repository at this point in the history
bluezdbus/manager: call advertisement callbacks from InterfacesAdded
  • Loading branch information
dlech authored Jul 26, 2022
2 parents 726c579 + da5ff1f commit 5aabf0a
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Fixed

* Documentation fixes.
* On empty characteristic description from WinRT, use the lookup table instead of returning empty string.
* Fixed detection of first advertisement in BlueZ backend. Merged #903.


`0.14.3`_ (2022-04-29)
Expand Down
94 changes: 62 additions & 32 deletions bleak/backends/bluezdbus/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,18 @@
import asyncio
import logging
import os
from typing import Any, Callable, Coroutine, Dict, List, NamedTuple, Set, Tuple, cast
from typing import (
Any,
Callable,
Coroutine,
Dict,
Iterable,
List,
NamedTuple,
Set,
Tuple,
cast,
)

from dbus_next import BusType, Message, MessageType, Variant
from dbus_next.aio.message_bus import MessageBus
Expand Down Expand Up @@ -484,9 +495,18 @@ def _parse_msg(self, message: Message):
obj_path, interfaces_and_props = message.body

for interface, props in interfaces_and_props.items():
self._properties.setdefault(obj_path, {})[interface] = unpack_variants(
props
)
unpacked_props = unpack_variants(props)
self._properties.setdefault(obj_path, {})[interface] = unpacked_props

# If this is a device and it has advertising data properties,
# then it should mean that this device just started advertising.
# Previously, we just relied on RSSI updates to determine if
# a device was actually advertising, but we were missing "slow"
# devices that only advertise once and then go to sleep for a while.
if interface == defs.DEVICE_INTERFACE:
self._run_advertisement_callbacks(
obj_path, cast(Device1, unpacked_props), unpacked_props.keys()
)
elif message.member == "InterfacesRemoved":
obj_path, interfaces = message.body

Expand All @@ -509,37 +529,47 @@ def _parse_msg(self, message: Message):
else:
self_interface.update(unpack_variants(changed))

if interface == defs.DEVICE_INTERFACE:
for (
callback,
adapter_path,
seen_devices,
) in self._advertisement_callbacks:
# filter messages from other adapters
if not message.path.startswith(adapter_path):
continue

first_time_seen = False

if message.path not in seen_devices:
first_time_seen = True
seen_devices.add(message.path)

# Only do advertising data callback if this is the first time the
# device has been seen or if an advertising data property changed.
# Otherwise we get a flood of callbacks from RSSI changing.
if (
first_time_seen
or not _ADVERTISING_DATA_PROPERTIES.isdisjoint(
changed.keys()
)
):
# TODO: this should be deep copy, not shallow
callback(message.path, cast(Device1, self_interface.copy()))

for name in invalidated:
del self_interface[name]

if interface == defs.DEVICE_INTERFACE:
self._run_advertisement_callbacks(
message.path, cast(Device1, self_interface), changed.keys()
)

def _run_advertisement_callbacks(
self, device_path: str, device: Device1, changed: Iterable[str]
) -> None:
"""
Runs any registered advertisement callbacks.
Args:
device_path: The D-Bus object path of the remote device.
device: The current D-Bus properties of the device.
changed: A list of properties that have changed since the last call.
"""
for (
callback,
adapter_path,
seen_devices,
) in self._advertisement_callbacks:
# filter messages from other adapters
if not device_path.startswith(adapter_path):
continue

first_time_seen = False

if device_path not in seen_devices:
first_time_seen = True
seen_devices.add(device_path)

# Only do advertising data callback if this is the first time the
# device has been seen or if an advertising data property changed.
# Otherwise we get a flood of callbacks from RSSI changing.
if first_time_seen or not _ADVERTISING_DATA_PROPERTIES.isdisjoint(changed):
# TODO: this should be deep copy, not shallow
callback(device_path, cast(Device1, device.copy()))


async def get_global_bluez_manager() -> BlueZManager:
"""
Expand Down

0 comments on commit 5aabf0a

Please sign in to comment.