Relocate base Bluetooth scanner code into an external library (#104930)

pull/104958/head
J. Nick Koston 2023-12-03 10:00:11 -10:00 committed by GitHub
parent c8bb72935d
commit 28584ad240
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 51 additions and 460 deletions

View File

@ -21,6 +21,7 @@ from bluetooth_adapters import (
adapter_unique_name,
get_adapters,
)
from habluetooth import HaBluetoothConnector
from home_assistant_bluetooth import BluetoothServiceInfo, BluetoothServiceInfoBleak
from homeassistant.components import usb
@ -77,12 +78,7 @@ from .const import (
)
from .manager import BluetoothManager
from .match import BluetoothCallbackMatcher, IntegrationMatcher
from .models import (
BluetoothCallback,
BluetoothChange,
BluetoothScanningMode,
HaBluetoothConnector,
)
from .models import BluetoothCallback, BluetoothChange, BluetoothScanningMode
from .scanner import MONOTONIC_TIME, HaScanner, ScannerStartError
from .storage import BluetoothStorage

View File

@ -1,19 +1,14 @@
"""Base classes for HA Bluetooth scanners for bluetooth."""
from __future__ import annotations
from abc import abstractmethod
import asyncio
from collections.abc import Callable, Generator
from contextlib import contextmanager
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import Any, Final, final
from typing import Any
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
from bleak_retry_connector import NO_RSSI_VALUE
from bluetooth_adapters import DiscoveredDeviceAdvertisementData, adapter_human_name
from bluetooth_data_tools import monotonic_time_coarse
from bluetooth_adapters import DiscoveredDeviceAdvertisementData
from habluetooth import BaseHaRemoteScanner, BaseHaScanner, HaBluetoothConnector
from home_assistant_bluetooth import BluetoothServiceInfoBleak
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
@ -25,16 +20,6 @@ from homeassistant.core import (
)
from . import models
from .const import (
CONNECTABLE_FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
SCANNER_WATCHDOG_INTERVAL,
SCANNER_WATCHDOG_TIMEOUT,
)
from .models import HaBluetoothConnector
SCANNER_WATCHDOG_INTERVAL_SECONDS: Final = SCANNER_WATCHDOG_INTERVAL.total_seconds()
MONOTONIC_TIME: Final = monotonic_time_coarse
_LOGGER = logging.getLogger(__name__)
@dataclass(slots=True)
@ -46,363 +31,6 @@ class BluetoothScannerDevice:
advertisement: AdvertisementData
class BaseHaScanner:
"""Base class for high availability BLE scanners."""
__slots__ = (
"adapter",
"connectable",
"source",
"connector",
"_connecting",
"name",
"scanning",
"_last_detection",
"_start_time",
"_cancel_watchdog",
"_loop",
)
def __init__(
self,
source: str,
adapter: str,
connector: HaBluetoothConnector | None = None,
) -> None:
"""Initialize the scanner."""
self.connectable = False
self.source = source
self.connector = connector
self._connecting = 0
self.adapter = adapter
self.name = adapter_human_name(adapter, source) if adapter != source else source
self.scanning = True
self._last_detection = 0.0
self._start_time = 0.0
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.cancel()
self._cancel_watchdog = None
@hass_callback
def _async_setup_scanner_watchdog(self) -> None:
"""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._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:
"""Check if the watchdog has been triggered."""
time_since_last_detection = MONOTONIC_TIME() - self._last_detection
_LOGGER.debug(
"%s: Scanner watchdog time_since_last_detection: %s",
self.name,
time_since_last_detection,
)
return time_since_last_detection > SCANNER_WATCHDOG_TIMEOUT
@hass_callback
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
is triggered.
"""
if self._async_watchdog_triggered():
_LOGGER.info(
(
"%s: Bluetooth scanner has gone quiet for %ss, check logs on the"
" scanner device for more information"
),
self.name,
SCANNER_WATCHDOG_TIMEOUT,
)
self.scanning = False
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."""
self._connecting += 1
self.scanning = not self._connecting
try:
yield
finally:
self._connecting -= 1
self.scanning = not self._connecting
@property
@abstractmethod
def discovered_devices(self) -> list[BLEDevice]:
"""Return a list of discovered devices."""
@property
@abstractmethod
def discovered_devices_and_advertisement_data(
self,
) -> dict[str, tuple[BLEDevice, AdvertisementData]]:
"""Return a list of discovered devices and their advertisement data."""
async def async_diagnostics(self) -> dict[str, Any]:
"""Return diagnostic information about the scanner."""
device_adv_datas = self.discovered_devices_and_advertisement_data.values()
return {
"name": self.name,
"start_time": self._start_time,
"source": self.source,
"scanning": self.scanning,
"type": self.__class__.__name__,
"last_detection": self._last_detection,
"monotonic_time": MONOTONIC_TIME(),
"discovered_devices_and_advertisement_data": [
{
"name": device.name,
"address": device.address,
"rssi": advertisement_data.rssi,
"advertisement_data": advertisement_data,
"details": device.details,
}
for device, advertisement_data in device_adv_datas
],
}
class BaseHaRemoteScanner(BaseHaScanner):
"""Base class for a high availability remote BLE scanner."""
__slots__ = (
"_new_info_callback",
"_discovered_device_advertisement_datas",
"_discovered_device_timestamps",
"_details",
"_expire_seconds",
"_cancel_track",
)
def __init__(
self,
scanner_id: str,
name: str,
new_info_callback: Callable[[BluetoothServiceInfoBleak], None],
connector: HaBluetoothConnector | None,
connectable: bool,
) -> None:
"""Initialize the scanner."""
super().__init__(scanner_id, name, connector)
self._new_info_callback = new_info_callback
self._discovered_device_advertisement_datas: dict[
str, tuple[BLEDevice, AdvertisementData]
] = {}
self._discovered_device_timestamps: dict[str, float] = {}
self.connectable = connectable
self._details: dict[str, str | HaBluetoothConnector] = {"source": scanner_id}
# 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
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."""
super().async_setup()
self._schedule_expire_devices()
self._async_setup_scanner_watchdog()
return self._unsetup
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_expire_devices(self) -> None:
"""Expire old devices."""
now = MONOTONIC_TIME()
expired = [
address
for address, timestamp in self._discovered_device_timestamps.items()
if now - timestamp > self._expire_seconds
]
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]:
"""Return a list of discovered devices."""
device_adv_datas = self._discovered_device_advertisement_datas.values()
return [
device_advertisement_data[0]
for device_advertisement_data in device_adv_datas
]
@property
def discovered_devices_and_advertisement_data(
self,
) -> dict[str, tuple[BLEDevice, AdvertisementData]]:
"""Return a list of discovered devices and advertisement data."""
return self._discovered_device_advertisement_datas
@hass_callback
def _async_on_advertisement(
self,
address: str,
rssi: int,
local_name: str | None,
service_uuids: list[str],
service_data: dict[str, bytes],
manufacturer_data: dict[int, bytes],
tx_power: int | None,
details: dict[Any, Any],
advertisement_monotonic_time: float,
) -> None:
"""Call the registered callback."""
self.scanning = not self._connecting
self._last_detection = advertisement_monotonic_time
try:
prev_discovery = self._discovered_device_advertisement_datas[address]
except KeyError:
# We expect this is the rare case and since py3.11+ has
# near zero cost try on success, and we can avoid .get()
# which is slower than [] we use the try/except pattern.
device = BLEDevice(
address=address,
name=local_name,
details=self._details | details,
rssi=rssi, # deprecated, will be removed in newer bleak
)
else:
# Merge the new data with the old data
# to function the same as BlueZ which
# merges the dicts on PropertiesChanged
prev_device = prev_discovery[0]
prev_advertisement = prev_discovery[1]
prev_service_uuids = prev_advertisement.service_uuids
prev_service_data = prev_advertisement.service_data
prev_manufacturer_data = prev_advertisement.manufacturer_data
prev_name = prev_device.name
if prev_name and (not local_name or len(prev_name) > len(local_name)):
local_name = prev_name
if service_uuids and service_uuids != prev_service_uuids:
service_uuids = list({*service_uuids, *prev_service_uuids})
elif not service_uuids:
service_uuids = prev_service_uuids
if service_data and service_data != prev_service_data:
service_data = prev_service_data | service_data
elif not service_data:
service_data = prev_service_data
if manufacturer_data and manufacturer_data != prev_manufacturer_data:
manufacturer_data = prev_manufacturer_data | manufacturer_data
elif not manufacturer_data:
manufacturer_data = prev_manufacturer_data
#
# Bleak updates the BLEDevice via create_or_update_device.
# We need to do the same to ensure integrations that already
# have the BLEDevice object get the updated details when they
# change.
#
# https://github.com/hbldh/bleak/blob/222618b7747f0467dbb32bd3679f8cfaa19b1668/bleak/backends/scanner.py#L203
#
device = prev_device
device.name = local_name
device.details = self._details | details
# pylint: disable-next=protected-access
device._rssi = rssi # deprecated, will be removed in newer bleak
advertisement_data = AdvertisementData(
local_name=None if local_name == "" else local_name,
manufacturer_data=manufacturer_data,
service_data=service_data,
service_uuids=service_uuids,
tx_power=NO_RSSI_VALUE if tx_power is None else tx_power,
rssi=rssi,
platform_data=(),
)
self._discovered_device_advertisement_datas[address] = (
device,
advertisement_data,
)
self._discovered_device_timestamps[address] = advertisement_monotonic_time
self._new_info_callback(
BluetoothServiceInfoBleak(
name=local_name or address,
address=address,
rssi=rssi,
manufacturer_data=manufacturer_data,
service_data=service_data,
service_uuids=service_uuids,
source=self.source,
device=device,
advertisement=advertisement_data,
connectable=self.connectable,
time=advertisement_monotonic_time,
)
)
async def async_diagnostics(self) -> dict[str, Any]:
"""Return diagnostic information about the scanner."""
now = MONOTONIC_TIME()
return await super().async_diagnostics() | {
"connectable": self.connectable,
"discovered_device_timestamps": self._discovered_device_timestamps,
"time_since_last_device_detection": {
address: now - timestamp
for address, timestamp in self._discovered_device_timestamps.items()
},
}
class HomeAssistantRemoteScanner(BaseHaRemoteScanner):
"""Home Assistant remote BLE scanner.

View File

@ -1,9 +1,15 @@
"""Constants for the Bluetooth integration."""
from __future__ import annotations
from datetime import timedelta
from typing import Final
from habluetooth import ( # noqa: F401
CONNECTABLE_FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
SCANNER_WATCHDOG_INTERVAL,
SCANNER_WATCHDOG_TIMEOUT,
)
DOMAIN = "bluetooth"
CONF_ADAPTER = "adapter"
@ -19,42 +25,6 @@ UNAVAILABLE_TRACK_SECONDS: Final = 60 * 5
START_TIMEOUT = 15
# The maximum time between advertisements for a device to be considered
# stale when the advertisement tracker cannot determine the interval.
#
# We have to set this quite high as we don't know
# when devices fall out of the ESPHome device (and other non-local scanners)'s
# stack like we do with BlueZ so its safer to assume its available
# since if it does go out of range and it is in range
# of another device the timeout is much shorter and it will
# switch over to using that adapter anyways.
#
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS: Final = 60 * 15
# The maximum time between advertisements for a device to be considered
# stale when the advertisement tracker can determine the interval for
# connectable devices.
#
# BlueZ uses 180 seconds by default but we give it a bit more time
# to account for the esp32's bluetooth stack being a bit slower
# than BlueZ's.
CONNECTABLE_FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS: Final = 195
# We must recover before we hit the 180s mark
# where the device is removed from the stack
# or the devices will go unavailable. Since
# we only check every 30s, we need this number
# to be
# 180s Time when device is removed from stack
# - 30s check interval
# - 30s scanner restart time * 2
#
SCANNER_WATCHDOG_TIMEOUT: Final = 90
# How often to check if the scanner has reached
# the SCANNER_WATCHDOG_TIMEOUT without seeing anything
SCANNER_WATCHDOG_INTERVAL: Final = timedelta(seconds=30)
# When the linux kernel is configured with
# CONFIG_FW_LOADER_USER_HELPER_FALLBACK it

View File

@ -19,6 +19,7 @@
"bluetooth-adapters==0.16.1",
"bluetooth-auto-recovery==1.2.3",
"bluetooth-data-tools==1.17.0",
"dbus-fast==2.14.0"
"dbus-fast==2.14.0",
"habluetooth==0.1.0"
]
}

View File

@ -2,11 +2,9 @@
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Final
from bleak import BaseBleakClient
from bluetooth_data_tools import monotonic_time_coarse
from home_assistant_bluetooth import BluetoothServiceInfoBleak
@ -19,15 +17,6 @@ MANAGER: BluetoothManager | None = None
MONOTONIC_TIME: Final = monotonic_time_coarse
@dataclass(slots=True)
class HaBluetoothConnector:
"""Data for how to connect a BLEDevice from a given scanner."""
client: type[BaseBleakClient]
source: str
can_connect: Callable[[], bool]
class BluetoothScanningMode(Enum):
"""The mode of scanning for bluetooth devices."""

View File

@ -16,13 +16,14 @@ from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData, AdvertisementDataCallback
from bleak_retry_connector import restore_discoveries
from bluetooth_adapters import DEFAULT_ADDRESS
from bluetooth_data_tools import monotonic_time_coarse as MONOTONIC_TIME
from dbus_fast import InvalidMessageError
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.package import is_docker_env
from .base_scanner import MONOTONIC_TIME, BaseHaScanner
from .base_scanner import BaseHaScanner
from .const import (
SCANNER_WATCHDOG_INTERVAL,
SCANNER_WATCHDOG_TIMEOUT,

View File

@ -23,6 +23,7 @@ dbus-fast==2.14.0
fnv-hash-fast==0.5.0
ha-av==10.1.1
ha-ffmpeg==3.1.0
habluetooth==0.1.0
hass-nabucasa==0.74.0
hassil==1.5.1
home-assistant-bluetooth==1.10.4

View File

@ -983,6 +983,9 @@ ha-philipsjs==3.1.1
# homeassistant.components.habitica
habitipy==0.2.0
# homeassistant.components.bluetooth
habluetooth==0.1.0
# homeassistant.components.cloud
hass-nabucasa==0.74.0

View File

@ -782,6 +782,9 @@ ha-philipsjs==3.1.1
# homeassistant.components.habitica
habitipy==0.2.0
# homeassistant.components.bluetooth
habluetooth==0.1.0
# homeassistant.components.cloud
hass-nabucasa==0.74.0

View File

@ -352,7 +352,7 @@ async def test_advertisment_interval_longer_than_adapter_stack_timeout_adapter_c
)
switchbot_device_went_unavailable = False
scanner = FakeScanner(hass, "new", "fake_adapter")
scanner = FakeScanner("new", "fake_adapter")
cancel_scanner = async_register_scanner(hass, scanner, False)
@callback

View File

@ -215,7 +215,7 @@ async def test_remote_scanner_expires_connectable(
seconds=CONNECTABLE_FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1
)
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=expire_monotonic,
):
async_fire_time_changed(hass, expire_utc)
@ -298,7 +298,7 @@ async def test_remote_scanner_expires_non_connectable(
seconds=CONNECTABLE_FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1
)
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=expire_monotonic,
):
async_fire_time_changed(hass, expire_utc)
@ -314,7 +314,7 @@ async def test_remote_scanner_expires_non_connectable(
seconds=FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1
)
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=expire_monotonic,
):
async_fire_time_changed(hass, expire_utc)
@ -515,7 +515,7 @@ async def test_device_with_ten_minute_advertising_interval(
)
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=new_time,
):
scanner.inject_advertisement(bparasite_device, bparasite_device_adv)
@ -528,7 +528,7 @@ async def test_device_with_ten_minute_advertising_interval(
for _ in range(1, 20):
new_time += advertising_interval
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=new_time,
):
scanner.inject_advertisement(bparasite_device, bparasite_device_adv)
@ -562,7 +562,7 @@ async def test_device_with_ten_minute_advertising_interval(
"homeassistant.components.bluetooth.manager.MONOTONIC_TIME",
return_value=missed_advertisement_future_time,
), patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=missed_advertisement_future_time,
):
# Fire once for the scanner to expire the device
@ -629,7 +629,7 @@ async def test_scanner_stops_responding(
)
# We hit the timer with no detections, so we reset the adapter and restart the scanner
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=failure_reached_time,
):
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
@ -653,7 +653,7 @@ async def test_scanner_stops_responding(
failure_reached_time += 1
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=failure_reached_time,
):
scanner.inject_advertisement(bparasite_device, bparasite_device_adv)

View File

@ -2815,7 +2815,7 @@ async def test_scanner_count_connectable(
hass: HomeAssistant, enable_bluetooth: None
) -> None:
"""Test getting the connectable scanner count."""
scanner = FakeScanner(hass, "any", "any")
scanner = FakeScanner("any", "any")
cancel = bluetooth.async_register_scanner(hass, scanner, False)
assert bluetooth.async_scanner_count(hass, connectable=True) == 1
cancel()
@ -2823,7 +2823,7 @@ async def test_scanner_count_connectable(
async def test_scanner_count(hass: HomeAssistant, enable_bluetooth: None) -> None:
"""Test getting the connectable and non-connectable scanner count."""
scanner = FakeScanner(hass, "any", "any")
scanner = FakeScanner("any", "any")
cancel = bluetooth.async_register_scanner(hass, scanner, False)
assert bluetooth.async_scanner_count(hass, connectable=False) == 2
cancel()

View File

@ -107,7 +107,6 @@ async def test_wrapped_bleak_client_local_adapter_only(
return None
scanner = FakeScanner(
hass,
"00:00:00:00:00:01",
"hci0",
)

View File

@ -228,7 +228,7 @@ async def test_recovery_from_dbus_restart(
# Ensure we don't restart the scanner if we don't need to
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic + 10,
):
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
@ -238,7 +238,7 @@ async def test_recovery_from_dbus_restart(
# Fire a callback to reset the timer
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic,
):
_callback(
@ -248,7 +248,7 @@ async def test_recovery_from_dbus_restart(
# Ensure we don't restart the scanner if we don't need to
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic + 20,
):
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
@ -258,7 +258,7 @@ async def test_recovery_from_dbus_restart(
# We hit the timer, so we restart the scanner
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic + SCANNER_WATCHDOG_TIMEOUT + 20,
):
async_fire_time_changed(
@ -303,7 +303,7 @@ async def test_adapter_recovery(hass: HomeAssistant, one_adapter: None) -> None:
start_time_monotonic = time.monotonic()
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic,
), patch(
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
@ -318,7 +318,7 @@ async def test_adapter_recovery(hass: HomeAssistant, one_adapter: None) -> None:
# Ensure we don't restart the scanner if we don't need to
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic + 10,
):
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
@ -328,7 +328,7 @@ async def test_adapter_recovery(hass: HomeAssistant, one_adapter: None) -> None:
# Ensure we don't restart the scanner if we don't need to
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic + 20,
):
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
@ -338,7 +338,7 @@ async def test_adapter_recovery(hass: HomeAssistant, one_adapter: None) -> None:
# We hit the timer with no detections, so we reset the adapter and restart the scanner
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic
+ SCANNER_WATCHDOG_TIMEOUT
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
@ -392,7 +392,7 @@ async def test_adapter_scanner_fails_to_start_first_time(
start_time_monotonic = time.monotonic()
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic,
), patch(
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
@ -407,7 +407,7 @@ async def test_adapter_scanner_fails_to_start_first_time(
# Ensure we don't restart the scanner if we don't need to
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic + 10,
):
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
@ -417,7 +417,7 @@ async def test_adapter_scanner_fails_to_start_first_time(
# Ensure we don't restart the scanner if we don't need to
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic + 20,
):
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
@ -427,7 +427,7 @@ async def test_adapter_scanner_fails_to_start_first_time(
# We hit the timer with no detections, so we reset the adapter and restart the scanner
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic
+ SCANNER_WATCHDOG_TIMEOUT
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
@ -443,7 +443,7 @@ async def test_adapter_scanner_fails_to_start_first_time(
# We hit the timer again the previous start call failed, make sure
# we try again
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic
+ SCANNER_WATCHDOG_TIMEOUT
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
@ -506,7 +506,7 @@ async def test_adapter_fails_to_start_and_takes_a_bit_to_init(
"homeassistant.components.bluetooth.scanner.ADAPTER_INIT_TIME",
0,
), patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic,
), patch(
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
@ -557,7 +557,7 @@ async def test_restart_takes_longer_than_watchdog_time(
"homeassistant.components.bluetooth.scanner.ADAPTER_INIT_TIME",
0,
), patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic,
), patch(
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
@ -572,7 +572,7 @@ async def test_restart_takes_longer_than_watchdog_time(
# Now force a recover adapter 2x
for _ in range(2):
with patch(
"homeassistant.components.bluetooth.base_scanner.MONOTONIC_TIME",
"habluetooth.base_scanner.MONOTONIC_TIME",
return_value=start_time_monotonic
+ SCANNER_WATCHDOG_TIMEOUT
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),