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,
|
adapter_unique_name,
|
||||||
get_adapters,
|
get_adapters,
|
||||||
)
|
)
|
||||||
from habluetooth import HaBluetoothConnector
|
from habluetooth import (
|
||||||
|
BluetoothScanningMode,
|
||||||
|
HaBluetoothConnector,
|
||||||
|
HaScanner,
|
||||||
|
ScannerStartError,
|
||||||
|
)
|
||||||
from home_assistant_bluetooth import BluetoothServiceInfo, BluetoothServiceInfoBleak
|
from home_assistant_bluetooth import BluetoothServiceInfo, BluetoothServiceInfoBleak
|
||||||
|
|
||||||
from homeassistant.components import usb
|
from homeassistant.components import usb
|
||||||
|
@ -76,10 +81,9 @@ from .const import (
|
||||||
LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
|
LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
|
||||||
SOURCE_LOCAL,
|
SOURCE_LOCAL,
|
||||||
)
|
)
|
||||||
from .manager import BluetoothManager
|
from .manager import MONOTONIC_TIME, BluetoothManager
|
||||||
from .match import BluetoothCallbackMatcher, IntegrationMatcher
|
from .match import BluetoothCallbackMatcher, IntegrationMatcher
|
||||||
from .models import BluetoothCallback, BluetoothChange, BluetoothScanningMode
|
from .models import BluetoothCallback, BluetoothChange
|
||||||
from .scanner import MONOTONIC_TIME, HaScanner, ScannerStartError
|
|
||||||
from .storage import BluetoothStorage
|
from .storage import BluetoothStorage
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
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
|
mode = BluetoothScanningMode.PASSIVE if passive else BluetoothScanningMode.ACTIVE
|
||||||
new_info_callback = async_get_advertisement_callback(hass)
|
new_info_callback = async_get_advertisement_callback(hass)
|
||||||
manager: BluetoothManager = hass.data[DATA_MANAGER]
|
manager: BluetoothManager = hass.data[DATA_MANAGER]
|
||||||
scanner = HaScanner(hass, mode, adapter, address, new_info_callback)
|
scanner = HaScanner(mode, adapter, address, new_info_callback)
|
||||||
try:
|
try:
|
||||||
scanner.async_setup()
|
scanner.async_setup()
|
||||||
except RuntimeError as err:
|
except RuntimeError as err:
|
||||||
|
|
|
@ -9,6 +9,7 @@ from asyncio import Future
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from typing import TYPE_CHECKING, cast
|
from typing import TYPE_CHECKING, cast
|
||||||
|
|
||||||
|
from habluetooth import BluetoothScanningMode
|
||||||
from home_assistant_bluetooth import BluetoothServiceInfoBleak
|
from home_assistant_bluetooth import BluetoothServiceInfoBleak
|
||||||
|
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
|
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 .const import DATA_MANAGER
|
||||||
from .manager import BluetoothManager
|
from .manager import BluetoothManager
|
||||||
from .match import BluetoothCallbackMatcher
|
from .match import BluetoothCallbackMatcher
|
||||||
from .models import (
|
from .models import BluetoothCallback, BluetoothChange, ProcessAdvertisementCallback
|
||||||
BluetoothCallback,
|
|
||||||
BluetoothChange,
|
|
||||||
BluetoothScanningMode,
|
|
||||||
ProcessAdvertisementCallback,
|
|
||||||
)
|
|
||||||
from .wrappers import HaBleakScannerWrapper
|
from .wrappers import HaBleakScannerWrapper
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
|
@ -17,13 +17,6 @@ MANAGER: BluetoothManager | None = None
|
||||||
MONOTONIC_TIME: Final = monotonic_time_coarse
|
MONOTONIC_TIME: Final = monotonic_time_coarse
|
||||||
|
|
||||||
|
|
||||||
class BluetoothScanningMode(Enum):
|
|
||||||
"""The mode of scanning for bluetooth devices."""
|
|
||||||
|
|
||||||
PASSIVE = "passive"
|
|
||||||
ACTIVE = "active"
|
|
||||||
|
|
||||||
|
|
||||||
BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT")
|
BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT")
|
||||||
BluetoothCallback = Callable[[BluetoothServiceInfoBleak, BluetoothChange], None]
|
BluetoothCallback = Callable[[BluetoothServiceInfoBleak, BluetoothChange], None]
|
||||||
ProcessAdvertisementCallback = Callable[[BluetoothServiceInfoBleak], bool]
|
ProcessAdvertisementCallback = Callable[[BluetoothServiceInfoBleak], bool]
|
||||||
|
|
|
@ -7,6 +7,8 @@ from functools import cache
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any, Generic, TypedDict, TypeVar, cast
|
from typing import TYPE_CHECKING, Any, Generic, TypedDict, TypeVar, cast
|
||||||
|
|
||||||
|
from habluetooth import BluetoothScanningMode
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CONNECTIONS,
|
ATTR_CONNECTIONS,
|
||||||
|
@ -33,11 +35,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .models import (
|
from .models import BluetoothChange, BluetoothServiceInfoBleak
|
||||||
BluetoothChange,
|
|
||||||
BluetoothScanningMode,
|
|
||||||
BluetoothServiceInfoBleak,
|
|
||||||
)
|
|
||||||
|
|
||||||
STORAGE_KEY = "bluetooth.passive_update_processor"
|
STORAGE_KEY = "bluetooth.passive_update_processor"
|
||||||
STORAGE_VERSION = 1
|
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
|
from abc import ABC, abstractmethod
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from habluetooth import BluetoothScanningMode
|
||||||
|
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||||
|
|
||||||
from .api import (
|
from .api import (
|
||||||
|
@ -13,7 +15,7 @@ from .api import (
|
||||||
async_track_unavailable,
|
async_track_unavailable,
|
||||||
)
|
)
|
||||||
from .match import BluetoothCallbackMatcher
|
from .match import BluetoothCallbackMatcher
|
||||||
from .models import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak
|
from .models import BluetoothChange, BluetoothServiceInfoBleak
|
||||||
|
|
||||||
|
|
||||||
class BasePassiveBluetoothCoordinator(ABC):
|
class BasePassiveBluetoothCoordinator(ABC):
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from bluetooth_adapters import BluetoothAdapters
|
from bluetooth_adapters import BluetoothAdapters
|
||||||
from bluetooth_auto_recovery import recover_adapter
|
|
||||||
from bluetooth_data_tools import monotonic_time_coarse
|
from bluetooth_data_tools import monotonic_time_coarse
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
@ -69,11 +68,3 @@ def async_load_history_from_system(
|
||||||
connectable_loaded_history[address] = service_info
|
connectable_loaded_history[address] = service_info
|
||||||
|
|
||||||
return all_loaded_history, connectable_loaded_history
|
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",
|
"homeassistant.components.bluetooth.platform.system",
|
||||||
return_value="Darwin",
|
return_value="Darwin",
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
"habluetooth.scanner.platform.system",
|
||||||
return_value="Darwin",
|
return_value="Darwin",
|
||||||
), patch(
|
), patch(
|
||||||
"bluetooth_adapters.systems.platform.system",
|
"bluetooth_adapters.systems.platform.system",
|
||||||
|
@ -76,7 +76,7 @@ def no_adapter_fixture():
|
||||||
"homeassistant.components.bluetooth.platform.system",
|
"homeassistant.components.bluetooth.platform.system",
|
||||||
return_value="Linux",
|
return_value="Linux",
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
"habluetooth.scanner.platform.system",
|
||||||
return_value="Linux",
|
return_value="Linux",
|
||||||
), patch(
|
), patch(
|
||||||
"bluetooth_adapters.systems.platform.system",
|
"bluetooth_adapters.systems.platform.system",
|
||||||
|
@ -97,7 +97,7 @@ def one_adapter_fixture():
|
||||||
"homeassistant.components.bluetooth.platform.system",
|
"homeassistant.components.bluetooth.platform.system",
|
||||||
return_value="Linux",
|
return_value="Linux",
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
"habluetooth.scanner.platform.system",
|
||||||
return_value="Linux",
|
return_value="Linux",
|
||||||
), patch(
|
), patch(
|
||||||
"bluetooth_adapters.systems.platform.system",
|
"bluetooth_adapters.systems.platform.system",
|
||||||
|
@ -128,7 +128,7 @@ def two_adapters_fixture():
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.platform.system", return_value="Linux"
|
"homeassistant.components.bluetooth.platform.system", return_value="Linux"
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
"habluetooth.scanner.platform.system",
|
||||||
return_value="Linux",
|
return_value="Linux",
|
||||||
), patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), patch(
|
), patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), patch(
|
||||||
"bluetooth_adapters.systems.linux.LinuxAdapters.refresh"
|
"bluetooth_adapters.systems.linux.LinuxAdapters.refresh"
|
||||||
|
@ -168,7 +168,7 @@ def one_adapter_old_bluez():
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.platform.system", return_value="Linux"
|
"homeassistant.components.bluetooth.platform.system", return_value="Linux"
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.platform.system",
|
"habluetooth.scanner.platform.system",
|
||||||
return_value="Linux",
|
return_value="Linux",
|
||||||
), patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), patch(
|
), patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), patch(
|
||||||
"bluetooth_adapters.systems.linux.LinuxAdapters.refresh"
|
"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 bleak.backends.scanner import AdvertisementData, BLEDevice
|
||||||
from bluetooth_adapters import DEFAULT_ADDRESS
|
from bluetooth_adapters import DEFAULT_ADDRESS
|
||||||
|
from habluetooth import HaScanner
|
||||||
|
|
||||||
from homeassistant.components import bluetooth
|
from homeassistant.components import bluetooth
|
||||||
from homeassistant.components.bluetooth import (
|
from homeassistant.components.bluetooth import (
|
||||||
|
@ -25,6 +26,21 @@ from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||||
from tests.typing import ClientSessionGenerator
|
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(
|
async def test_diagnostics(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
@ -38,15 +54,8 @@ async def test_diagnostics(
|
||||||
# because we cannot import the scanner class directly without it throwing an
|
# 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
|
# error if the test is not running on linux since we won't have the correct
|
||||||
# deps installed when testing on MacOS.
|
# deps installed when testing on MacOS.
|
||||||
|
|
||||||
with patch(
|
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",
|
"homeassistant.components.bluetooth.diagnostics.platform.system",
|
||||||
return_value="Linux",
|
return_value="Linux",
|
||||||
), patch(
|
), patch(
|
||||||
|
@ -88,25 +97,25 @@ async def test_diagnostics(
|
||||||
"adapters": {
|
"adapters": {
|
||||||
"hci0": {
|
"hci0": {
|
||||||
"address": "00:00:00:00:00:01",
|
"address": "00:00:00:00:00:01",
|
||||||
|
"connection_slots": 1,
|
||||||
"hw_version": "usb:v1D6Bp0246d053F",
|
"hw_version": "usb:v1D6Bp0246d053F",
|
||||||
"passive_scan": False,
|
|
||||||
"sw_version": "homeassistant",
|
|
||||||
"manufacturer": "ACME",
|
"manufacturer": "ACME",
|
||||||
|
"passive_scan": False,
|
||||||
"product": "Bluetooth Adapter 5.0",
|
"product": "Bluetooth Adapter 5.0",
|
||||||
"product_id": "aa01",
|
"product_id": "aa01",
|
||||||
|
"sw_version": ANY,
|
||||||
"vendor_id": "cc01",
|
"vendor_id": "cc01",
|
||||||
"connection_slots": 1,
|
|
||||||
},
|
},
|
||||||
"hci1": {
|
"hci1": {
|
||||||
"address": "00:00:00:00:00:02",
|
"address": "00:00:00:00:00:02",
|
||||||
|
"connection_slots": 2,
|
||||||
"hw_version": "usb:v1D6Bp0246d053F",
|
"hw_version": "usb:v1D6Bp0246d053F",
|
||||||
"passive_scan": True,
|
|
||||||
"sw_version": "homeassistant",
|
|
||||||
"manufacturer": "ACME",
|
"manufacturer": "ACME",
|
||||||
|
"passive_scan": True,
|
||||||
"product": "Bluetooth Adapter 5.0",
|
"product": "Bluetooth Adapter 5.0",
|
||||||
"product_id": "aa01",
|
"product_id": "aa01",
|
||||||
|
"sw_version": ANY,
|
||||||
"vendor_id": "cc01",
|
"vendor_id": "cc01",
|
||||||
"connection_slots": 2,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"dbus": {
|
"dbus": {
|
||||||
|
@ -126,63 +135,42 @@ async def test_diagnostics(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"manager": {
|
"manager": {
|
||||||
"slot_manager": {
|
|
||||||
"adapter_slots": {"hci0": 5, "hci1": 2},
|
|
||||||
"allocations_by_adapter": {"hci0": [], "hci1": []},
|
|
||||||
"manager": False,
|
|
||||||
},
|
|
||||||
"adapters": {
|
"adapters": {
|
||||||
"hci0": {
|
"hci0": {
|
||||||
"address": "00:00:00:00:00:01",
|
"address": "00:00:00:00:00:01",
|
||||||
|
"connection_slots": 1,
|
||||||
"hw_version": "usb:v1D6Bp0246d053F",
|
"hw_version": "usb:v1D6Bp0246d053F",
|
||||||
"passive_scan": False,
|
|
||||||
"sw_version": "homeassistant",
|
|
||||||
"manufacturer": "ACME",
|
"manufacturer": "ACME",
|
||||||
|
"passive_scan": False,
|
||||||
"product": "Bluetooth Adapter 5.0",
|
"product": "Bluetooth Adapter 5.0",
|
||||||
"product_id": "aa01",
|
"product_id": "aa01",
|
||||||
|
"sw_version": "homeassistant",
|
||||||
"vendor_id": "cc01",
|
"vendor_id": "cc01",
|
||||||
"connection_slots": 1,
|
|
||||||
},
|
},
|
||||||
"hci1": {
|
"hci1": {
|
||||||
"address": "00:00:00:00:00:02",
|
"address": "00:00:00:00:00:02",
|
||||||
|
"connection_slots": 2,
|
||||||
"hw_version": "usb:v1D6Bp0246d053F",
|
"hw_version": "usb:v1D6Bp0246d053F",
|
||||||
"passive_scan": True,
|
|
||||||
"sw_version": "homeassistant",
|
|
||||||
"manufacturer": "ACME",
|
"manufacturer": "ACME",
|
||||||
|
"passive_scan": True,
|
||||||
"product": "Bluetooth Adapter 5.0",
|
"product": "Bluetooth Adapter 5.0",
|
||||||
"product_id": "aa01",
|
"product_id": "aa01",
|
||||||
|
"sw_version": "homeassistant",
|
||||||
"vendor_id": "cc01",
|
"vendor_id": "cc01",
|
||||||
"connection_slots": 2,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"advertisement_tracker": {
|
"advertisement_tracker": {
|
||||||
"intervals": {},
|
|
||||||
"fallback_intervals": {},
|
"fallback_intervals": {},
|
||||||
|
"intervals": {},
|
||||||
"sources": {},
|
"sources": {},
|
||||||
"timings": {},
|
"timings": {},
|
||||||
},
|
},
|
||||||
"connectable_history": [],
|
|
||||||
"all_history": [],
|
"all_history": [],
|
||||||
|
"connectable_history": [],
|
||||||
"scanners": [
|
"scanners": [
|
||||||
{
|
{
|
||||||
"adapter": "hci0",
|
"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,
|
"last_detection": ANY,
|
||||||
"monotonic_time": ANY,
|
"monotonic_time": ANY,
|
||||||
"name": "hci0 (00:00:00:00:00:01)",
|
"name": "hci0 (00:00:00:00:00:01)",
|
||||||
|
@ -216,7 +204,7 @@ async def test_diagnostics(
|
||||||
"scanning": True,
|
"scanning": True,
|
||||||
"source": "00:00:00:00:00:01",
|
"source": "00:00:00:00:00:01",
|
||||||
"start_time": ANY,
|
"start_time": ANY,
|
||||||
"type": "HaScanner",
|
"type": "FakeHaScanner",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"adapter": "hci1",
|
"adapter": "hci1",
|
||||||
|
@ -243,13 +231,19 @@ async def test_diagnostics(
|
||||||
"scanning": True,
|
"scanning": True,
|
||||||
"source": "00:00:00:00:00:02",
|
"source": "00:00:00:00:00:02",
|
||||||
"start_time": ANY,
|
"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(
|
async def test_diagnostics_macos(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
@ -269,14 +263,6 @@ async def test_diagnostics_macos(
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
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",
|
"homeassistant.components.bluetooth.diagnostics.platform.system",
|
||||||
return_value="Darwin",
|
return_value="Darwin",
|
||||||
), patch(
|
), patch(
|
||||||
|
@ -297,70 +283,36 @@ async def test_diagnostics_macos(
|
||||||
inject_advertisement(hass, switchbot_device, switchbot_adv)
|
inject_advertisement(hass, switchbot_device, switchbot_adv)
|
||||||
|
|
||||||
diag = await get_diagnostics_for_config_entry(hass, hass_client, entry1)
|
diag = await get_diagnostics_for_config_entry(hass, hass_client, entry1)
|
||||||
|
|
||||||
assert diag == {
|
assert diag == {
|
||||||
"adapters": {
|
"adapters": {
|
||||||
"Core Bluetooth": {
|
"Core Bluetooth": {
|
||||||
"address": "00:00:00:00:00:00",
|
"address": "00:00:00:00:00:00",
|
||||||
"passive_scan": False,
|
|
||||||
"sw_version": ANY,
|
|
||||||
"manufacturer": "Apple",
|
"manufacturer": "Apple",
|
||||||
|
"passive_scan": False,
|
||||||
"product": "Unknown MacOS Model",
|
"product": "Unknown MacOS Model",
|
||||||
"product_id": "Unknown",
|
"product_id": "Unknown",
|
||||||
|
"sw_version": ANY,
|
||||||
"vendor_id": "Unknown",
|
"vendor_id": "Unknown",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"manager": {
|
"manager": {
|
||||||
"slot_manager": {
|
|
||||||
"adapter_slots": {"Core Bluetooth": 5},
|
|
||||||
"allocations_by_adapter": {"Core Bluetooth": []},
|
|
||||||
"manager": False,
|
|
||||||
},
|
|
||||||
"adapters": {
|
"adapters": {
|
||||||
"Core Bluetooth": {
|
"Core Bluetooth": {
|
||||||
"address": "00:00:00:00:00:00",
|
"address": "00:00:00:00:00:00",
|
||||||
"passive_scan": False,
|
|
||||||
"sw_version": ANY,
|
|
||||||
"manufacturer": "Apple",
|
"manufacturer": "Apple",
|
||||||
|
"passive_scan": False,
|
||||||
"product": "Unknown MacOS Model",
|
"product": "Unknown MacOS Model",
|
||||||
"product_id": "Unknown",
|
"product_id": "Unknown",
|
||||||
|
"sw_version": ANY,
|
||||||
"vendor_id": "Unknown",
|
"vendor_id": "Unknown",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"advertisement_tracker": {
|
"advertisement_tracker": {
|
||||||
"intervals": {},
|
|
||||||
"fallback_intervals": {},
|
"fallback_intervals": {},
|
||||||
|
"intervals": {},
|
||||||
"sources": {"44:44:33:11:23:45": "local"},
|
"sources": {"44:44:33:11:23:45": "local"},
|
||||||
"timings": {"44:44:33:11:23:45": [ANY]},
|
"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": [
|
"all_history": [
|
||||||
{
|
{
|
||||||
"address": "44:44:33:11:23:45",
|
"address": "44:44:33:11:23:45",
|
||||||
|
@ -373,11 +325,39 @@ async def test_diagnostics_macos(
|
||||||
-127,
|
-127,
|
||||||
[[]],
|
[[]],
|
||||||
],
|
],
|
||||||
|
"connectable": True,
|
||||||
"device": {
|
"device": {
|
||||||
"__type": "<class 'bleak.backends.device.BLEDevice'>",
|
"__type": "<class 'bleak.backends.device.BLEDevice'>",
|
||||||
"repr": "BLEDevice(44:44:33:11:23:45, wohand)",
|
"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,
|
"connectable": True,
|
||||||
|
"device": {
|
||||||
|
"__type": "<class 'bleak.backends.device.BLEDevice'>",
|
||||||
|
"repr": "BLEDevice(44:44:33:11:23:45, wohand)",
|
||||||
|
},
|
||||||
"manufacturer_data": {
|
"manufacturer_data": {
|
||||||
"1": {"__type": "<class 'bytes'>", "repr": "b'\\x01'"}
|
"1": {"__type": "<class 'bytes'>", "repr": "b'\\x01'"}
|
||||||
},
|
},
|
||||||
|
@ -396,13 +376,8 @@ async def test_diagnostics_macos(
|
||||||
{
|
{
|
||||||
"address": "44:44:33:11:23:45",
|
"address": "44:44:33:11:23:45",
|
||||||
"advertisement_data": [
|
"advertisement_data": [
|
||||||
"wohand",
|
"x",
|
||||||
{
|
{},
|
||||||
"1": {
|
|
||||||
"__type": "<class 'bytes'>",
|
|
||||||
"repr": "b'\\x01'",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{},
|
{},
|
||||||
[],
|
[],
|
||||||
-127,
|
-127,
|
||||||
|
@ -420,13 +395,19 @@ async def test_diagnostics_macos(
|
||||||
"scanning": True,
|
"scanning": True,
|
||||||
"source": "Core Bluetooth",
|
"source": "Core Bluetooth",
|
||||||
"start_time": ANY,
|
"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(
|
async def test_diagnostics_remote_adapter(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
@ -497,17 +478,12 @@ async def test_diagnostics_remote_adapter(
|
||||||
"passive_scan": False,
|
"passive_scan": False,
|
||||||
"product": "Bluetooth Adapter 5.0",
|
"product": "Bluetooth Adapter 5.0",
|
||||||
"product_id": "aa01",
|
"product_id": "aa01",
|
||||||
"sw_version": "homeassistant",
|
"sw_version": ANY,
|
||||||
"vendor_id": "cc01",
|
"vendor_id": "cc01",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dbus": {},
|
"dbus": {},
|
||||||
"manager": {
|
"manager": {
|
||||||
"slot_manager": {
|
|
||||||
"adapter_slots": {"hci0": 5},
|
|
||||||
"allocations_by_adapter": {"hci0": []},
|
|
||||||
"manager": False,
|
|
||||||
},
|
|
||||||
"adapters": {
|
"adapters": {
|
||||||
"hci0": {
|
"hci0": {
|
||||||
"address": "00:00:00:00:00:01",
|
"address": "00:00:00:00:00:01",
|
||||||
|
@ -521,8 +497,8 @@ async def test_diagnostics_remote_adapter(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"advertisement_tracker": {
|
"advertisement_tracker": {
|
||||||
"intervals": {},
|
|
||||||
"fallback_intervals": {},
|
"fallback_intervals": {},
|
||||||
|
"intervals": {},
|
||||||
"sources": {"44:44:33:11:23:45": "esp32"},
|
"sources": {"44:44:33:11:23:45": "esp32"},
|
||||||
"timings": {"44:44:33:11:23:45": [ANY]},
|
"timings": {"44:44:33:11:23:45": [ANY]},
|
||||||
},
|
},
|
||||||
|
@ -596,19 +572,34 @@ async def test_diagnostics_remote_adapter(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"adapter": "hci0",
|
"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,
|
"last_detection": ANY,
|
||||||
"monotonic_time": ANY,
|
"monotonic_time": ANY,
|
||||||
"name": "hci0 (00:00:00:00:00:01)",
|
"name": "hci0 (00:00:00:00:00:01)",
|
||||||
"scanning": True,
|
"scanning": True,
|
||||||
"source": "00:00:00:00:00:01",
|
"source": "00:00:00:00:00:01",
|
||||||
"start_time": ANY,
|
"start_time": ANY,
|
||||||
"type": "HaScanner",
|
"type": "FakeHaScanner",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"connectable": False,
|
"connectable": False,
|
||||||
"discovered_device_timestamps": {"44:44:33:11:23:45": ANY},
|
"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": [
|
"discovered_devices_and_advertisement_data": [
|
||||||
{
|
{
|
||||||
"address": "44:44:33:11:23:45",
|
"address": "44:44:33:11:23:45",
|
||||||
|
@ -639,11 +630,17 @@ async def test_diagnostics_remote_adapter(
|
||||||
"name": "esp32",
|
"name": "esp32",
|
||||||
"scanning": True,
|
"scanning": True,
|
||||||
"source": "esp32",
|
"source": "esp32",
|
||||||
"storage": None,
|
|
||||||
"type": "FakeScanner",
|
|
||||||
"start_time": ANY,
|
"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 import BleakError
|
||||||
from bleak.backends.scanner import AdvertisementData, BLEDevice
|
from bleak.backends.scanner import AdvertisementData, BLEDevice
|
||||||
from bluetooth_adapters import DEFAULT_ADDRESS
|
from bluetooth_adapters import DEFAULT_ADDRESS
|
||||||
|
from habluetooth import scanner
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import bluetooth
|
from homeassistant.components import bluetooth
|
||||||
|
@ -17,7 +18,6 @@ from homeassistant.components.bluetooth import (
|
||||||
async_process_advertisements,
|
async_process_advertisements,
|
||||||
async_rediscover_address,
|
async_rediscover_address,
|
||||||
async_track_unavailable,
|
async_track_unavailable,
|
||||||
scanner,
|
|
||||||
)
|
)
|
||||||
from homeassistant.components.bluetooth.const import (
|
from homeassistant.components.bluetooth.const import (
|
||||||
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
|
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
|
||||||
|
@ -107,7 +107,7 @@ async def test_setup_and_stop_passive(
|
||||||
"""Register a callback."""
|
"""Register a callback."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
MockPassiveBleakScanner,
|
MockPassiveBleakScanner,
|
||||||
):
|
):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
@ -158,7 +158,7 @@ async def test_setup_and_stop_old_bluez(
|
||||||
"""Register a callback."""
|
"""Register a callback."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
MockBleakScanner,
|
MockBleakScanner,
|
||||||
):
|
):
|
||||||
assert await async_setup_component(
|
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"}
|
{"domain": "switchbot", "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b"}
|
||||||
]
|
]
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
side_effect=BleakError,
|
side_effect=BleakError,
|
||||||
) as mock_ha_bleak_scanner, patch(
|
) as mock_ha_bleak_scanner, patch(
|
||||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
"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."""
|
"""Test we fail gracefully when bluetooth/dbus is broken."""
|
||||||
mock_bt = []
|
mock_bt = []
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
side_effect=BleakError,
|
side_effect=BleakError,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
"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)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
with patch.object(scanner, "START_TIMEOUT", 0), patch(
|
with patch.object(scanner, "START_TIMEOUT", 0), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
side_effect=_mock_hang,
|
side_effect=_mock_hang,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
"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."""
|
"""Test we retry if the adapter is not yet available."""
|
||||||
mock_bt = []
|
mock_bt = []
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
side_effect=BleakError,
|
side_effect=BleakError,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
"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
|
assert entry.state == ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
):
|
):
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert entry.state == ConfigEntryState.LOADED
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.stop",
|
"habluetooth.scanner.OriginalBleakScanner.stop",
|
||||||
):
|
):
|
||||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||||
await hass.async_block_till_done()
|
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."""
|
"""Test we can successfully reload when the entry is in a retry state."""
|
||||||
mock_bt = []
|
mock_bt = []
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
side_effect=BleakError,
|
side_effect=BleakError,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
"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
|
assert entry.state == ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
):
|
):
|
||||||
await hass.config_entries.async_reload(entry.entry_id)
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
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
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.stop",
|
"habluetooth.scanner.OriginalBleakScanner.stop",
|
||||||
):
|
):
|
||||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||||
await hass.async_block_till_done()
|
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."""
|
"""Test we fail gracefully when asking for discovered devices and there is no blueooth."""
|
||||||
mock_bt = []
|
mock_bt = []
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
side_effect=FileNotFoundError,
|
side_effect=FileNotFoundError,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
|
"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_INTERVAL,
|
||||||
SCANNER_WATCHDOG_TIMEOUT,
|
SCANNER_WATCHDOG_TIMEOUT,
|
||||||
)
|
)
|
||||||
from homeassistant.components.bluetooth.scanner import NEED_RESET_ERRORS
|
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -30,6 +29,14 @@ from . import (
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
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(
|
async def test_config_entry_can_be_reloaded_when_stop_raises(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -42,7 +49,7 @@ async def test_config_entry_can_be_reloaded_when_stop_raises(
|
||||||
assert entry.state == ConfigEntryState.LOADED
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.stop",
|
"habluetooth.scanner.OriginalBleakScanner.stop",
|
||||||
side_effect=BleakError,
|
side_effect=BleakError,
|
||||||
):
|
):
|
||||||
await hass.config_entries.async_reload(entry.entry_id)
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
@ -57,10 +64,8 @@ async def test_dbus_socket_missing_in_container(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test we handle dbus being missing in the container."""
|
"""Test we handle dbus being missing in the container."""
|
||||||
|
|
||||||
with patch(
|
with patch("habluetooth.scanner.is_docker_env", return_value=True), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.is_docker_env", return_value=True
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
), patch(
|
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
|
||||||
side_effect=FileNotFoundError,
|
side_effect=FileNotFoundError,
|
||||||
):
|
):
|
||||||
await async_setup_with_one_adapter(hass)
|
await async_setup_with_one_adapter(hass)
|
||||||
|
@ -79,10 +84,8 @@ async def test_dbus_socket_missing(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test we handle dbus being missing."""
|
"""Test we handle dbus being missing."""
|
||||||
|
|
||||||
with patch(
|
with patch("habluetooth.scanner.is_docker_env", return_value=False), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.is_docker_env", return_value=False
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
), patch(
|
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
|
||||||
side_effect=FileNotFoundError,
|
side_effect=FileNotFoundError,
|
||||||
):
|
):
|
||||||
await async_setup_with_one_adapter(hass)
|
await async_setup_with_one_adapter(hass)
|
||||||
|
@ -101,10 +104,8 @@ async def test_dbus_broken_pipe_in_container(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test we handle dbus broken pipe in the container."""
|
"""Test we handle dbus broken pipe in the container."""
|
||||||
|
|
||||||
with patch(
|
with patch("habluetooth.scanner.is_docker_env", return_value=True), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.is_docker_env", return_value=True
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
), patch(
|
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
|
||||||
side_effect=BrokenPipeError,
|
side_effect=BrokenPipeError,
|
||||||
):
|
):
|
||||||
await async_setup_with_one_adapter(hass)
|
await async_setup_with_one_adapter(hass)
|
||||||
|
@ -124,10 +125,8 @@ async def test_dbus_broken_pipe(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test we handle dbus broken pipe."""
|
"""Test we handle dbus broken pipe."""
|
||||||
|
|
||||||
with patch(
|
with patch("habluetooth.scanner.is_docker_env", return_value=False), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.is_docker_env", return_value=False
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
), patch(
|
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
|
||||||
side_effect=BrokenPipeError,
|
side_effect=BrokenPipeError,
|
||||||
):
|
):
|
||||||
await async_setup_with_one_adapter(hass)
|
await async_setup_with_one_adapter(hass)
|
||||||
|
@ -148,7 +147,7 @@ async def test_invalid_dbus_message(
|
||||||
"""Test we handle invalid dbus message."""
|
"""Test we handle invalid dbus message."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
side_effect=InvalidMessageError,
|
side_effect=InvalidMessageError,
|
||||||
):
|
):
|
||||||
await async_setup_with_one_adapter(hass)
|
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."""
|
"""Test we cycle the adapter when it needs a restart."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
side_effect=[BleakError(error), None],
|
side_effect=[BleakError(error), None],
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
"habluetooth.util.recover_adapter", return_value=True
|
||||||
) as mock_recover_adapter:
|
) as mock_recover_adapter:
|
||||||
await async_setup_with_one_adapter(hass)
|
await async_setup_with_one_adapter(hass)
|
||||||
|
|
||||||
|
@ -216,7 +215,7 @@ async def test_recovery_from_dbus_restart(
|
||||||
return mock_discovered
|
return mock_discovered
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
MockBleakScanner,
|
MockBleakScanner,
|
||||||
):
|
):
|
||||||
await async_setup_with_one_adapter(hass)
|
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",
|
"habluetooth.base_scanner.MONOTONIC_TIME",
|
||||||
return_value=start_time_monotonic,
|
return_value=start_time_monotonic,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
return_value=scanner,
|
return_value=scanner,
|
||||||
):
|
):
|
||||||
await async_setup_with_one_adapter(hass)
|
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_TIMEOUT
|
||||||
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
|
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
"habluetooth.util.recover_adapter", return_value=True
|
||||||
) as mock_recover_adapter:
|
) as mock_recover_adapter:
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
|
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
|
||||||
await hass.async_block_till_done()
|
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",
|
"habluetooth.base_scanner.MONOTONIC_TIME",
|
||||||
return_value=start_time_monotonic,
|
return_value=start_time_monotonic,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
return_value=scanner,
|
return_value=scanner,
|
||||||
):
|
):
|
||||||
await async_setup_with_one_adapter(hass)
|
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_TIMEOUT
|
||||||
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
|
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
"habluetooth.util.recover_adapter", return_value=True
|
||||||
) as mock_recover_adapter:
|
) as mock_recover_adapter:
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
|
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
|
||||||
await hass.async_block_till_done()
|
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_TIMEOUT
|
||||||
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
|
+ SCANNER_WATCHDOG_INTERVAL.total_seconds(),
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
"habluetooth.util.recover_adapter", return_value=True
|
||||||
) as mock_recover_adapter:
|
) as mock_recover_adapter:
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
|
async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
|
||||||
await hass.async_block_till_done()
|
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()
|
start_time_monotonic = time.monotonic()
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.ADAPTER_INIT_TIME",
|
"habluetooth.scanner.ADAPTER_INIT_TIME",
|
||||||
0,
|
0,
|
||||||
), patch(
|
), patch(
|
||||||
"habluetooth.base_scanner.MONOTONIC_TIME",
|
"habluetooth.base_scanner.MONOTONIC_TIME",
|
||||||
return_value=start_time_monotonic,
|
return_value=start_time_monotonic,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
return_value=scanner,
|
return_value=scanner,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
"habluetooth.util.recover_adapter", return_value=True
|
||||||
) as mock_recover_adapter:
|
) as mock_recover_adapter:
|
||||||
await async_setup_with_one_adapter(hass)
|
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()
|
start_time_monotonic = time.monotonic()
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.ADAPTER_INIT_TIME",
|
"habluetooth.scanner.ADAPTER_INIT_TIME",
|
||||||
0,
|
0,
|
||||||
), patch(
|
), patch(
|
||||||
"habluetooth.base_scanner.MONOTONIC_TIME",
|
"habluetooth.base_scanner.MONOTONIC_TIME",
|
||||||
return_value=start_time_monotonic,
|
return_value=start_time_monotonic,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
return_value=scanner,
|
return_value=scanner,
|
||||||
), patch(
|
), patch("habluetooth.util.recover_adapter", return_value=True):
|
||||||
"homeassistant.components.bluetooth.util.recover_adapter", return_value=True
|
|
||||||
):
|
|
||||||
await async_setup_with_one_adapter(hass)
|
await async_setup_with_one_adapter(hass)
|
||||||
|
|
||||||
assert called_start == 1
|
assert called_start == 1
|
||||||
|
@ -617,7 +614,7 @@ async def test_setup_and_stop_macos(
|
||||||
"""Register a callback."""
|
"""Register a callback."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
|
"habluetooth.scanner.OriginalBleakScanner",
|
||||||
MockBleakScanner,
|
MockBleakScanner,
|
||||||
):
|
):
|
||||||
assert await async_setup_component(
|
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
|
# Late imports to avoid loading bleak unless we need it
|
||||||
|
|
||||||
# pylint: disable-next=import-outside-toplevel
|
# 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
|
# 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
|
# out start and this fixture will expire before the stop method is called
|
||||||
# when EVENT_HOMEASSISTANT_STOP is fired.
|
# when EVENT_HOMEASSISTANT_STOP is fired.
|
||||||
bluetooth_scanner.OriginalBleakScanner.stop = AsyncMock() # type: ignore[assignment]
|
bluetooth_scanner.OriginalBleakScanner.stop = AsyncMock() # type: ignore[assignment]
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.bluetooth.scanner.OriginalBleakScanner.start",
|
"habluetooth.scanner.OriginalBleakScanner.start",
|
||||||
) as mock_bleak_scanner_start:
|
) as mock_bleak_scanner_start:
|
||||||
yield mock_bleak_scanner_start
|
yield mock_bleak_scanner_start
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue