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

feat: add connect scanner helper #4

Merged
merged 3 commits into from
Dec 17, 2023
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
97 changes: 95 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ aioesphomeapi = ">=21.0.0"
bleak = ">=0.21.1"
bluetooth-data-tools = ">=1.18.0"
habluetooth = ">=1.0.0"
lru-dict = ">=1.2.0"

[tool.poetry.group.dev.dependencies]
pytest = "^7.0"
Expand Down
5 changes: 5 additions & 0 deletions src/bleak_esphome/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .connect import connect_scanner

__all__ = [
"connect_scanner",
]
120 changes: 120 additions & 0 deletions src/bleak_esphome/connect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Bluetooth support for esphome."""
from __future__ import annotations

import asyncio
import logging
from collections.abc import Coroutine
from functools import partial
from typing import TYPE_CHECKING, Any, Callable

from aioesphomeapi import APIClient, BluetoothProxyFeature, DeviceInfo
from habluetooth import (
HaBluetoothConnector,
)

from .backend.cache import ESPHomeBluetoothCache
from .backend.client import ESPHomeClient, ESPHomeClientData
from .backend.device import ESPHomeBluetoothDevice
from .backend.scanner import ESPHomeScanner

_LOGGER = logging.getLogger(__name__)


def _can_connect(bluetooth_device: ESPHomeBluetoothDevice, source: str) -> bool:
"""Check if a given source can make another connection."""
can_connect = bool(

Check warning on line 25 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L25

Added line #L25 was not covered by tests
bluetooth_device.available and bluetooth_device.ble_connections_free
)
_LOGGER.debug(

Check warning on line 28 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L28

Added line #L28 was not covered by tests
(
"%s [%s]: Checking can connect, available=%s, ble_connections_free=%s"
" result=%s"
),
bluetooth_device.name,
source,
bluetooth_device.available,
bluetooth_device.ble_connections_free,
can_connect,
)
return can_connect

Check warning on line 39 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L39

Added line #L39 was not covered by tests


async def connect_scanner(
cli: APIClient,
device_info: DeviceInfo,
cache: ESPHomeBluetoothCache,
available: bool,
) -> ESPHomeClientData:
"""
Connect scanner.

The caller is responsible for:

1. Calling ESPHomeClientData.scanner.async_setup()
2. Calling ESPHomeClientData.disconnect_callbacks when the ESP is disconnected.
3. Registering the scanner with the HA Bluetooth manager and also
un-registering it when the ESP is disconnected.

The caller may choose to override ESPHomeClientData.disconnect_callbacks
with its own set. If it does so, it must do so before calling
ESPHomeClientData.scanner.async_setup().
"""
source = device_info.mac_address
name = device_info.name

Check warning on line 63 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L62-L63

Added lines #L62 - L63 were not covered by tests
if TYPE_CHECKING:
assert cli.api_version is not None
feature_flags = device_info.bluetooth_proxy_feature_flags_compat(cli.api_version)
connectable = bool(feature_flags & BluetoothProxyFeature.ACTIVE_CONNECTIONS)
bluetooth_device = ESPHomeBluetoothDevice(

Check warning on line 68 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L66-L68

Added lines #L66 - L68 were not covered by tests
name, device_info.mac_address, available=available
)
_LOGGER.debug(

Check warning on line 71 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L71

Added line #L71 was not covered by tests
"%s [%s]: Connecting scanner feature_flags=%s, connectable=%s",
name,
source,
feature_flags,
connectable,
)
client_data = ESPHomeClientData(

Check warning on line 78 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L78

Added line #L78 was not covered by tests
bluetooth_device=bluetooth_device,
cache=cache,
client=cli,
device_info=device_info,
api_version=cli.api_version,
title=name,
scanner=None,
)
connector = HaBluetoothConnector(

Check warning on line 87 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L87

Added line #L87 was not covered by tests
client=partial(ESPHomeClient, client_data=client_data),
source=source,
can_connect=partial(_can_connect, bluetooth_device, source),
)
scanner = ESPHomeScanner(source, name, connector, connectable)
client_data.scanner = scanner
coros: list[Coroutine[Any, Any, Callable[[], None]]] = []

Check warning on line 94 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L92-L94

Added lines #L92 - L94 were not covered by tests
# These calls all return a callback that can be used to unsubscribe
# but we never unsubscribe so we don't care about the return value

if connectable:
# If its connectable be sure not to register the scanner
# until we know the connection is fully setup since otherwise
# there is a race condition where the connection can fail
coros.append(

Check warning on line 102 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L102

Added line #L102 was not covered by tests
cli.subscribe_bluetooth_connections_free(
bluetooth_device.async_update_ble_connection_limits
)
)

if feature_flags & BluetoothProxyFeature.RAW_ADVERTISEMENTS:
coros.append(

Check warning on line 109 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L109

Added line #L109 was not covered by tests
cli.subscribe_bluetooth_le_raw_advertisements(
scanner.async_on_raw_advertisements
)
)
else:
coros.append(

Check warning on line 115 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L115

Added line #L115 was not covered by tests
cli.subscribe_bluetooth_le_advertisements(scanner.async_on_advertisement)
)

await asyncio.gather(*coros)
return client_data

Check warning on line 120 in src/bleak_esphome/connect.py

View check run for this annotation

Codecov / codecov/patch

src/bleak_esphome/connect.py#L119-L120

Added lines #L119 - L120 were not covered by tests
Loading