Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache service resolution on connect when using BlueZ/Dbus backend when requested #923

Merged
merged 7 commits into from
Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Changed
* Extended disconnect timeout to 120 seconds in WinRT backend. Fixes #807.
* Changed version check for BlueZ battery workaround to exclude versions >= 5.55. Merged #976.
* Use Poetry for build system and dependencies.
* The BlueZ D-Bus backend implements a services cache between connections to significancy improve reconnect performance.
To use the cache, call ``connect`` and ``get_services`` with the ``dangerous_use_bleak_cache``
argument to avoid services being resolved again.

Fixed
-----
Expand Down
21 changes: 17 additions & 4 deletions bleak/backends/bluezdbus/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def __init__(self, address_or_ble_device: Union[BLEDevice, str], **kwargs):

# Connectivity methods

async def connect(self, **kwargs) -> bool:
async def connect(self, dangerous_use_bleak_cache: bool = False, **kwargs) -> bool:
"""Connect to the specified GATT server.

Keyword Args:
Expand Down Expand Up @@ -172,7 +172,13 @@ def on_value_changed(char_path: str, value: bytes) -> None:
self._disconnect_monitor_event = asyncio.Event()
asyncio.ensure_future(self._disconnect_monitor())

await self.get_services()
#
# We will try to use the cache if it exists and `dangerous_use_bleak_cache`
# is True.
#
await self.get_services(
dangerous_use_bleak_cache=dangerous_use_bleak_cache
)

return True
except BaseException:
Expand Down Expand Up @@ -471,9 +477,14 @@ def mtu_size(self) -> int:

# GATT services methods

async def get_services(self, **kwargs) -> BleakGATTServiceCollection:
async def get_services(
self, dangerous_use_bleak_cache: bool = False, **kwargs
) -> BleakGATTServiceCollection:
"""Get all services registered for this GATT server.

Args:
dangerous_use_bleak_cache (bool): Use cached services if available.

Returns:
A :py:class:`bleak.backends.service.BleakGATTServiceCollection` with this device's services tree.

Expand All @@ -486,7 +497,9 @@ async def get_services(self, **kwargs) -> BleakGATTServiceCollection:

manager = await get_global_bluez_manager()

self.services = await manager.get_services(self._device_path)
self.services = await manager.get_services(
self._device_path, dangerous_use_bleak_cache
)
self._services_resolved = True

return self.services
Expand Down
23 changes: 21 additions & 2 deletions bleak/backends/bluezdbus/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ def __init__(self):
self._device_removed_callbacks: List[DeviceRemovedCallbackAndState] = []
self._device_watchers: Set[DeviceWatcher] = set()
self._condition_callbacks: Set[Callable] = set()
self._services_cache: Dict[str, BleakGATTServiceCollection] = {}

async def async_init(self):
"""
Expand All @@ -176,6 +177,8 @@ async def async_init(self):
if self._bus and self._bus.connected:
return

self._services_cache = {}

# We need to create a new MessageBus each time as
# dbus-next will destory the underlying file descriptors
# when the previous one is closed in its finalizer.
Expand Down Expand Up @@ -508,16 +511,29 @@ def remove_device_watcher(self, watcher: DeviceWatcher) -> None:
"""
self._device_watchers.remove(watcher)

async def get_services(self, device_path: str) -> BleakGATTServiceCollection:
async def get_services(
self, device_path: str, use_cached: bool
dlech marked this conversation as resolved.
Show resolved Hide resolved
) -> BleakGATTServiceCollection:
"""
Builds a new :class:`BleakGATTServiceCollection` from the current state.

Args:
device_path: The D-Bus object path of the Bluetooth device.
device_path:
The D-Bus object path of the Bluetooth device.
use_cached:
When ``True`` if there is a cached :class:`BleakGATTServiceCollection`,
the method will not wait for ``"ServicesResolved"`` to become true
and instead return the cached service collection immediately.

Returns:
A new :class:`BleakGATTServiceCollection`.
"""
if use_cached:
services = self._services_cache.get(device_path)
if services is not None:
logger.debug("Using cached services for %s", device_path)
return services

await self._wait_condition(device_path, "ServicesResolved", True)

services = BleakGATTServiceCollection()
Expand Down Expand Up @@ -565,6 +581,8 @@ async def get_services(self, device_path: str) -> BleakGATTServiceCollection:

services.add_descriptor(desc)

self._services_cache[device_path] = services

return services

def get_device_name(self, device_path: str) -> str:
Expand Down Expand Up @@ -676,6 +694,7 @@ def _parse_msg(self, message: Message):
del self._properties[obj_path][interface]

if interface == defs.DEVICE_INTERFACE:
self._services_cache.pop(obj_path, None)
try:
del self._service_map[obj_path]
except KeyError:
Expand Down
5 changes: 5 additions & 0 deletions docs/backends/linux.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ Before that commit, ``Characteristic.WriteValue`` was only "Write with response"
`Bluez 5.46 <https://git.kernel.org/pub/scm/bluetooth/bluez.git/commit/doc/gatt-api.txt?id=f59f3dedb2c79a75e51a3a0d27e2ae06fefc603e>`_
which can be used to "Write without response", but for older versions of Bluez (5.43, 5.44, 5.45), it is not possible to "Write without response".


Resolving services with ``get_services``
----------------------------------------

By default, calling ``get_services`` will wait for services to be resolved before returning the ``BleakGATTServiceCollection``. If a previous connection to the device was made, passing the ``dangerous_use_bleak_cache`` argument will return the cached services without waiting for them to be resolved again. This is useful when you know services have not changed, and you want to use the services immediately, but don't want to wait for them to be resolved again.