Refactor bluetooth scanners for better seperation of concerns (#104909)

This commit is contained in:
J. Nick Koston 2023-12-02 13:20:06 -10:00 committed by GitHub
parent 7a9c3819e0
commit dd9c22672a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 195 additions and 111 deletions

View File

@ -59,7 +59,11 @@ from .api import (
async_set_fallback_availability_interval,
async_track_unavailable,
)
from .base_scanner import BaseHaRemoteScanner, BaseHaScanner, BluetoothScannerDevice
from .base_scanner import (
BaseHaScanner,
BluetoothScannerDevice,
HomeAssistantRemoteScanner,
)
from .const import (
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
CONF_ADAPTER,
@ -103,7 +107,7 @@ __all__ = [
"async_scanner_count",
"async_scanner_devices_by_address",
"BaseHaScanner",
"BaseHaRemoteScanner",
"HomeAssistantRemoteScanner",
"BluetoothCallbackMatcher",
"BluetoothChange",
"BluetoothServiceInfo",

View File

@ -1,14 +1,13 @@
"""Base classes for HA Bluetooth scanners for bluetooth."""
from __future__ import annotations
from abc import ABC, abstractmethod
from abc import abstractmethod
import asyncio
from collections.abc import Callable, Generator
from contextlib import contextmanager
from dataclasses import dataclass
import datetime
from datetime import timedelta
import logging
from typing import Any, Final
from typing import Any, Final, final
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
@ -24,8 +23,6 @@ from homeassistant.core import (
HomeAssistant,
callback as hass_callback,
)
from homeassistant.helpers.event import async_track_time_interval
import homeassistant.util.dt as dt_util
from . import models
from .const import (
@ -35,6 +32,7 @@ from .const import (
)
from .models import HaBluetoothConnector
SCANNER_WATCHDOG_INTERVAL_SECONDS: Final = SCANNER_WATCHDOG_INTERVAL.total_seconds()
MONOTONIC_TIME: Final = monotonic_time_coarse
_LOGGER = logging.getLogger(__name__)
@ -48,11 +46,10 @@ class BluetoothScannerDevice:
advertisement: AdvertisementData
class BaseHaScanner(ABC):
"""Base class for Ha Scanners."""
class BaseHaScanner:
"""Base class for high availability BLE scanners."""
__slots__ = (
"hass",
"adapter",
"connectable",
"source",
@ -63,17 +60,16 @@ class BaseHaScanner(ABC):
"_last_detection",
"_start_time",
"_cancel_watchdog",
"_loop",
)
def __init__(
self,
hass: HomeAssistant,
source: str,
adapter: str,
connector: HaBluetoothConnector | None = None,
) -> None:
"""Initialize the scanner."""
self.hass = hass
self.connectable = False
self.source = source
self.connector = connector
@ -83,13 +79,20 @@ class BaseHaScanner(ABC):
self.scanning = True
self._last_detection = 0.0
self._start_time = 0.0
self._cancel_watchdog: CALLBACK_TYPE | None = None
self._cancel_watchdog: asyncio.TimerHandle | None = None
self._loop: asyncio.AbstractEventLoop | None = None
@hass_callback
def async_setup(self) -> CALLBACK_TYPE:
"""Set up the scanner."""
self._loop = asyncio.get_running_loop()
return self._unsetup
@hass_callback
def _async_stop_scanner_watchdog(self) -> None:
"""Stop the scanner watchdog."""
if self._cancel_watchdog:
self._cancel_watchdog()
self._cancel_watchdog.cancel()
self._cancel_watchdog = None
@hass_callback
@ -97,12 +100,22 @@ class BaseHaScanner(ABC):
"""If something has restarted or updated, we need to restart the scanner."""
self._start_time = self._last_detection = MONOTONIC_TIME()
if not self._cancel_watchdog:
self._cancel_watchdog = async_track_time_interval(
self.hass,
self._async_scanner_watchdog,
SCANNER_WATCHDOG_INTERVAL,
name=f"{self.name} Bluetooth scanner watchdog",
)
self._schedule_watchdog()
def _schedule_watchdog(self) -> None:
"""Schedule the watchdog."""
loop = self._loop
assert loop is not None
self._cancel_watchdog = loop.call_at(
loop.time() + SCANNER_WATCHDOG_INTERVAL_SECONDS,
self._async_call_scanner_watchdog,
)
@final
def _async_call_scanner_watchdog(self) -> None:
"""Call the scanner watchdog and schedule the next one."""
self._async_scanner_watchdog()
self._schedule_watchdog()
@hass_callback
def _async_watchdog_triggered(self) -> bool:
@ -116,7 +129,7 @@ class BaseHaScanner(ABC):
return time_since_last_detection > SCANNER_WATCHDOG_TIMEOUT
@hass_callback
def _async_scanner_watchdog(self, now: datetime.datetime) -> None:
def _async_scanner_watchdog(self) -> None:
"""Check if the scanner is running.
Override this method if you need to do something else when the watchdog
@ -135,6 +148,10 @@ class BaseHaScanner(ABC):
return
self.scanning = not self._connecting
@hass_callback
def _unsetup(self) -> None:
"""Unset up the scanner."""
@contextmanager
def connecting(self) -> Generator[None, None, None]:
"""Context manager to track connecting state."""
@ -183,7 +200,7 @@ class BaseHaScanner(ABC):
class BaseHaRemoteScanner(BaseHaScanner):
"""Base class for a Home Assistant remote BLE scanner."""
"""Base class for a high availability remote BLE scanner."""
__slots__ = (
"_new_info_callback",
@ -191,12 +208,11 @@ class BaseHaRemoteScanner(BaseHaScanner):
"_discovered_device_timestamps",
"_details",
"_expire_seconds",
"_storage",
"_cancel_track",
)
def __init__(
self,
hass: HomeAssistant,
scanner_id: str,
name: str,
new_info_callback: Callable[[BluetoothServiceInfoBleak], None],
@ -204,7 +220,7 @@ class BaseHaRemoteScanner(BaseHaScanner):
connectable: bool,
) -> None:
"""Initialize the scanner."""
super().__init__(hass, scanner_id, name, connector)
super().__init__(scanner_id, name, connector)
self._new_info_callback = new_info_callback
self._discovered_device_advertisement_datas: dict[
str, tuple[BLEDevice, AdvertisementData]
@ -215,55 +231,37 @@ class BaseHaRemoteScanner(BaseHaScanner):
# Scanners only care about connectable devices. The manager
# will handle taking care of availability for non-connectable devices
self._expire_seconds = CONNECTABLE_FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS
assert models.MANAGER is not None
self._storage = models.MANAGER.storage
self._cancel_track: asyncio.TimerHandle | None = None
def _cancel_expire_devices(self) -> None:
"""Cancel the expiration of old devices."""
if self._cancel_track:
self._cancel_track.cancel()
self._cancel_track = None
@hass_callback
def _unsetup(self) -> None:
"""Unset up the scanner."""
self._async_stop_scanner_watchdog()
self._cancel_expire_devices()
@hass_callback
def async_setup(self) -> CALLBACK_TYPE:
"""Set up the scanner."""
if history := self._storage.async_get_advertisement_history(self.source):
self._discovered_device_advertisement_datas = (
history.discovered_device_advertisement_datas
)
self._discovered_device_timestamps = history.discovered_device_timestamps
# Expire anything that is too old
self._async_expire_devices(dt_util.utcnow())
cancel_track = async_track_time_interval(
self.hass,
self._async_expire_devices,
timedelta(seconds=30),
name=f"{self.name} Bluetooth scanner device expire",
)
cancel_stop = self.hass.bus.async_listen(
EVENT_HOMEASSISTANT_STOP, self._async_save_history
)
super().async_setup()
self._schedule_expire_devices()
self._async_setup_scanner_watchdog()
return self._unsetup
@hass_callback
def _cancel() -> None:
self._async_save_history()
self._async_stop_scanner_watchdog()
cancel_track()
cancel_stop()
return _cancel
def _schedule_expire_devices(self) -> None:
"""Schedule the expiration of old devices."""
loop = self._loop
assert loop is not None
self._cancel_expire_devices()
self._cancel_track = loop.call_at(loop.time() + 30, self._async_expire_devices)
@hass_callback
def _async_save_history(self, event: Event | None = None) -> None:
"""Save the history."""
self._storage.async_set_advertisement_history(
self.source,
DiscoveredDeviceAdvertisementData(
self.connectable,
self._expire_seconds,
self._discovered_device_advertisement_datas,
self._discovered_device_timestamps,
),
)
@hass_callback
def _async_expire_devices(self, _datetime: datetime.datetime) -> None:
def _async_expire_devices(self) -> None:
"""Expire old devices."""
now = MONOTONIC_TIME()
expired = [
@ -274,6 +272,7 @@ class BaseHaRemoteScanner(BaseHaScanner):
for address in expired:
del self._discovered_device_advertisement_datas[address]
del self._discovered_device_timestamps[address]
self._schedule_expire_devices()
@property
def discovered_devices(self) -> list[BLEDevice]:
@ -395,9 +394,6 @@ class BaseHaRemoteScanner(BaseHaScanner):
"""Return diagnostic information about the scanner."""
now = MONOTONIC_TIME()
return await super().async_diagnostics() | {
"storage": self._storage.async_get_advertisement_history_as_dict(
self.source
),
"connectable": self.connectable,
"discovered_device_timestamps": self._discovered_device_timestamps,
"time_since_last_device_detection": {
@ -405,3 +401,79 @@ class BaseHaRemoteScanner(BaseHaScanner):
for address, timestamp in self._discovered_device_timestamps.items()
},
}
class HomeAssistantRemoteScanner(BaseHaRemoteScanner):
"""Home Assistant remote BLE scanner.
This is the only object that should know about
the hass object.
"""
__slots__ = (
"hass",
"_storage",
"_cancel_stop",
)
def __init__(
self,
hass: HomeAssistant,
scanner_id: str,
name: str,
new_info_callback: Callable[[BluetoothServiceInfoBleak], None],
connector: HaBluetoothConnector | None,
connectable: bool,
) -> None:
"""Initialize the scanner."""
self.hass = hass
assert models.MANAGER is not None
self._storage = models.MANAGER.storage
self._cancel_stop: CALLBACK_TYPE | None = None
super().__init__(scanner_id, name, new_info_callback, connector, connectable)
@hass_callback
def async_setup(self) -> CALLBACK_TYPE:
"""Set up the scanner."""
super().async_setup()
if history := self._storage.async_get_advertisement_history(self.source):
self._discovered_device_advertisement_datas = (
history.discovered_device_advertisement_datas
)
self._discovered_device_timestamps = history.discovered_device_timestamps
# Expire anything that is too old
self._async_expire_devices()
self._cancel_stop = self.hass.bus.async_listen(
EVENT_HOMEASSISTANT_STOP, self._async_save_history
)
return self._unsetup
@hass_callback
def _unsetup(self) -> None:
super()._unsetup()
self._async_save_history()
if self._cancel_stop:
self._cancel_stop()
self._cancel_stop = None
@hass_callback
def _async_save_history(self, event: Event | None = None) -> None:
"""Save the history."""
self._storage.async_set_advertisement_history(
self.source,
DiscoveredDeviceAdvertisementData(
self.connectable,
self._expire_seconds,
self._discovered_device_advertisement_datas,
self._discovered_device_timestamps,
),
)
async def async_diagnostics(self) -> dict[str, Any]:
"""Return diagnostic information about the scanner."""
diag = await super().async_diagnostics()
diag["storage"] = self._storage.async_get_advertisement_history_as_dict(
self.source
)
return diag

View File

@ -3,7 +3,6 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable
from datetime import datetime
import logging
import platform
from typing import Any
@ -19,7 +18,7 @@ from bleak_retry_connector import restore_discoveries
from bluetooth_adapters import DEFAULT_ADDRESS
from dbus_fast import InvalidMessageError
from homeassistant.core import HomeAssistant, callback as hass_callback
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.package import is_docker_env
@ -133,12 +132,13 @@ class HaScanner(BaseHaScanner):
"""Init bluetooth discovery."""
self.mac_address = address
source = address if address != DEFAULT_ADDRESS else adapter or SOURCE_LOCAL
super().__init__(hass, source, adapter)
super().__init__(source, adapter)
self.connectable = True
self.mode = mode
self._start_stop_lock = asyncio.Lock()
self._new_info_callback = new_info_callback
self.scanning = False
self.hass = hass
@property
def discovered_devices(self) -> list[BLEDevice]:
@ -153,11 +153,13 @@ class HaScanner(BaseHaScanner):
return self.scanner.discovered_devices_and_advertisement_data
@hass_callback
def async_setup(self) -> None:
def async_setup(self) -> CALLBACK_TYPE:
"""Set up the scanner."""
super().async_setup()
self.scanner = create_bleak_scanner(
self._async_detection_callback, self.mode, self.adapter
)
return self._unsetup
async def async_diagnostics(self) -> dict[str, Any]:
"""Return diagnostic information about the scanner."""
@ -314,7 +316,7 @@ class HaScanner(BaseHaScanner):
await restore_discoveries(self.scanner, self.adapter)
@hass_callback
def _async_scanner_watchdog(self, now: datetime) -> None:
def _async_scanner_watchdog(self) -> None:
"""Check if the scanner is running."""
if not self._async_watchdog_triggered():
return

View File

@ -7,11 +7,14 @@ from bluetooth_data_tools import (
parse_advertisement_data_tuple,
)
from homeassistant.components.bluetooth import MONOTONIC_TIME, BaseHaRemoteScanner
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
HomeAssistantRemoteScanner,
)
from homeassistant.core import callback
class ESPHomeScanner(BaseHaRemoteScanner):
class ESPHomeScanner(HomeAssistantRemoteScanner):
"""Scanner for esphome."""
__slots__ = ()

View File

@ -10,7 +10,7 @@ from home_assistant_bluetooth import BluetoothServiceInfoBleak
from homeassistant.components.bluetooth import (
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
MONOTONIC_TIME,
BaseHaRemoteScanner,
HomeAssistantRemoteScanner,
async_get_advertisement_callback,
async_register_scanner,
)
@ -22,7 +22,7 @@ from .coordinator import RuuviGatewayUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
class RuuviGatewayScanner(BaseHaRemoteScanner):
class RuuviGatewayScanner(HomeAssistantRemoteScanner):
"""Scanner for Ruuvi Gateway."""
def __init__(

View File

@ -6,13 +6,16 @@ from typing import Any
from aioshelly.ble import parse_ble_scan_result_event
from aioshelly.ble.const import BLE_SCAN_RESULT_EVENT, BLE_SCAN_RESULT_VERSION
from homeassistant.components.bluetooth import MONOTONIC_TIME, BaseHaRemoteScanner
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
HomeAssistantRemoteScanner,
)
from homeassistant.core import callback
from ..const import LOGGER
class ShellyBLEScanner(BaseHaRemoteScanner):
class ShellyBLEScanner(HomeAssistantRemoteScanner):
"""Scanner for shelly."""
@callback

View File

@ -7,9 +7,9 @@ import pytest
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
BaseHaScanner,
HaBluetoothConnector,
HomeAssistantRemoteScanner,
async_scanner_by_source,
async_scanner_devices_by_address,
)
@ -27,7 +27,7 @@ from . import (
async def test_scanner_by_source(hass: HomeAssistant, enable_bluetooth: None) -> None:
"""Test we can get a scanner by source."""
hci2_scanner = FakeScanner(hass, "hci2", "hci2")
hci2_scanner = FakeScanner("hci2", "hci2")
cancel_hci2 = bluetooth.async_register_scanner(hass, hci2_scanner, True)
assert async_scanner_by_source(hass, "hci2") is hci2_scanner
@ -46,7 +46,7 @@ async def test_async_scanner_devices_by_address_connectable(
"""Test getting scanner devices by address with connectable devices."""
manager = _get_manager()
class FakeInjectableScanner(BaseHaRemoteScanner):
class FakeInjectableScanner(HomeAssistantRemoteScanner):
def inject_advertisement(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
@ -135,7 +135,7 @@ async def test_async_scanner_devices_by_address_non_connectable(
connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
)
scanner = FakeStaticScanner(hass, "esp32", "esp32", connector)
scanner = FakeStaticScanner("esp32", "esp32", connector)
cancel = manager.async_register_scanner(scanner, False)
assert scanner.discovered_devices_and_advertisement_data == {

View File

@ -13,8 +13,8 @@ import pytest
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
HaBluetoothConnector,
HomeAssistantRemoteScanner,
storage,
)
from homeassistant.components.bluetooth.advertisement_tracker import (
@ -89,7 +89,7 @@ async def test_remote_scanner(
rssi=-100,
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
def inject_advertisement(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
@ -173,7 +173,7 @@ async def test_remote_scanner_expires_connectable(
rssi=-100,
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
def inject_advertisement(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
@ -248,7 +248,7 @@ async def test_remote_scanner_expires_non_connectable(
rssi=-100,
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
def inject_advertisement(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
@ -346,7 +346,7 @@ async def test_base_scanner_connecting_behavior(
rssi=-100,
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
def inject_advertisement(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
@ -418,7 +418,7 @@ async def test_restore_history_remote_adapter(
connector = (
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
)
scanner = BaseHaRemoteScanner(
scanner = HomeAssistantRemoteScanner(
hass,
"atom-bluetooth-proxy-ceaac4",
"atom-bluetooth-proxy-ceaac4",
@ -434,7 +434,7 @@ async def test_restore_history_remote_adapter(
cancel()
unsetup()
scanner = BaseHaRemoteScanner(
scanner = HomeAssistantRemoteScanner(
hass,
"atom-bluetooth-proxy-ceaac4",
"atom-bluetooth-proxy-ceaac4",
@ -470,7 +470,7 @@ async def test_device_with_ten_minute_advertising_interval(
rssi=-100,
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
def inject_advertisement(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
@ -592,7 +592,7 @@ async def test_scanner_stops_responding(
"""Test we mark a scanner are not scanning when it stops responding."""
manager = _get_manager()
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
"""A fake remote scanner."""
def inject_advertisement(

View File

@ -7,8 +7,8 @@ from bluetooth_adapters import DEFAULT_ADDRESS
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
HaBluetoothConnector,
HomeAssistantRemoteScanner,
)
from homeassistant.core import HomeAssistant
@ -442,7 +442,7 @@ async def test_diagnostics_remote_adapter(
local_name="wohand", service_uuids=[], manufacturer_data={1: b"\x01"}
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
def inject_advertisement(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:

View File

@ -12,12 +12,12 @@ import pytest
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
BluetoothChange,
BluetoothScanningMode,
BluetoothServiceInfo,
BluetoothServiceInfoBleak,
HaBluetoothConnector,
HomeAssistantRemoteScanner,
async_ble_device_from_address,
async_get_advertisement_callback,
async_get_fallback_availability_interval,
@ -56,7 +56,7 @@ from tests.common import async_fire_time_changed, load_fixture
@pytest.fixture
def register_hci0_scanner(hass: HomeAssistant) -> Generator[None, None, None]:
"""Register an hci0 scanner."""
hci0_scanner = FakeScanner(hass, "hci0", "hci0")
hci0_scanner = FakeScanner("hci0", "hci0")
cancel = bluetooth.async_register_scanner(hass, hci0_scanner, True)
yield
cancel()
@ -65,7 +65,7 @@ def register_hci0_scanner(hass: HomeAssistant) -> Generator[None, None, None]:
@pytest.fixture
def register_hci1_scanner(hass: HomeAssistant) -> Generator[None, None, None]:
"""Register an hci1 scanner."""
hci1_scanner = FakeScanner(hass, "hci1", "hci1")
hci1_scanner = FakeScanner("hci1", "hci1")
cancel = bluetooth.async_register_scanner(hass, hci1_scanner, True)
yield
cancel()
@ -562,7 +562,7 @@ async def test_switching_adapters_when_one_goes_away(
) -> None:
"""Test switching adapters when one goes away."""
cancel_hci2 = bluetooth.async_register_scanner(
hass, FakeScanner(hass, "hci2", "hci2"), True
hass, FakeScanner("hci2", "hci2"), True
)
address = "44:44:33:11:23:45"
@ -612,7 +612,7 @@ async def test_switching_adapters_when_one_stop_scanning(
hass: HomeAssistant, enable_bluetooth: None, register_hci0_scanner: None
) -> None:
"""Test switching adapters when stops scanning."""
hci2_scanner = FakeScanner(hass, "hci2", "hci2")
hci2_scanner = FakeScanner("hci2", "hci2")
cancel_hci2 = bluetooth.async_register_scanner(hass, hci2_scanner, True)
address = "44:44:33:11:23:45"
@ -704,7 +704,7 @@ async def test_goes_unavailable_connectable_only_and_recovers(
BluetoothScanningMode.ACTIVE,
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
def inject_advertisement(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
@ -877,7 +877,7 @@ async def test_goes_unavailable_dismisses_discovery_and_makes_discoverable(
BluetoothScanningMode.ACTIVE,
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
def inject_advertisement(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:

View File

@ -10,9 +10,9 @@ from bleak.backends.scanner import AdvertisementData
import pytest
from homeassistant.components.bluetooth import (
BaseHaRemoteScanner,
BaseHaScanner,
HaBluetoothConnector,
HomeAssistantRemoteScanner,
)
from homeassistant.components.bluetooth.wrappers import (
HaBleakClientWrapper,
@ -158,7 +158,7 @@ async def test_wrapped_bleak_client_set_disconnected_callback_after_connected(
local_name="wohand", service_uuids=[], manufacturer_data={1: b"\x01"}, rssi=-100
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
@property
def discovered_devices(self) -> list[BLEDevice]:
"""Return a list of discovered devices."""
@ -271,7 +271,7 @@ async def test_ble_device_with_proxy_client_out_of_connections(
local_name="wohand", service_uuids=[], manufacturer_data={1: b"\x01"}
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
@property
def discovered_devices(self) -> list[BLEDevice]:
"""Return a list of discovered devices."""
@ -336,7 +336,7 @@ async def test_ble_device_with_proxy_clear_cache(
local_name="wohand", service_uuids=[], manufacturer_data={1: b"\x01"}
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
@property
def discovered_devices(self) -> list[BLEDevice]:
"""Return a list of discovered devices."""
@ -439,7 +439,7 @@ async def test_ble_device_with_proxy_client_out_of_connections_uses_best_availab
"esp32_no_connection_slot",
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
@property
def discovered_devices(self) -> list[BLEDevice]:
"""Return a list of discovered devices."""
@ -553,7 +553,7 @@ async def test_ble_device_with_proxy_client_out_of_connections_uses_best_availab
"esp32_no_connection_slot",
)
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
@property
def discovered_devices(self) -> list[BLEDevice]:
"""Return a list of discovered devices."""

View File

@ -12,9 +12,9 @@ import pytest
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
BluetoothServiceInfoBleak,
HaBluetoothConnector,
HomeAssistantRemoteScanner,
async_get_advertisement_callback,
)
from homeassistant.components.bluetooth.usage import (
@ -26,7 +26,7 @@ from homeassistant.core import HomeAssistant
from . import _get_manager, generate_advertisement_data, generate_ble_device
class FakeScanner(BaseHaRemoteScanner):
class FakeScanner(HomeAssistantRemoteScanner):
"""Fake scanner."""
def __init__(