Move local bluetooth scanner code into habluetooth library (#104970)
This commit is contained in:
parent
428c184c75
commit
b6245c834d
12 changed files with 180 additions and 592 deletions
|
@ -21,7 +21,12 @@ from bluetooth_adapters import (
|
|||
adapter_unique_name,
|
||||
get_adapters,
|
||||
)
|
||||
from habluetooth import HaBluetoothConnector
|
||||
from habluetooth import (
|
||||
BluetoothScanningMode,
|
||||
HaBluetoothConnector,
|
||||
HaScanner,
|
||||
ScannerStartError,
|
||||
)
|
||||
from home_assistant_bluetooth import BluetoothServiceInfo, BluetoothServiceInfoBleak
|
||||
|
||||
from homeassistant.components import usb
|
||||
|
@ -76,10 +81,9 @@ from .const import (
|
|||
LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
|
||||
SOURCE_LOCAL,
|
||||
)
|
||||
from .manager import BluetoothManager
|
||||
from .manager import MONOTONIC_TIME, BluetoothManager
|
||||
from .match import BluetoothCallbackMatcher, IntegrationMatcher
|
||||
from .models import BluetoothCallback, BluetoothChange, BluetoothScanningMode
|
||||
from .scanner import MONOTONIC_TIME, HaScanner, ScannerStartError
|
||||
from .models import BluetoothCallback, BluetoothChange
|
||||
from .storage import BluetoothStorage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -281,7 +285,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
mode = BluetoothScanningMode.PASSIVE if passive else BluetoothScanningMode.ACTIVE
|
||||
new_info_callback = async_get_advertisement_callback(hass)
|
||||
manager: BluetoothManager = hass.data[DATA_MANAGER]
|
||||
scanner = HaScanner(hass, mode, adapter, address, new_info_callback)
|
||||
scanner = HaScanner(mode, adapter, address, new_info_callback)
|
||||
try:
|
||||
scanner.async_setup()
|
||||
except RuntimeError as err:
|
||||
|
|
|
@ -9,6 +9,7 @@ from asyncio import Future
|
|||
from collections.abc import Callable, Iterable
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from habluetooth import BluetoothScanningMode
|
||||
from home_assistant_bluetooth import BluetoothServiceInfoBleak
|
||||
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
|
||||
|
@ -17,12 +18,7 @@ from .base_scanner import BaseHaScanner, BluetoothScannerDevice
|
|||
from .const import DATA_MANAGER
|
||||
from .manager import BluetoothManager
|
||||
from .match import BluetoothCallbackMatcher
|
||||
from .models import (
|
||||
BluetoothCallback,
|
||||
BluetoothChange,
|
||||
BluetoothScanningMode,
|
||||
ProcessAdvertisementCallback,
|
||||
)
|
||||
from .models import BluetoothCallback, BluetoothChange, ProcessAdvertisementCallback
|
||||
from .wrappers import HaBleakScannerWrapper
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
|
@ -17,13 +17,6 @@ MANAGER: BluetoothManager | None = None
|
|||
MONOTONIC_TIME: Final = monotonic_time_coarse
|
||||
|
||||
|
||||
class BluetoothScanningMode(Enum):
|
||||
"""The mode of scanning for bluetooth devices."""
|
||||
|
||||
PASSIVE = "passive"
|
||||
ACTIVE = "active"
|
||||
|
||||
|
||||
BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT")
|
||||
BluetoothCallback = Callable[[BluetoothServiceInfoBleak, BluetoothChange], None]
|
||||
ProcessAdvertisementCallback = Callable[[BluetoothServiceInfoBleak], bool]
|
||||
|
|
|
@ -7,6 +7,8 @@ from functools import cache
|
|||
import logging
|
||||
from typing import TYPE_CHECKING, Any, Generic, TypedDict, TypeVar, cast
|
||||
|
||||
from habluetooth import BluetoothScanningMode
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import (
|
||||
ATTR_CONNECTIONS,
|
||||
|
@ -33,11 +35,7 @@ if TYPE_CHECKING:
|
|||
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .models import (
|
||||
BluetoothChange,
|
||||
BluetoothScanningMode,
|
||||
BluetoothServiceInfoBleak,
|
||||
)
|
||||
from .models import BluetoothChange, BluetoothServiceInfoBleak
|
||||
|
||||
STORAGE_KEY = "bluetooth.passive_update_processor"
|
||||
STORAGE_VERSION = 1
|
||||
|
|
|
@ -1,390 +0,0 @@
|
|||
"""The bluetooth integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
import platform
|
||||
from typing import Any
|
||||
|
||||
import bleak
|
||||
from bleak import BleakError
|
||||
from bleak.assigned_numbers import AdvertisementDataType
|
||||
from bleak.backends.bluezdbus.advertisement_monitor import OrPattern
|
||||
from bleak.backends.bluezdbus.scanner import BlueZScannerArgs
|
||||
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 BaseHaScanner
|
||||
from .const import (
|
||||
SCANNER_WATCHDOG_INTERVAL,
|
||||
SCANNER_WATCHDOG_TIMEOUT,
|
||||
SOURCE_LOCAL,
|
||||
START_TIMEOUT,
|
||||
)
|
||||
from .models import BluetoothScanningMode, BluetoothServiceInfoBleak
|
||||
from .util import async_reset_adapter
|
||||
|
||||
OriginalBleakScanner = bleak.BleakScanner
|
||||
|
||||
# or_patterns is a workaround for the fact that passive scanning
|
||||
# needs at least one matcher to be set. The below matcher
|
||||
# will match all devices.
|
||||
PASSIVE_SCANNER_ARGS = BlueZScannerArgs(
|
||||
or_patterns=[
|
||||
OrPattern(0, AdvertisementDataType.FLAGS, b"\x06"),
|
||||
OrPattern(0, AdvertisementDataType.FLAGS, b"\x1a"),
|
||||
]
|
||||
)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# If the adapter is in a stuck state the following errors are raised:
|
||||
NEED_RESET_ERRORS = [
|
||||
"org.bluez.Error.Failed",
|
||||
"org.bluez.Error.InProgress",
|
||||
"org.bluez.Error.NotReady",
|
||||
"not found",
|
||||
]
|
||||
|
||||
# When the adapter is still initializing, the scanner will raise an exception
|
||||
# with org.freedesktop.DBus.Error.UnknownObject
|
||||
WAIT_FOR_ADAPTER_TO_INIT_ERRORS = ["org.freedesktop.DBus.Error.UnknownObject"]
|
||||
ADAPTER_INIT_TIME = 1.5
|
||||
|
||||
START_ATTEMPTS = 3
|
||||
|
||||
SCANNING_MODE_TO_BLEAK = {
|
||||
BluetoothScanningMode.ACTIVE: "active",
|
||||
BluetoothScanningMode.PASSIVE: "passive",
|
||||
}
|
||||
|
||||
# The minimum number of seconds to know
|
||||
# the adapter has not had advertisements
|
||||
# and we already tried to restart the scanner
|
||||
# without success when the first time the watch
|
||||
# dog hit the failure path.
|
||||
SCANNER_WATCHDOG_MULTIPLE = (
|
||||
SCANNER_WATCHDOG_TIMEOUT + SCANNER_WATCHDOG_INTERVAL.total_seconds()
|
||||
)
|
||||
|
||||
|
||||
class ScannerStartError(HomeAssistantError):
|
||||
"""Error to indicate that the scanner failed to start."""
|
||||
|
||||
|
||||
def create_bleak_scanner(
|
||||
detection_callback: AdvertisementDataCallback,
|
||||
scanning_mode: BluetoothScanningMode,
|
||||
adapter: str | None,
|
||||
) -> bleak.BleakScanner:
|
||||
"""Create a Bleak scanner."""
|
||||
scanner_kwargs: dict[str, Any] = {
|
||||
"detection_callback": detection_callback,
|
||||
"scanning_mode": SCANNING_MODE_TO_BLEAK[scanning_mode],
|
||||
}
|
||||
system = platform.system()
|
||||
if system == "Linux":
|
||||
# Only Linux supports multiple adapters
|
||||
if adapter:
|
||||
scanner_kwargs["adapter"] = adapter
|
||||
if scanning_mode == BluetoothScanningMode.PASSIVE:
|
||||
scanner_kwargs["bluez"] = PASSIVE_SCANNER_ARGS
|
||||
elif system == "Darwin":
|
||||
# We want mac address on macOS
|
||||
scanner_kwargs["cb"] = {"use_bdaddr": True}
|
||||
_LOGGER.debug("Initializing bluetooth scanner with %s", scanner_kwargs)
|
||||
|
||||
try:
|
||||
return OriginalBleakScanner(**scanner_kwargs)
|
||||
except (FileNotFoundError, BleakError) as ex:
|
||||
raise RuntimeError(f"Failed to initialize Bluetooth: {ex}") from ex
|
||||
|
||||
|
||||
class HaScanner(BaseHaScanner):
|
||||
"""Operate and automatically recover a BleakScanner.
|
||||
|
||||
Multiple BleakScanner can be used at the same time
|
||||
if there are multiple adapters. This is only useful
|
||||
if the adapters are not located physically next to each other.
|
||||
|
||||
Example use cases are usbip, a long extension cable, usb to bluetooth
|
||||
over ethernet, usb over ethernet, etc.
|
||||
"""
|
||||
|
||||
scanner: bleak.BleakScanner
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
mode: BluetoothScanningMode,
|
||||
adapter: str,
|
||||
address: str,
|
||||
new_info_callback: Callable[[BluetoothServiceInfoBleak], None],
|
||||
) -> None:
|
||||
"""Init bluetooth discovery."""
|
||||
self.mac_address = address
|
||||
source = address if address != DEFAULT_ADDRESS else adapter or SOURCE_LOCAL
|
||||
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
|
||||
self._last_detection = 0.0
|
||||
|
||||
@property
|
||||
def discovered_devices(self) -> list[BLEDevice]:
|
||||
"""Return a list of discovered devices."""
|
||||
return self.scanner.discovered_devices
|
||||
|
||||
@property
|
||||
def discovered_devices_and_advertisement_data(
|
||||
self,
|
||||
) -> dict[str, tuple[BLEDevice, AdvertisementData]]:
|
||||
"""Return a list of discovered devices and advertisement data."""
|
||||
return self.scanner.discovered_devices_and_advertisement_data
|
||||
|
||||
@hass_callback
|
||||
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."""
|
||||
base_diag = await super().async_diagnostics()
|
||||
return base_diag | {
|
||||
"adapter": self.adapter,
|
||||
}
|
||||
|
||||
@hass_callback
|
||||
def _async_detection_callback(
|
||||
self,
|
||||
device: BLEDevice,
|
||||
advertisement_data: AdvertisementData,
|
||||
) -> None:
|
||||
"""Call the callback when an advertisement is received.
|
||||
|
||||
Currently this is used to feed the callbacks into the
|
||||
central manager.
|
||||
"""
|
||||
callback_time = MONOTONIC_TIME()
|
||||
if (
|
||||
advertisement_data.local_name
|
||||
or advertisement_data.manufacturer_data
|
||||
or advertisement_data.service_data
|
||||
or advertisement_data.service_uuids
|
||||
):
|
||||
# Don't count empty advertisements
|
||||
# as the adapter is in a failure
|
||||
# state if all the data is empty.
|
||||
self._last_detection = callback_time
|
||||
self._new_info_callback(
|
||||
BluetoothServiceInfoBleak(
|
||||
name=advertisement_data.local_name or device.name or device.address,
|
||||
address=device.address,
|
||||
rssi=advertisement_data.rssi,
|
||||
manufacturer_data=advertisement_data.manufacturer_data,
|
||||
service_data=advertisement_data.service_data,
|
||||
service_uuids=advertisement_data.service_uuids,
|
||||
source=self.source,
|
||||
device=device,
|
||||
advertisement=advertisement_data,
|
||||
connectable=True,
|
||||
time=callback_time,
|
||||
)
|
||||
)
|
||||
|
||||
async def async_start(self) -> None:
|
||||
"""Start bluetooth scanner."""
|
||||
async with self._start_stop_lock:
|
||||
await self._async_start()
|
||||
|
||||
async def _async_start(self) -> None:
|
||||
"""Start bluetooth scanner under the lock."""
|
||||
for attempt in range(START_ATTEMPTS):
|
||||
_LOGGER.debug(
|
||||
"%s: Starting bluetooth discovery attempt: (%s/%s)",
|
||||
self.name,
|
||||
attempt + 1,
|
||||
START_ATTEMPTS,
|
||||
)
|
||||
try:
|
||||
async with asyncio.timeout(START_TIMEOUT):
|
||||
await self.scanner.start() # type: ignore[no-untyped-call]
|
||||
except InvalidMessageError as ex:
|
||||
_LOGGER.debug(
|
||||
"%s: Invalid DBus message received: %s",
|
||||
self.name,
|
||||
ex,
|
||||
exc_info=True,
|
||||
)
|
||||
raise ScannerStartError(
|
||||
f"{self.name}: Invalid DBus message received: {ex}; "
|
||||
"try restarting `dbus`"
|
||||
) from ex
|
||||
except BrokenPipeError as ex:
|
||||
_LOGGER.debug(
|
||||
"%s: DBus connection broken: %s", self.name, ex, exc_info=True
|
||||
)
|
||||
if is_docker_env():
|
||||
raise ScannerStartError(
|
||||
f"{self.name}: DBus connection broken: {ex}; try restarting "
|
||||
"`bluetooth`, `dbus`, and finally the docker container"
|
||||
) from ex
|
||||
raise ScannerStartError(
|
||||
f"{self.name}: DBus connection broken: {ex}; try restarting "
|
||||
"`bluetooth` and `dbus`"
|
||||
) from ex
|
||||
except FileNotFoundError as ex:
|
||||
_LOGGER.debug(
|
||||
"%s: FileNotFoundError while starting bluetooth: %s",
|
||||
self.name,
|
||||
ex,
|
||||
exc_info=True,
|
||||
)
|
||||
if is_docker_env():
|
||||
raise ScannerStartError(
|
||||
f"{self.name}: DBus service not found; docker config may "
|
||||
"be missing `-v /run/dbus:/run/dbus:ro`: {ex}"
|
||||
) from ex
|
||||
raise ScannerStartError(
|
||||
f"{self.name}: DBus service not found; make sure the DBus socket "
|
||||
f"is available to Home Assistant: {ex}"
|
||||
) from ex
|
||||
except asyncio.TimeoutError as ex:
|
||||
if attempt == 0:
|
||||
await self._async_reset_adapter()
|
||||
continue
|
||||
raise ScannerStartError(
|
||||
f"{self.name}: Timed out starting Bluetooth after"
|
||||
f" {START_TIMEOUT} seconds"
|
||||
) from ex
|
||||
except BleakError as ex:
|
||||
error_str = str(ex)
|
||||
if attempt == 0:
|
||||
if any(
|
||||
needs_reset_error in error_str
|
||||
for needs_reset_error in NEED_RESET_ERRORS
|
||||
):
|
||||
await self._async_reset_adapter()
|
||||
continue
|
||||
if attempt != START_ATTEMPTS - 1:
|
||||
# If we are not out of retry attempts, and the
|
||||
# adapter is still initializing, wait a bit and try again.
|
||||
if any(
|
||||
wait_error in error_str
|
||||
for wait_error in WAIT_FOR_ADAPTER_TO_INIT_ERRORS
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"%s: Waiting for adapter to initialize; attempt (%s/%s)",
|
||||
self.name,
|
||||
attempt + 1,
|
||||
START_ATTEMPTS,
|
||||
)
|
||||
await asyncio.sleep(ADAPTER_INIT_TIME)
|
||||
continue
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s: BleakError while starting bluetooth; attempt: (%s/%s): %s",
|
||||
self.name,
|
||||
attempt + 1,
|
||||
START_ATTEMPTS,
|
||||
ex,
|
||||
exc_info=True,
|
||||
)
|
||||
raise ScannerStartError(
|
||||
f"{self.name}: Failed to start Bluetooth: {ex}"
|
||||
) from ex
|
||||
|
||||
# Everything is fine, break out of the loop
|
||||
break
|
||||
|
||||
self.scanning = True
|
||||
self._async_setup_scanner_watchdog()
|
||||
await restore_discoveries(self.scanner, self.adapter)
|
||||
|
||||
@hass_callback
|
||||
def _async_scanner_watchdog(self) -> None:
|
||||
"""Check if the scanner is running."""
|
||||
if not self._async_watchdog_triggered():
|
||||
return
|
||||
if self._start_stop_lock.locked():
|
||||
_LOGGER.debug(
|
||||
"%s: Scanner is already restarting, deferring restart",
|
||||
self.name,
|
||||
)
|
||||
return
|
||||
_LOGGER.info(
|
||||
"%s: Bluetooth scanner has gone quiet for %ss, restarting",
|
||||
self.name,
|
||||
SCANNER_WATCHDOG_TIMEOUT,
|
||||
)
|
||||
# Immediately mark the scanner as not scanning
|
||||
# since the restart task will have to wait for the lock
|
||||
self.scanning = False
|
||||
self.hass.async_create_task(self._async_restart_scanner())
|
||||
|
||||
async def _async_restart_scanner(self) -> None:
|
||||
"""Restart the scanner."""
|
||||
async with self._start_stop_lock:
|
||||
time_since_last_detection = MONOTONIC_TIME() - self._last_detection
|
||||
# Stop the scanner but not the watchdog
|
||||
# since we want to try again later if it's still quiet
|
||||
await self._async_stop_scanner()
|
||||
# If there have not been any valid advertisements,
|
||||
# or the watchdog has hit the failure path multiple times,
|
||||
# do the reset.
|
||||
if (
|
||||
self._start_time == self._last_detection
|
||||
or time_since_last_detection > SCANNER_WATCHDOG_MULTIPLE
|
||||
):
|
||||
await self._async_reset_adapter()
|
||||
try:
|
||||
await self._async_start()
|
||||
except ScannerStartError as ex:
|
||||
_LOGGER.exception(
|
||||
"%s: Failed to restart Bluetooth scanner: %s",
|
||||
self.name,
|
||||
ex,
|
||||
)
|
||||
|
||||
async def _async_reset_adapter(self) -> None:
|
||||
"""Reset the adapter."""
|
||||
# There is currently nothing the user can do to fix this
|
||||
# so we log at debug level. If we later come up with a repair
|
||||
# strategy, we will change this to raise a repair issue as well.
|
||||
_LOGGER.debug("%s: adapter stopped responding; executing reset", self.name)
|
||||
result = await async_reset_adapter(self.adapter, self.mac_address)
|
||||
_LOGGER.debug("%s: adapter reset result: %s", self.name, result)
|
||||
|
||||
async def async_stop(self) -> None:
|
||||
"""Stop bluetooth scanner."""
|
||||
async with self._start_stop_lock:
|
||||
self._async_stop_scanner_watchdog()
|
||||
await self._async_stop_scanner()
|
||||
|
||||
async def _async_stop_scanner(self) -> None:
|
||||
"""Stop bluetooth discovery under the lock."""
|
||||
self.scanning = False
|
||||
_LOGGER.debug("%s: Stopping bluetooth discovery", self.name)
|
||||
try:
|
||||
await self.scanner.stop() # type: ignore[no-untyped-call]
|
||||
except BleakError as ex:
|
||||
# This is not fatal, and they may want to reload
|
||||
# the config entry to restart the scanner if they
|
||||
# change the bluetooth dongle.
|
||||
_LOGGER.error("%s: Error stopping scanner: %s", self.name, ex)
|
|
@ -4,6 +4,8 @@ from __future__ import annotations
|
|||
from abc import ABC, abstractmethod
|
||||
import logging
|
||||
|
||||
from habluetooth import BluetoothScanningMode
|
||||
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
|
||||
from .api import (
|
||||
|
@ -13,7 +15,7 @@ from .api import (
|
|||
async_track_unavailable,
|
||||
)
|
||||
from .match import BluetoothCallbackMatcher
|
||||
from .models import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak
|
||||
from .models import BluetoothChange, BluetoothServiceInfoBleak
|
||||
|
||||
|
||||
class BasePassiveBluetoothCoordinator(ABC):
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from bluetooth_adapters import BluetoothAdapters
|
||||
from bluetooth_auto_recovery import recover_adapter
|
||||
from bluetooth_data_tools import monotonic_time_coarse
|
||||
|
||||
from homeassistant.core import callback
|
||||
|
@ -69,11 +68,3 @@ def async_load_history_from_system(
|
|||
connectable_loaded_history[address] = service_info
|
||||
|
||||
return all_loaded_history, connectable_loaded_history
|
||||
|
||||
|
||||
async def async_reset_adapter(adapter: str | None, mac_address: str) -> bool | None:
|
||||
"""Reset the adapter."""
|
||||
if adapter and adapter.startswith("hci"):
|
||||
adapter_id = int(adapter[3:])
|
||||
return await recover_adapter(adapter_id, mac_address)
|
||||
return False
|
||||
|
|
|
@ -50,7 +50,7 @@ def macos_adapter():
|
|||
"homeassistant.components.bluetooth.platform.system",
|
||||
return_value="Darwin",
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
||||
"habluetooth.scanner.platform.system",
|
||||
return_value="Darwin",
|
||||
), patch(
|
||||
"bluetooth_adapters.systems.platform.system",
|
||||
|
@ -76,7 +76,7 @@ def no_adapter_fixture():
|
|||
"homeassistant.components.bluetooth.platform.system",
|
||||
return_value="Linux",
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
||||
"habluetooth.scanner.platform.system",
|
||||
return_value="Linux",
|
||||
), patch(
|
||||
"bluetooth_adapters.systems.platform.system",
|
||||
|
@ -97,7 +97,7 @@ def one_adapter_fixture():
|
|||
"homeassistant.components.bluetooth.platform.system",
|
||||
return_value="Linux",
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
||||
"habluetooth.scanner.platform.system",
|
||||
return_value="Linux",
|
||||
), patch(
|
||||
"bluetooth_adapters.systems.platform.system",
|
||||
|
@ -128,7 +128,7 @@ def two_adapters_fixture():
|
|||
with patch(
|
||||
"homeassistant.components.bluetooth.platform.system", return_value="Linux"
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
||||
"habluetooth.scanner.platform.system",
|
||||
return_value="Linux",
|
||||
), patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), patch(
|
||||
"bluetooth_adapters.systems.linux.LinuxAdapters.refresh"
|
||||
|
@ -168,7 +168,7 @@ def one_adapter_old_bluez():
|
|||
with patch(
|
||||
"homeassistant.components.bluetooth.platform.system", return_value="Linux"
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
||||
"habluetooth.scanner.platform.system",
|
||||
return_value="Linux",
|
||||
), patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), patch(
|
||||
"bluetooth_adapters.systems.linux.LinuxAdapters.refresh"
|
||||
|
|
|
@ -3,6 +3,7 @@ from unittest.mock import ANY, MagicMock, patch
|
|||
|
||||
from bleak.backends.scanner import AdvertisementData, BLEDevice
|
||||
from bluetooth_adapters import DEFAULT_ADDRESS
|
||||
from habluetooth import HaScanner
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.components.bluetooth import (
|
||||
|
@ -25,6 +26,21 @@ from tests.components.diagnostics import get_diagnostics_for_config_entry
|
|||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
class FakeHaScanner(HaScanner):
|
||||
"""Fake HaScanner."""
|
||||
|
||||
@property
|
||||
def discovered_devices_and_advertisement_data(self):
|
||||
"""Return the discovered devices and advertisement data."""
|
||||
return {
|
||||
"44:44:33:11:23:45": (
|
||||
generate_ble_device(name="x", rssi=-127, address="44:44:33:11:23:45"),
|
||||
generate_advertisement_data(local_name="x"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@patch("homeassistant.components.bluetooth.HaScanner", FakeHaScanner)
|
||||
async def test_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
|
@ -38,15 +54,8 @@ async def test_diagnostics(
|
|||
# because we cannot import the scanner class directly without it throwing an
|
||||
# error if the test is not running on linux since we won't have the correct
|
||||
# deps installed when testing on MacOS.
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.HaScanner.discovered_devices_and_advertisement_data",
|
||||
{
|
||||
"44:44:33:11:23:45": (
|
||||
generate_ble_device(name="x", rssi=-127, address="44:44:33:11:23:45"),
|
||||
generate_advertisement_data(local_name="x"),
|
||||
)
|
||||
},
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.diagnostics.platform.system",
|
||||
return_value="Linux",
|
||||
), patch(
|
||||
|
@ -88,25 +97,25 @@ async def test_diagnostics(
|
|||
"adapters": {
|
||||
"hci0": {
|
||||
"address": "00:00:00:00:00:01",
|
||||
"connection_slots": 1,
|
||||
"hw_version": "usb:v1D6Bp0246d053F",
|
||||
"passive_scan": False,
|
||||
"sw_version": "homeassistant",
|
||||
"manufacturer": "ACME",
|
||||
"passive_scan": False,
|
||||
"product": "Bluetooth Adapter 5.0",
|
||||
"product_id": "aa01",
|
||||
"sw_version": ANY,
|
||||
"vendor_id": "cc01",
|
||||
"connection_slots": 1,
|
||||
},
|
||||
"hci1": {
|
||||
"address": "00:00:00:00:00:02",
|
||||
"connection_slots": 2,
|
||||
"hw_version": "usb:v1D6Bp0246d053F",
|
||||
"passive_scan": True,
|
||||
"sw_version": "homeassistant",
|
||||
"manufacturer": "ACME",
|
||||
"passive_scan": True,
|
||||
"product": "Bluetooth Adapter 5.0",
|
||||
"product_id": "aa01",
|
||||
"sw_version": ANY,
|
||||
"vendor_id": "cc01",
|
||||
"connection_slots": 2,
|
||||
},
|
||||
},
|
||||
"dbus": {
|
||||
|
@ -126,63 +135,42 @@ async def test_diagnostics(
|
|||
}
|
||||
},
|
||||
"manager": {
|
||||
"slot_manager": {
|
||||
"adapter_slots": {"hci0": 5, "hci1": 2},
|
||||
"allocations_by_adapter": {"hci0": [], "hci1": []},
|
||||
"manager": False,
|
||||
},
|
||||
"adapters": {
|
||||
"hci0": {
|
||||
"address": "00:00:00:00:00:01",
|
||||
"connection_slots": 1,
|
||||
"hw_version": "usb:v1D6Bp0246d053F",
|
||||
"passive_scan": False,
|
||||
"sw_version": "homeassistant",
|
||||
"manufacturer": "ACME",
|
||||
"passive_scan": False,
|
||||
"product": "Bluetooth Adapter 5.0",
|
||||
"product_id": "aa01",
|
||||
"sw_version": "homeassistant",
|
||||
"vendor_id": "cc01",
|
||||
"connection_slots": 1,
|
||||
},
|
||||
"hci1": {
|
||||
"address": "00:00:00:00:00:02",
|
||||
"connection_slots": 2,
|
||||
"hw_version": "usb:v1D6Bp0246d053F",
|
||||
"passive_scan": True,
|
||||
"sw_version": "homeassistant",
|
||||
"manufacturer": "ACME",
|
||||
"passive_scan": True,
|
||||
"product": "Bluetooth Adapter 5.0",
|
||||
"product_id": "aa01",
|
||||
"sw_version": "homeassistant",
|
||||
"vendor_id": "cc01",
|
||||
"connection_slots": 2,
|
||||
},
|
||||
},
|
||||
"advertisement_tracker": {
|
||||
"intervals": {},
|
||||
"fallback_intervals": {},
|
||||
"intervals": {},
|
||||
"sources": {},
|
||||
"timings": {},
|
||||
},
|
||||
"connectable_history": [],
|
||||
"all_history": [],
|
||||
"connectable_history": [],
|
||||
"scanners": [
|
||||
{
|
||||
"adapter": "hci0",
|
||||
"discovered_devices_and_advertisement_data": [
|
||||
{
|
||||
"address": "44:44:33:11:23:45",
|
||||
"advertisement_data": [
|
||||
"x",
|
||||
{},
|
||||
{},
|
||||
[],
|
||||
-127,
|
||||
-127,
|
||||
[[]],
|
||||
],
|
||||
"details": None,
|
||||
"name": "x",
|
||||
"rssi": -127,
|
||||
}
|
||||
],
|
||||
"discovered_devices_and_advertisement_data": [],
|
||||
"last_detection": ANY,
|
||||
"monotonic_time": ANY,
|
||||
"name": "hci0 (00:00:00:00:00:01)",
|
||||
|
@ -216,7 +204,7 @@ async def test_diagnostics(
|
|||
"scanning": True,
|
||||
"source": "00:00:00:00:00:01",
|
||||
"start_time": ANY,
|
||||
"type": "HaScanner",
|
||||
"type": "FakeHaScanner",
|
||||
},
|
||||
{
|
||||
"adapter": "hci1",
|
||||
|
@ -243,13 +231,19 @@ async def test_diagnostics(
|
|||
"scanning": True,
|
||||
"source": "00:00:00:00:00:02",
|
||||
"start_time": ANY,
|
||||
"type": "HaScanner",
|
||||
"type": "FakeHaScanner",
|
||||
},
|
||||
],
|
||||
"slot_manager": {
|
||||
"adapter_slots": {"hci0": 5, "hci1": 2},
|
||||
"allocations_by_adapter": {"hci0": [], "hci1": []},
|
||||
"manager": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@patch("homeassistant.components.bluetooth.HaScanner", FakeHaScanner)
|
||||
async def test_diagnostics_macos(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
|
@ -269,14 +263,6 @@ async def test_diagnostics_macos(
|
|||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.HaScanner.discovered_devices_and_advertisement_data",
|
||||
{
|
||||
"44:44:33:11:23:45": (
|
||||
generate_ble_device(name="x", rssi=-127, address="44:44:33:11:23:45"),
|
||||
switchbot_adv,
|
||||
)
|
||||
},
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.diagnostics.platform.system",
|
||||
return_value="Darwin",
|
||||
), patch(
|
||||
|
@ -297,70 +283,36 @@ async def test_diagnostics_macos(
|
|||
inject_advertisement(hass, switchbot_device, switchbot_adv)
|
||||
|
||||
diag = await get_diagnostics_for_config_entry(hass, hass_client, entry1)
|
||||
|
||||
assert diag == {
|
||||
"adapters": {
|
||||
"Core Bluetooth": {
|
||||
"address": "00:00:00:00:00:00",
|
||||
"passive_scan": False,
|
||||
"sw_version": ANY,
|
||||
"manufacturer": "Apple",
|
||||
"passive_scan": False,
|
||||
"product": "Unknown MacOS Model",
|
||||
"product_id": "Unknown",
|
||||
"sw_version": ANY,
|
||||
"vendor_id": "Unknown",
|
||||
}
|
||||
},
|
||||
"manager": {
|
||||
"slot_manager": {
|
||||
"adapter_slots": {"Core Bluetooth": 5},
|
||||
"allocations_by_adapter": {"Core Bluetooth": []},
|
||||
"manager": False,
|
||||
},
|
||||
"adapters": {
|
||||
"Core Bluetooth": {
|
||||
"address": "00:00:00:00:00:00",
|
||||
"passive_scan": False,
|
||||
"sw_version": ANY,
|
||||
"manufacturer": "Apple",
|
||||
"passive_scan": False,
|
||||
"product": "Unknown MacOS Model",
|
||||
"product_id": "Unknown",
|
||||
"sw_version": ANY,
|
||||
"vendor_id": "Unknown",
|
||||
}
|
||||
},
|
||||
"advertisement_tracker": {
|
||||
"intervals": {},
|
||||
"fallback_intervals": {},
|
||||
"intervals": {},
|
||||
"sources": {"44:44:33:11:23:45": "local"},
|
||||
"timings": {"44:44:33:11:23:45": [ANY]},
|
||||
},
|
||||
"connectable_history": [
|
||||
{
|
||||
"address": "44:44:33:11:23:45",
|
||||
"advertisement": [
|
||||
"wohand",
|
||||
{"1": {"__type": "<class 'bytes'>", "repr": "b'\\x01'"}},
|
||||
{},
|
||||
[],
|
||||
-127,
|
||||
-127,
|
||||
[[]],
|
||||
],
|
||||
"device": {
|
||||
"__type": "<class 'bleak.backends.device.BLEDevice'>",
|
||||
"repr": "BLEDevice(44:44:33:11:23:45, wohand)",
|
||||
},
|
||||
"connectable": True,
|
||||
"manufacturer_data": {
|
||||
"1": {"__type": "<class 'bytes'>", "repr": "b'\\x01'"}
|
||||
},
|
||||
"name": "wohand",
|
||||
"rssi": -127,
|
||||
"service_data": {},
|
||||
"service_uuids": [],
|
||||
"source": "local",
|
||||
"time": ANY,
|
||||
}
|
||||
],
|
||||
"all_history": [
|
||||
{
|
||||
"address": "44:44:33:11:23:45",
|
||||
|
@ -373,11 +325,39 @@ async def test_diagnostics_macos(
|
|||
-127,
|
||||
[[]],
|
||||
],
|
||||
"connectable": True,
|
||||
"device": {
|
||||
"__type": "<class 'bleak.backends.device.BLEDevice'>",
|
||||
"repr": "BLEDevice(44:44:33:11:23:45, wohand)",
|
||||
},
|
||||
"manufacturer_data": {
|
||||
"1": {"__type": "<class 'bytes'>", "repr": "b'\\x01'"}
|
||||
},
|
||||
"name": "wohand",
|
||||
"rssi": -127,
|
||||
"service_data": {},
|
||||
"service_uuids": [],
|
||||
"source": "local",
|
||||
"time": ANY,
|
||||
}
|
||||
],
|
||||
"connectable_history": [
|
||||
{
|
||||
"address": "44:44:33:11:23:45",
|
||||
"advertisement": [
|
||||
"wohand",
|
||||
{"1": {"__type": "<class 'bytes'>", "repr": "b'\\x01'"}},
|
||||
{},
|
||||
[],
|
||||
-127,
|
||||
-127,
|
||||
[[]],
|
||||
],
|
||||
"connectable": True,
|
||||
"device": {
|
||||
"__type": "<class 'bleak.backends.device.BLEDevice'>",
|
||||
"repr": "BLEDevice(44:44:33:11:23:45, wohand)",
|
||||
},
|
||||
"manufacturer_data": {
|
||||
"1": {"__type": "<class 'bytes'>", "repr": "b'\\x01'"}
|
||||
},
|
||||
|
@ -396,13 +376,8 @@ async def test_diagnostics_macos(
|
|||
{
|
||||
"address": "44:44:33:11:23:45",
|
||||
"advertisement_data": [
|
||||
"wohand",
|
||||
{
|
||||
"1": {
|
||||
"__type": "<class 'bytes'>",
|
||||
"repr": "b'\\x01'",
|
||||
}
|
||||
},
|
||||
"x",
|
||||
{},
|
||||
{},
|
||||
[],
|
||||
-127,
|
||||
|
@ -420,13 +395,19 @@ async def test_diagnostics_macos(
|
|||
"scanning": True,
|
||||
"source": "Core Bluetooth",
|
||||
"start_time": ANY,
|
||||
"type": "HaScanner",
|
||||
"type": "FakeHaScanner",
|
||||
}
|
||||
],
|
||||
"slot_manager": {
|
||||
"adapter_slots": {"Core Bluetooth": 5},
|
||||
"allocations_by_adapter": {"Core Bluetooth": []},
|
||||
"manager": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@patch("homeassistant.components.bluetooth.HaScanner", FakeHaScanner)
|
||||
async def test_diagnostics_remote_adapter(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
|
@ -497,17 +478,12 @@ async def test_diagnostics_remote_adapter(
|
|||
"passive_scan": False,
|
||||
"product": "Bluetooth Adapter 5.0",
|
||||
"product_id": "aa01",
|
||||
"sw_version": "homeassistant",
|
||||
"sw_version": ANY,
|
||||
"vendor_id": "cc01",
|
||||
}
|
||||
},
|
||||
"dbus": {},
|
||||
"manager": {
|
||||
"slot_manager": {
|
||||
"adapter_slots": {"hci0": 5},
|
||||
"allocations_by_adapter": {"hci0": []},
|
||||
"manager": False,
|
||||
},
|
||||
"adapters": {
|
||||
"hci0": {
|
||||
"address": "00:00:00:00:00:01",
|
||||
|
@ -521,8 +497,8 @@ async def test_diagnostics_remote_adapter(
|
|||
}
|
||||
},
|
||||
"advertisement_tracker": {
|
||||
"intervals": {},
|
||||
"fallback_intervals": {},
|
||||
"intervals": {},
|
||||
"sources": {"44:44:33:11:23:45": "esp32"},
|
||||
"timings": {"44:44:33:11:23:45": [ANY]},
|
||||
},
|
||||
|
@ -596,19 +572,34 @@ async def test_diagnostics_remote_adapter(
|
|||
},
|
||||
{
|
||||
"adapter": "hci0",
|
||||
"discovered_devices_and_advertisement_data": [],
|
||||
"discovered_devices_and_advertisement_data": [
|
||||
{
|
||||
"address": "44:44:33:11:23:45",
|
||||
"advertisement_data": [
|
||||
"x",
|
||||
{},
|
||||
{},
|
||||
[],
|
||||
-127,
|
||||
-127,
|
||||
[[]],
|
||||
],
|
||||
"details": None,
|
||||
"name": "x",
|
||||
"rssi": -127,
|
||||
}
|
||||
],
|
||||
"last_detection": ANY,
|
||||
"monotonic_time": ANY,
|
||||
"name": "hci0 (00:00:00:00:00:01)",
|
||||
"scanning": True,
|
||||
"source": "00:00:00:00:00:01",
|
||||
"start_time": ANY,
|
||||
"type": "HaScanner",
|
||||
"type": "FakeHaScanner",
|
||||
},
|
||||
{
|
||||
"connectable": False,
|
||||
"discovered_device_timestamps": {"44:44:33:11:23:45": ANY},
|
||||
"time_since_last_device_detection": {"44:44:33:11:23:45": ANY},
|
||||
"discovered_devices_and_advertisement_data": [
|
||||
{
|
||||
"address": "44:44:33:11:23:45",
|
||||
|
@ -639,11 +630,17 @@ async def test_diagnostics_remote_adapter(
|
|||
"name": "esp32",
|
||||
"scanning": True,
|
||||
"source": "esp32",
|
||||
"storage": None,
|
||||
"type": "FakeScanner",
|
||||
"start_time": ANY,
|
||||
"storage": None,
|
||||
"time_since_last_device_detection": {"44:44:33:11:23:45": ANY},
|
||||
"type": "FakeScanner",
|
||||
},
|
||||
],
|
||||
"slot_manager": {
|
||||
"adapter_slots": {"hci0": 5},
|
||||
"allocations_by_adapter": {"hci0": []},
|
||||
"manager": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ from unittest.mock import ANY, MagicMock, Mock, patch
|
|||
from bleak import BleakError
|
||||
from bleak.backends.scanner import AdvertisementData, BLEDevice
|
||||
from bluetooth_adapters import DEFAULT_ADDRESS
|
||||
from habluetooth import scanner
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
|
@ -17,7 +18,6 @@ from homeassistant.components.bluetooth import (
|
|||
async_process_advertisements,
|
||||
async_rediscover_address,
|
||||
async_track_unavailable,
|
||||
scanner,
|
||||
)
|
||||
from homeassistant.components.bluetooth.const import (
|
||||
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
|
||||
|
@ -107,7 +107,7 @@ async def test_setup_and_stop_passive(
|
|||
"""Register a callback."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
MockPassiveBleakScanner,
|
||||
):
|
||||
assert await async_setup_component(
|
||||
|
@ -158,7 +158,7 @@ async def test_setup_and_stop_old_bluez(
|
|||
"""Register a callback."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
MockBleakScanner,
|
||||
):
|
||||
assert await async_setup_component(
|
||||
|
@ -185,7 +185,7 @@ async def test_setup_and_stop_no_bluetooth(
|
|||
{"domain": "switchbot", "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b"}
|
||||
]
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
side_effect=BleakError,
|
||||
) as mock_ha_bleak_scanner, patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
|
@ -206,7 +206,7 @@ async def test_setup_and_stop_broken_bluetooth(
|
|||
"""Test we fail gracefully when bluetooth/dbus is broken."""
|
||||
mock_bt = []
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=BleakError,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
|
@ -231,7 +231,7 @@ async def test_setup_and_stop_broken_bluetooth_hanging(
|
|||
await asyncio.sleep(1)
|
||||
|
||||
with patch.object(scanner, "START_TIMEOUT", 0), patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=_mock_hang,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
|
@ -251,7 +251,7 @@ async def test_setup_and_retry_adapter_not_yet_available(
|
|||
"""Test we retry if the adapter is not yet available."""
|
||||
mock_bt = []
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=BleakError,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
|
@ -267,14 +267,14 @@ async def test_setup_and_retry_adapter_not_yet_available(
|
|||
assert entry.state == ConfigEntryState.SETUP_RETRY
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
):
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
||||
await hass.async_block_till_done()
|
||||
assert entry.state == ConfigEntryState.LOADED
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.stop",
|
||||
"habluetooth.scanner.OriginalBleakScanner.stop",
|
||||
):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -286,7 +286,7 @@ async def test_no_race_during_manual_reload_in_retry_state(
|
|||
"""Test we can successfully reload when the entry is in a retry state."""
|
||||
mock_bt = []
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=BleakError,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
|
@ -302,7 +302,7 @@ async def test_no_race_during_manual_reload_in_retry_state(
|
|||
assert entry.state == ConfigEntryState.SETUP_RETRY
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
):
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -310,7 +310,7 @@ async def test_no_race_during_manual_reload_in_retry_state(
|
|||
assert entry.state == ConfigEntryState.LOADED
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.stop",
|
||||
"habluetooth.scanner.OriginalBleakScanner.stop",
|
||||
):
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -322,7 +322,7 @@ async def test_calling_async_discovered_devices_no_bluetooth(
|
|||
"""Test we fail gracefully when asking for discovered devices and there is no blueooth."""
|
||||
mock_bt = []
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
side_effect=FileNotFoundError,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
||||
|
|
|
@ -14,7 +14,6 @@ from homeassistant.components.bluetooth.const import (
|
|||
SCANNER_WATCHDOG_INTERVAL,
|
||||
SCANNER_WATCHDOG_TIMEOUT,
|
||||
)
|
||||
from homeassistant.components.bluetooth.scanner import NEED_RESET_ERRORS
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
@ -30,6 +29,14 @@ from . import (
|
|||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
# If the adapter is in a stuck state the following errors are raised:
|
||||
NEED_RESET_ERRORS = [
|
||||
"org.bluez.Error.Failed",
|
||||
"org.bluez.Error.InProgress",
|
||||
"org.bluez.Error.NotReady",
|
||||
"not found",
|
||||
]
|
||||
|
||||
|
||||
async def test_config_entry_can_be_reloaded_when_stop_raises(
|
||||
hass: HomeAssistant,
|
||||
|
@ -42,7 +49,7 @@ async def test_config_entry_can_be_reloaded_when_stop_raises(
|
|||
assert entry.state == ConfigEntryState.LOADED
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.stop",
|
||||
"habluetooth.scanner.OriginalBleakScanner.stop",
|
||||
side_effect=BleakError,
|
||||
):
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@ -57,10 +64,8 @@ async def test_dbus_socket_missing_in_container(
|
|||
) -> None:
|
||||
"""Test we handle dbus being missing in the container."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.is_docker_env", return_value=True
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
with patch("habluetooth.scanner.is_docker_env", return_value=True), patch(
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=FileNotFoundError,
|
||||
):
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
@ -79,10 +84,8 @@ async def test_dbus_socket_missing(
|
|||
) -> None:
|
||||
"""Test we handle dbus being missing."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.is_docker_env", return_value=False
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
with patch("habluetooth.scanner.is_docker_env", return_value=False), patch(
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=FileNotFoundError,
|
||||
):
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
@ -101,10 +104,8 @@ async def test_dbus_broken_pipe_in_container(
|
|||
) -> None:
|
||||
"""Test we handle dbus broken pipe in the container."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.is_docker_env", return_value=True
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
with patch("habluetooth.scanner.is_docker_env", return_value=True), patch(
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=BrokenPipeError,
|
||||
):
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
@ -124,10 +125,8 @@ async def test_dbus_broken_pipe(
|
|||
) -> None:
|
||||
"""Test we handle dbus broken pipe."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.is_docker_env", return_value=False
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
with patch("habluetooth.scanner.is_docker_env", return_value=False), patch(
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=BrokenPipeError,
|
||||
):
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
@ -148,7 +147,7 @@ async def test_invalid_dbus_message(
|
|||
"""Test we handle invalid dbus message."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=InvalidMessageError,
|
||||
):
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
@ -168,10 +167,10 @@ async def test_adapter_needs_reset_at_start(
|
|||
"""Test we cycle the adapter when it needs a restart."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
side_effect=[BleakError(error), None],
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
||||
"habluetooth.util.recover_adapter", return_value=True
|
||||
) as mock_recover_adapter:
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
||||
|
@ -216,7 +215,7 @@ async def test_recovery_from_dbus_restart(
|
|||
return mock_discovered
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
MockBleakScanner,
|
||||
):
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
@ -306,7 +305,7 @@ async def test_adapter_recovery(hass: HomeAssistant, one_adapter: None) -> None:
|
|||
"habluetooth.base_scanner.MONOTONIC_TIME",
|
||||
return_value=start_time_monotonic,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
return_value=scanner,
|
||||
):
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
@ -343,7 +342,7 @@ async def test_adapter_recovery(hass: HomeAssistant, one_adapter: None) -> None:
|
|||
+ SCANNER_WATCHDOG_TIMEOUT
|
||||
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
||||
"habluetooth.util.recover_adapter", return_value=True
|
||||
) as mock_recover_adapter:
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -395,7 +394,7 @@ async def test_adapter_scanner_fails_to_start_first_time(
|
|||
"habluetooth.base_scanner.MONOTONIC_TIME",
|
||||
return_value=start_time_monotonic,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
return_value=scanner,
|
||||
):
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
@ -432,7 +431,7 @@ async def test_adapter_scanner_fails_to_start_first_time(
|
|||
+ SCANNER_WATCHDOG_TIMEOUT
|
||||
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
||||
"habluetooth.util.recover_adapter", return_value=True
|
||||
) as mock_recover_adapter:
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -448,7 +447,7 @@ async def test_adapter_scanner_fails_to_start_first_time(
|
|||
+ SCANNER_WATCHDOG_TIMEOUT
|
||||
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
||||
"habluetooth.util.recover_adapter", return_value=True
|
||||
) as mock_recover_adapter:
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -503,16 +502,16 @@ async def test_adapter_fails_to_start_and_takes_a_bit_to_init(
|
|||
start_time_monotonic = time.monotonic()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.ADAPTER_INIT_TIME",
|
||||
"habluetooth.scanner.ADAPTER_INIT_TIME",
|
||||
0,
|
||||
), patch(
|
||||
"habluetooth.base_scanner.MONOTONIC_TIME",
|
||||
return_value=start_time_monotonic,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
return_value=scanner,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
||||
"habluetooth.util.recover_adapter", return_value=True
|
||||
) as mock_recover_adapter:
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
||||
|
@ -554,17 +553,15 @@ async def test_restart_takes_longer_than_watchdog_time(
|
|||
start_time_monotonic = time.monotonic()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.ADAPTER_INIT_TIME",
|
||||
"habluetooth.scanner.ADAPTER_INIT_TIME",
|
||||
0,
|
||||
), patch(
|
||||
"habluetooth.base_scanner.MONOTONIC_TIME",
|
||||
return_value=start_time_monotonic,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
return_value=scanner,
|
||||
), patch(
|
||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
||||
):
|
||||
), patch("habluetooth.util.recover_adapter", return_value=True):
|
||||
await async_setup_with_one_adapter(hass)
|
||||
|
||||
assert called_start == 1
|
||||
|
@ -617,7 +614,7 @@ async def test_setup_and_stop_macos(
|
|||
"""Register a callback."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
||||
"habluetooth.scanner.OriginalBleakScanner",
|
||||
MockBleakScanner,
|
||||
):
|
||||
assert await async_setup_component(
|
||||
|
|
|
@ -1574,14 +1574,14 @@ def mock_bleak_scanner_start() -> Generator[MagicMock, None, None]:
|
|||
# Late imports to avoid loading bleak unless we need it
|
||||
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from homeassistant.components.bluetooth import scanner as bluetooth_scanner
|
||||
from habluetooth import scanner as bluetooth_scanner
|
||||
|
||||
# We need to drop the stop method from the object since we patched
|
||||
# out start and this fixture will expire before the stop method is called
|
||||
# when EVENT_HOMEASSISTANT_STOP is fired.
|
||||
bluetooth_scanner.OriginalBleakScanner.stop = AsyncMock() # type: ignore[assignment]
|
||||
with patch(
|
||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
||||
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||
) as mock_bleak_scanner_start:
|
||||
yield mock_bleak_scanner_start
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue