Skip to content

Commit 9c093b5

Browse files
authored
feat: update scanner mode from callback (#137)
1 parent f01395d commit 9c093b5

File tree

5 files changed

+91
-7
lines changed

5 files changed

+91
-7
lines changed

src/bleak_esphome/backend/scanner.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
from __future__ import annotations
44

5-
from aioesphomeapi import BluetoothLEAdvertisement, BluetoothLERawAdvertisementsResponse
5+
from aioesphomeapi import (
6+
BluetoothLEAdvertisement,
7+
BluetoothLERawAdvertisementsResponse,
8+
BluetoothScannerMode,
9+
BluetoothScannerStateResponse,
10+
)
611
from bluetooth_data_tools import (
712
int_to_bluetooth_address,
813
)
914
from bluetooth_data_tools import (
1015
monotonic_time_coarse as MONOTONIC_TIME,
1116
)
17+
from habluetooth import BluetoothScanningMode
1218
from habluetooth.base_scanner import BaseHaRemoteScanner
1319

1420

@@ -17,6 +23,18 @@ class ESPHomeScanner(BaseHaRemoteScanner):
1723

1824
__slots__ = ()
1925

26+
def async_update_scanner_state(self, state: BluetoothScannerStateResponse) -> None:
27+
"""Update the scanner state."""
28+
if state.mode == BluetoothScannerMode.ACTIVE:
29+
self.current_mode = BluetoothScanningMode.ACTIVE # type: ignore[misc]
30+
self.requested_mode = BluetoothScanningMode.ACTIVE # type: ignore[misc]
31+
elif state.mode == BluetoothScannerMode.PASSIVE:
32+
self.current_mode = BluetoothScanningMode.PASSIVE
33+
self.requested_mode = BluetoothScanningMode.PASSIVE
34+
else:
35+
self.current_mode = None
36+
self.requested_mode = None
37+
2038
def async_on_advertisement(self, adv: BluetoothLEAdvertisement) -> None:
2139
"""Call the registered callback."""
2240
# The mac address is a uint64, but we need a string

src/bleak_esphome/connect.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ def connect_scanner(
9595
bluetooth_device.async_update_ble_connection_limits
9696
)
9797

98+
if feature_flags & BluetoothProxyFeature.FEATURE_STATE_AND_MODE:
99+
_LOGGER.debug(
100+
"%s [%s]: Bluetooth scanner state and mode support available", name, source
101+
)
102+
cli.subscribe_bluetooth_scanner_state(scanner.async_update_scanner_state)
103+
98104
if feature_flags & BluetoothProxyFeature.RAW_ADVERTISEMENTS:
99105
cli.subscribe_bluetooth_le_raw_advertisements(
100106
scanner.async_on_raw_advertisements

tests/backend/test_scanner.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1+
import pytest
12
from aioesphomeapi import (
3+
APIClient,
24
BluetoothLERawAdvertisement,
35
BluetoothLERawAdvertisementsResponse,
6+
BluetoothScannerMode,
7+
BluetoothScannerStateResponse,
48
)
59
from bluetooth_data_tools import int_to_bluetooth_address
6-
from habluetooth import BaseHaRemoteScanner, HaBluetoothConnector, get_manager
10+
from habluetooth import (
11+
BaseHaRemoteScanner,
12+
BluetoothScanningMode,
13+
HaBluetoothConnector,
14+
get_manager,
15+
)
716

817
from bleak_esphome.backend.client import ESPHomeClientData
918
from bleak_esphome.backend.scanner import ESPHomeScanner
@@ -12,15 +21,18 @@
1221
ESP_NAME = "proxy"
1322

1423

15-
def test_scanner() -> None:
24+
@pytest.fixture
25+
def scanner() -> ESPHomeScanner:
26+
"""Fixture to create an ESPHomeScanner instance."""
1627
connector = HaBluetoothConnector(ESPHomeClientData, ESP_MAC_ADDRESS, lambda: True)
17-
scanner = ESPHomeScanner(ESP_MAC_ADDRESS, ESP_NAME, connector, True)
28+
return ESPHomeScanner(ESP_MAC_ADDRESS, ESP_NAME, connector, True)
29+
30+
31+
def test_scanner(scanner: ESPHomeScanner) -> None:
1832
assert isinstance(scanner, BaseHaRemoteScanner)
1933

2034

21-
def test_scanner_async_on_advertisement() -> None:
22-
connector = HaBluetoothConnector(ESPHomeClientData, ESP_MAC_ADDRESS, lambda: True)
23-
scanner = ESPHomeScanner(ESP_MAC_ADDRESS, ESP_NAME, connector, True)
35+
def test_scanner_async_on_advertisement(scanner: ESPHomeScanner) -> None:
2436
adv = BluetoothLERawAdvertisementsResponse(
2537
advertisements=[
2638
BluetoothLERawAdvertisement(
@@ -45,3 +57,26 @@ def test_scanner_async_on_advertisement() -> None:
4557
assert manager.async_last_service_info(
4658
int_to_bluetooth_address(246965243285491), True
4759
)
60+
61+
62+
def test_scanner_async_update_scanner_state(
63+
scanner: ESPHomeScanner, mock_client: APIClient
64+
) -> None:
65+
mock_client.subscribe_bluetooth_scanner_state(scanner.async_update_scanner_state)
66+
scanner.async_update_scanner_state(
67+
BluetoothScannerStateResponse(
68+
mode=BluetoothScannerMode.ACTIVE,
69+
)
70+
)
71+
assert scanner.current_mode == BluetoothScanningMode.ACTIVE
72+
assert scanner.requested_mode == BluetoothScanningMode.ACTIVE
73+
scanner.async_update_scanner_state(
74+
BluetoothScannerStateResponse(
75+
mode=BluetoothScannerMode.PASSIVE,
76+
)
77+
)
78+
assert scanner.current_mode == BluetoothScanningMode.PASSIVE
79+
assert scanner.requested_mode == BluetoothScanningMode.PASSIVE
80+
scanner.async_update_scanner_state(BluetoothScannerStateResponse(mode=None))
81+
assert scanner.current_mode is None
82+
assert scanner.requested_mode is None

tests/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from aioesphomeapi import (
55
APIClient,
66
APIVersion,
7+
BluetoothProxyFeature,
78
DeviceInfo,
89
ReconnectLogic,
910
)
@@ -32,6 +33,14 @@ def mock_device_info() -> DeviceInfo:
3233
legacy_bluetooth_proxy_version=0,
3334
# ESPHome mac addresses are UPPER case
3435
mac_address="11:22:33:44:55:AA",
36+
bluetooth_mac_address="11:22:33:44:55:AC",
37+
bluetooth_proxy_feature_flags=BluetoothProxyFeature.PASSIVE_SCAN
38+
| BluetoothProxyFeature.ACTIVE_CONNECTIONS
39+
| BluetoothProxyFeature.REMOTE_CACHING
40+
| BluetoothProxyFeature.PAIRING
41+
| BluetoothProxyFeature.CACHE_CLEARING
42+
| BluetoothProxyFeature.RAW_ADVERTISEMENTS
43+
| BluetoothProxyFeature.FEATURE_STATE_AND_MODE,
3544
esphome_version="1.0.0",
3645
)
3746

tests/test_connect.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import pytest
2+
from aioesphomeapi import APIClient
3+
4+
from bleak_esphome.connect import connect_scanner
5+
6+
7+
@pytest.mark.asyncio
8+
async def test_connect(mock_client: APIClient) -> None:
9+
"""Test the connect_scanner function."""
10+
device_info = await mock_client.device_info()
11+
scanner = connect_scanner(mock_client, device_info, available=True)
12+
assert scanner is not None
13+
assert (
14+
scanner.device_info.bluetooth_mac_address == device_info.bluetooth_mac_address
15+
)
16+
assert scanner.client is mock_client

0 commit comments

Comments
 (0)