Refactor Bluetooth scanners to avoid the need to pass a callback (#105607)

This commit is contained in:
J. Nick Koston 2023-12-12 22:17:48 -10:00 committed by GitHub
parent aaccf19013
commit 5dbd0dede1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 30 additions and 68 deletions

View file

@ -106,6 +106,7 @@ __all__ = [
"async_scanner_by_source", "async_scanner_by_source",
"async_scanner_count", "async_scanner_count",
"async_scanner_devices_by_address", "async_scanner_devices_by_address",
"async_get_advertisement_callback",
"BaseHaScanner", "BaseHaScanner",
"HomeAssistantRemoteScanner", "HomeAssistantRemoteScanner",
"BluetoothCallbackMatcher", "BluetoothCallbackMatcher",
@ -287,9 +288,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
passive = entry.options.get(CONF_PASSIVE) passive = entry.options.get(CONF_PASSIVE)
mode = BluetoothScanningMode.PASSIVE if passive else BluetoothScanningMode.ACTIVE mode = BluetoothScanningMode.PASSIVE if passive else BluetoothScanningMode.ACTIVE
new_info_callback = async_get_advertisement_callback(hass)
manager: HomeAssistantBluetoothManager = hass.data[DATA_MANAGER] manager: HomeAssistantBluetoothManager = hass.data[DATA_MANAGER]
scanner = HaScanner(mode, adapter, address, new_info_callback) scanner = HaScanner(mode, adapter, address)
try: try:
scanner.async_setup() scanner.async_setup()
except RuntimeError as err: except RuntimeError as err:

View file

@ -20,6 +20,6 @@
"bluetooth-auto-recovery==1.2.3", "bluetooth-auto-recovery==1.2.3",
"bluetooth-data-tools==1.17.0", "bluetooth-data-tools==1.17.0",
"dbus-fast==2.21.0", "dbus-fast==2.21.0",
"habluetooth==0.11.1" "habluetooth==1.0.0"
] ]
} }

View file

@ -11,7 +11,6 @@ from aioesphomeapi import APIClient, BluetoothProxyFeature
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
HaBluetoothConnector, HaBluetoothConnector,
async_get_advertisement_callback,
async_register_scanner, async_register_scanner,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -63,7 +62,6 @@ async def async_connect_scanner(
"""Connect scanner.""" """Connect scanner."""
assert entry.unique_id is not None assert entry.unique_id is not None
source = str(entry.unique_id) source = str(entry.unique_id)
new_info_callback = async_get_advertisement_callback(hass)
device_info = entry_data.device_info device_info = entry_data.device_info
assert device_info is not None assert device_info is not None
feature_flags = device_info.bluetooth_proxy_feature_flags_compat( feature_flags = device_info.bluetooth_proxy_feature_flags_compat(
@ -98,9 +96,7 @@ async def async_connect_scanner(
partial(_async_can_connect, entry_data, bluetooth_device, source) partial(_async_can_connect, entry_data, bluetooth_device, source)
), ),
) )
scanner = ESPHomeScanner( scanner = ESPHomeScanner(source, entry.title, connector, connectable)
source, entry.title, new_info_callback, connector, connectable
)
client_data.scanner = scanner client_data.scanner = scanner
coros: list[Coroutine[Any, Any, CALLBACK_TYPE]] = [] coros: list[Coroutine[Any, Any, CALLBACK_TYPE]] = []
# These calls all return a callback that can be used to unsubscribe # These calls all return a callback that can be used to unsubscribe

View file

@ -1,17 +1,13 @@
"""Bluetooth support for Ruuvi Gateway.""" """Bluetooth support for Ruuvi Gateway."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
import logging import logging
import time import time
from home_assistant_bluetooth import BluetoothServiceInfoBleak
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
MONOTONIC_TIME, MONOTONIC_TIME,
BaseHaRemoteScanner, BaseHaRemoteScanner,
async_get_advertisement_callback,
async_register_scanner, async_register_scanner,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -29,7 +25,6 @@ class RuuviGatewayScanner(BaseHaRemoteScanner):
self, self,
scanner_id: str, scanner_id: str,
name: str, name: str,
new_info_callback: Callable[[BluetoothServiceInfoBleak], None],
*, *,
coordinator: RuuviGatewayUpdateCoordinator, coordinator: RuuviGatewayUpdateCoordinator,
) -> None: ) -> None:
@ -37,7 +32,6 @@ class RuuviGatewayScanner(BaseHaRemoteScanner):
super().__init__( super().__init__(
scanner_id, scanner_id,
name, name,
new_info_callback,
connector=None, connector=None,
connectable=False, connectable=False,
) )
@ -87,7 +81,6 @@ def async_connect_scanner(
scanner = RuuviGatewayScanner( scanner = RuuviGatewayScanner(
scanner_id=source, scanner_id=source,
name=entry.title, name=entry.title,
new_info_callback=async_get_advertisement_callback(hass),
coordinator=coordinator, coordinator=coordinator,
) )
unload_callbacks = [ unload_callbacks = [

View file

@ -14,7 +14,6 @@ from aioshelly.ble.const import (
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
HaBluetoothConnector, HaBluetoothConnector,
async_get_advertisement_callback,
async_register_scanner, async_register_scanner,
) )
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
@ -36,14 +35,13 @@ async def async_connect_scanner(
device = coordinator.device device = coordinator.device
entry = coordinator.entry entry = coordinator.entry
source = format_mac(coordinator.mac).upper() source = format_mac(coordinator.mac).upper()
new_info_callback = async_get_advertisement_callback(hass)
connector = HaBluetoothConnector( connector = HaBluetoothConnector(
# no active connections to shelly yet # no active connections to shelly yet
client=None, # type: ignore[arg-type] client=None, # type: ignore[arg-type]
source=source, source=source,
can_connect=lambda: False, can_connect=lambda: False,
) )
scanner = ShellyBLEScanner(source, entry.title, new_info_callback, connector, False) scanner = ShellyBLEScanner(source, entry.title, connector, False)
unload_callbacks = [ unload_callbacks = [
async_register_scanner(hass, scanner, False), async_register_scanner(hass, scanner, False),
scanner.async_setup(), scanner.async_setup(),

View file

@ -23,7 +23,7 @@ dbus-fast==2.21.0
fnv-hash-fast==0.5.0 fnv-hash-fast==0.5.0
ha-av==10.1.1 ha-av==10.1.1
ha-ffmpeg==3.1.0 ha-ffmpeg==3.1.0
habluetooth==0.11.1 habluetooth==1.0.0
hass-nabucasa==0.74.0 hass-nabucasa==0.74.0
hassil==1.5.1 hassil==1.5.1
home-assistant-bluetooth==1.11.0 home-assistant-bluetooth==1.11.0

View file

@ -984,7 +984,7 @@ ha-philipsjs==3.1.1
habitipy==0.2.0 habitipy==0.2.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
habluetooth==0.11.1 habluetooth==1.0.0
# homeassistant.components.cloud # homeassistant.components.cloud
hass-nabucasa==0.74.0 hass-nabucasa==0.74.0

View file

@ -783,7 +783,7 @@ ha-philipsjs==3.1.1
habitipy==0.2.0 habitipy==0.2.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
habluetooth==0.11.1 habluetooth==1.0.0
# homeassistant.components.cloud # homeassistant.components.cloud
hass-nabucasa==0.74.0 hass-nabucasa==0.74.0

View file

@ -40,6 +40,14 @@ async def test_monotonic_time() -> None:
assert MONOTONIC_TIME() == pytest.approx(time.monotonic(), abs=0.1) assert MONOTONIC_TIME() == pytest.approx(time.monotonic(), abs=0.1)
async def test_async_get_advertisement_callback(
hass: HomeAssistant, enable_bluetooth: None
) -> None:
"""Test getting advertisement callback."""
callback = bluetooth.async_get_advertisement_callback(hass)
assert callback is not None
async def test_async_scanner_devices_by_address_connectable( async def test_async_scanner_devices_by_address_connectable(
hass: HomeAssistant, enable_bluetooth: None hass: HomeAssistant, enable_bluetooth: None
) -> None: ) -> None:
@ -63,13 +71,10 @@ async def test_async_scanner_devices_by_address_connectable(
MONOTONIC_TIME(), MONOTONIC_TIME(),
) )
new_info_callback = manager.scanner_adv_received
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
scanner = FakeInjectableScanner( scanner = FakeInjectableScanner("esp32", "esp32", connector, False)
"esp32", "esp32", new_info_callback, connector, False
)
unsetup = scanner.async_setup() unsetup = scanner.async_setup()
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)
switchbot_device = generate_ble_device( switchbot_device = generate_ble_device(

View file

@ -111,11 +111,10 @@ async def test_remote_scanner(
rssi=-100, rssi=-100,
) )
new_info_callback = manager.scanner_adv_received
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
scanner = FakeScanner("esp32", "esp32", new_info_callback, connector, True) scanner = FakeScanner("esp32", "esp32", connector, True)
unsetup = scanner.async_setup() unsetup = scanner.async_setup()
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)
@ -178,11 +177,10 @@ async def test_remote_scanner_expires_connectable(
rssi=-100, rssi=-100,
) )
new_info_callback = manager.scanner_adv_received
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
scanner = FakeScanner("esp32", "esp32", new_info_callback, connector, True) scanner = FakeScanner("esp32", "esp32", connector, True)
unsetup = scanner.async_setup() unsetup = scanner.async_setup()
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)
@ -233,11 +231,10 @@ async def test_remote_scanner_expires_non_connectable(
rssi=-100, rssi=-100,
) )
new_info_callback = manager.scanner_adv_received
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
scanner = FakeScanner("esp32", "esp32", new_info_callback, connector, False) scanner = FakeScanner("esp32", "esp32", connector, False)
unsetup = scanner.async_setup() unsetup = scanner.async_setup()
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)
@ -308,11 +305,10 @@ async def test_base_scanner_connecting_behavior(
rssi=-100, rssi=-100,
) )
new_info_callback = manager.scanner_adv_received
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
scanner = FakeScanner("esp32", "esp32", new_info_callback, connector, False) scanner = FakeScanner("esp32", "esp32", connector, False)
unsetup = scanner.async_setup() unsetup = scanner.async_setup()
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)
@ -366,7 +362,6 @@ async def test_restore_history_remote_adapter(
scanner = BaseHaRemoteScanner( scanner = BaseHaRemoteScanner(
"atom-bluetooth-proxy-ceaac4", "atom-bluetooth-proxy-ceaac4",
"atom-bluetooth-proxy-ceaac4", "atom-bluetooth-proxy-ceaac4",
lambda adv: None,
connector, connector,
True, True,
) )
@ -381,7 +376,6 @@ async def test_restore_history_remote_adapter(
scanner = BaseHaRemoteScanner( scanner = BaseHaRemoteScanner(
"atom-bluetooth-proxy-ceaac4", "atom-bluetooth-proxy-ceaac4",
"atom-bluetooth-proxy-ceaac4", "atom-bluetooth-proxy-ceaac4",
lambda adv: None,
connector, connector,
True, True,
) )
@ -413,11 +407,10 @@ async def test_device_with_ten_minute_advertising_interval(
rssi=-100, rssi=-100,
) )
new_info_callback = manager.scanner_adv_received
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
scanner = FakeScanner("esp32", "esp32", new_info_callback, connector, False) scanner = FakeScanner("esp32", "esp32", connector, False)
unsetup = scanner.async_setup() unsetup = scanner.async_setup()
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)
@ -505,11 +498,10 @@ async def test_scanner_stops_responding(
"""Test we mark a scanner are not scanning when it stops responding.""" """Test we mark a scanner are not scanning when it stops responding."""
manager = _get_manager() manager = _get_manager()
new_info_callback = manager.scanner_adv_received
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
scanner = FakeScanner("esp32", "esp32", new_info_callback, connector, False) scanner = FakeScanner("esp32", "esp32", connector, False)
unsetup = scanner.async_setup() unsetup = scanner.async_setup()
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)

View file

@ -454,11 +454,10 @@ async def test_diagnostics_remote_adapter(
assert await hass.config_entries.async_setup(entry1.entry_id) assert await hass.config_entries.async_setup(entry1.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
new_info_callback = manager.scanner_adv_received
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
scanner = FakeScanner("esp32", "esp32", new_info_callback, connector, False) scanner = FakeScanner("esp32", "esp32", connector, False)
unsetup = scanner.async_setup() unsetup = scanner.async_setup()
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)

View file

@ -20,7 +20,6 @@ from homeassistant.components.bluetooth import (
BluetoothServiceInfoBleak, BluetoothServiceInfoBleak,
HaBluetoothConnector, HaBluetoothConnector,
async_ble_device_from_address, async_ble_device_from_address,
async_get_advertisement_callback,
async_get_fallback_availability_interval, async_get_fallback_availability_interval,
async_get_learned_advertising_interval, async_get_learned_advertising_interval,
async_scanner_count, async_scanner_count,
@ -720,14 +719,12 @@ async def test_goes_unavailable_connectable_only_and_recovers(
MONOTONIC_TIME(), MONOTONIC_TIME(),
) )
new_info_callback = async_get_advertisement_callback(hass)
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
connectable_scanner = FakeScanner( connectable_scanner = FakeScanner(
"connectable", "connectable",
"connectable", "connectable",
new_info_callback,
connector, connector,
True, True,
) )
@ -750,7 +747,6 @@ async def test_goes_unavailable_connectable_only_and_recovers(
not_connectable_scanner = FakeScanner( not_connectable_scanner = FakeScanner(
"not_connectable", "not_connectable",
"not_connectable", "not_connectable",
new_info_callback,
connector, connector,
False, False,
) )
@ -800,7 +796,6 @@ async def test_goes_unavailable_connectable_only_and_recovers(
connectable_scanner_2 = FakeScanner( connectable_scanner_2 = FakeScanner(
"connectable", "connectable",
"connectable", "connectable",
new_info_callback,
connector, connector,
True, True,
) )
@ -896,14 +891,12 @@ async def test_goes_unavailable_dismisses_discovery_and_makes_discoverable(
self._discovered_device_timestamps.clear() self._discovered_device_timestamps.clear()
self._previous_service_info.clear() self._previous_service_info.clear()
new_info_callback = async_get_advertisement_callback(hass)
connector = ( connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
) )
non_connectable_scanner = FakeScanner( non_connectable_scanner = FakeScanner(
"connectable", "connectable",
"connectable", "connectable",
new_info_callback,
connector, connector,
False, False,
) )

View file

@ -184,7 +184,6 @@ async def test_wrapped_bleak_client_set_disconnected_callback_after_connected(
scanner = FakeScanner( scanner = FakeScanner(
"esp32_has_connection_slot", "esp32_has_connection_slot",
"esp32_has_connection_slot", "esp32_has_connection_slot",
lambda info: None,
connector, connector,
True, True,
) )
@ -291,7 +290,7 @@ async def test_ble_device_with_proxy_client_out_of_connections(
return None return None
connector = HaBluetoothConnector(MockBleakClient, "esp32", lambda: False) connector = HaBluetoothConnector(MockBleakClient, "esp32", lambda: False)
scanner = FakeScanner("esp32", "esp32", lambda info: None, connector, True) scanner = FakeScanner("esp32", "esp32", connector, True)
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)
inject_advertisement_with_source( inject_advertisement_with_source(
hass, switchbot_proxy_device_no_connection_slot, switchbot_adv, "esp32" hass, switchbot_proxy_device_no_connection_slot, switchbot_adv, "esp32"
@ -356,7 +355,7 @@ async def test_ble_device_with_proxy_clear_cache(
return None return None
connector = HaBluetoothConnector(MockBleakClient, "esp32", lambda: True) connector = HaBluetoothConnector(MockBleakClient, "esp32", lambda: True)
scanner = FakeScanner("esp32", "esp32", lambda info: None, connector, True) scanner = FakeScanner("esp32", "esp32", connector, True)
cancel = manager.async_register_scanner(scanner, True) cancel = manager.async_register_scanner(scanner, True)
inject_advertisement_with_source( inject_advertisement_with_source(
hass, switchbot_proxy_device_with_connection_slot, switchbot_adv, "esp32" hass, switchbot_proxy_device_with_connection_slot, switchbot_adv, "esp32"
@ -464,7 +463,6 @@ async def test_ble_device_with_proxy_client_out_of_connections_uses_best_availab
scanner = FakeScanner( scanner = FakeScanner(
"esp32_has_connection_slot", "esp32_has_connection_slot",
"esp32_has_connection_slot", "esp32_has_connection_slot",
lambda info: None,
connector, connector,
True, True,
) )
@ -577,7 +575,6 @@ async def test_ble_device_with_proxy_client_out_of_connections_uses_best_availab
scanner = FakeScanner( scanner = FakeScanner(
"esp32_has_connection_slot", "esp32_has_connection_slot",
"esp32_has_connection_slot", "esp32_has_connection_slot",
lambda info: None,
connector, connector,
True, True,
) )

View file

@ -1,7 +1,6 @@
"""Tests for the Bluetooth integration.""" """Tests for the Bluetooth integration."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
from contextlib import contextmanager from contextlib import contextmanager
from unittest.mock import patch from unittest.mock import patch
@ -18,10 +17,8 @@ import pytest
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
MONOTONIC_TIME, MONOTONIC_TIME,
BaseHaRemoteScanner, BaseHaRemoteScanner,
BluetoothServiceInfoBleak,
HaBluetoothConnector, HaBluetoothConnector,
HomeAssistantBluetoothManager, HomeAssistantBluetoothManager,
async_get_advertisement_callback,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -43,12 +40,11 @@ class FakeScanner(BaseHaRemoteScanner):
self, self,
scanner_id: str, scanner_id: str,
name: str, name: str,
new_info_callback: Callable[[BluetoothServiceInfoBleak], None],
connector: None, connector: None,
connectable: bool, connectable: bool,
) -> None: ) -> None:
"""Initialize the scanner.""" """Initialize the scanner."""
super().__init__(scanner_id, name, new_info_callback, connector, connectable) super().__init__(scanner_id, name, connector, connectable)
self._details: dict[str, str | HaBluetoothConnector] = {} self._details: dict[str, str | HaBluetoothConnector] = {}
def __repr__(self) -> str: def __repr__(self) -> str:
@ -182,13 +178,8 @@ def _generate_scanners_with_fake_devices(hass):
) )
hci1_device_advs[device.address] = (device, adv_data) hci1_device_advs[device.address] = (device, adv_data)
new_info_callback = async_get_advertisement_callback(hass) scanner_hci0 = FakeScanner("00:00:00:00:00:01", "hci0", None, True)
scanner_hci0 = FakeScanner( scanner_hci1 = FakeScanner("00:00:00:00:00:02", "hci1", None, True)
"00:00:00:00:00:01", "hci0", new_info_callback, None, True
)
scanner_hci1 = FakeScanner(
"00:00:00:00:00:02", "hci1", new_info_callback, None, True
)
for device, adv_data in hci0_device_advs.values(): for device, adv_data in hci0_device_advs.values():
scanner_hci0.inject_advertisement(device, adv_data) scanner_hci0.inject_advertisement(device, adv_data)

View file

@ -43,9 +43,7 @@ async def client_data_fixture(
), ),
api_version=APIVersion(1, 9), api_version=APIVersion(1, 9),
title=ESP_NAME, title=ESP_NAME,
scanner=ESPHomeScanner( scanner=ESPHomeScanner(ESP_MAC_ADDRESS, ESP_NAME, connector, True),
ESP_MAC_ADDRESS, ESP_NAME, lambda info: None, connector, True
),
) )