Reduce overhead to update passive bluetooth devices (#78545)

This commit is contained in:
J. Nick Koston 2022-09-16 12:24:20 +02:00 committed by GitHub
parent b093c2840b
commit 085abc74ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 364 additions and 709 deletions

View file

@ -103,6 +103,16 @@ def async_discovered_service_info(
return _get_manager(hass).async_discovered_service_info(connectable)
@hass_callback
def async_last_service_info(
hass: HomeAssistant, address: str, connectable: bool = True
) -> BluetoothServiceInfoBleak | None:
"""Return the last service info for an address."""
if DATA_MANAGER not in hass.data:
return None
return _get_manager(hass).async_last_service_info(address, connectable)
@hass_callback
def async_ble_device_from_address(
hass: HomeAssistant, address: str, connectable: bool = True
@ -173,7 +183,7 @@ async def async_process_advertisements(
@hass_callback
def async_track_unavailable(
hass: HomeAssistant,
callback: Callable[[str], None],
callback: Callable[[BluetoothServiceInfoBleak], None],
address: str,
connectable: bool = True,
) -> Callable[[], None]:

View file

@ -143,9 +143,11 @@ class BluetoothManager:
self.hass = hass
self._integration_matcher = integration_matcher
self._cancel_unavailable_tracking: list[CALLBACK_TYPE] = []
self._unavailable_callbacks: dict[str, list[Callable[[str], None]]] = {}
self._unavailable_callbacks: dict[
str, list[Callable[[BluetoothServiceInfoBleak], None]]
] = {}
self._connectable_unavailable_callbacks: dict[
str, list[Callable[[str], None]]
str, list[Callable[[BluetoothServiceInfoBleak], None]]
] = {}
self._callback_index = BluetoothCallbackMatcherIndex()
self._bleak_callbacks: list[
@ -269,12 +271,12 @@ class BluetoothManager:
}
disappeared = history_set.difference(active_addresses)
for address in disappeared:
del history[address]
service_info = history.pop(address)
if not (callbacks := unavailable_callbacks.get(address)):
continue
for callback in callbacks:
try:
callback(address)
callback(service_info)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error in unavailable callback")
@ -358,7 +360,10 @@ class BluetoothManager:
@hass_callback
def async_track_unavailable(
self, callback: Callable[[str], None], address: str, connectable: bool
self,
callback: Callable[[BluetoothServiceInfoBleak], None],
address: str,
connectable: bool,
) -> Callable[[], None]:
"""Register a callback."""
unavailable_callbacks = self._get_unavailable_callbacks_by_type(connectable)
@ -430,9 +435,16 @@ class BluetoothManager:
def async_discovered_service_info(
self, connectable: bool
) -> Iterable[BluetoothServiceInfoBleak]:
"""Return if the address is present."""
"""Return all the discovered services info."""
return self._get_history_by_type(connectable).values()
@hass_callback
def async_last_service_info(
self, address: str, connectable: bool
) -> BluetoothServiceInfoBleak | None:
"""Return the last service info for an address."""
return self._get_history_by_type(connectable).get(address)
@hass_callback
def async_rediscover_address(self, address: str) -> None:
"""Trigger discovery of devices which have already been seen."""
@ -448,7 +460,7 @@ class BluetoothManager:
def _get_unavailable_callbacks_by_type(
self, connectable: bool
) -> dict[str, list[Callable[[str], None]]]:
) -> dict[str, list[Callable[[BluetoothServiceInfoBleak], None]]]:
"""Return the unavailable callbacks by type."""
return (
self._connectable_unavailable_callbacks

View file

@ -41,9 +41,11 @@ class PassiveBluetoothDataUpdateCoordinator(BasePassiveBluetoothCoordinator):
update_callback()
@callback
def _async_handle_unavailable(self, address: str) -> None:
def _async_handle_unavailable(
self, service_info: BluetoothServiceInfoBleak
) -> None:
"""Handle the device going unavailable."""
super()._async_handle_unavailable(address)
super()._async_handle_unavailable(service_info)
self.async_update_listeners()
@callback
@ -73,7 +75,6 @@ class PassiveBluetoothDataUpdateCoordinator(BasePassiveBluetoothCoordinator):
change: BluetoothChange,
) -> None:
"""Handle a Bluetooth event."""
super()._async_handle_bluetooth_event(service_info, change)
self.async_update_listeners()

View file

@ -101,9 +101,11 @@ class PassiveBluetoothProcessorCoordinator(
return remove_processor
@callback
def _async_handle_unavailable(self, address: str) -> None:
def _async_handle_unavailable(
self, service_info: BluetoothServiceInfoBleak
) -> None:
"""Handle the device going unavailable."""
super()._async_handle_unavailable(address)
super()._async_handle_unavailable(service_info)
for processor in self._processors:
processor.async_handle_unavailable()

View file

@ -1,8 +1,8 @@
"""Update coordinator for the Bluetooth integration."""
from __future__ import annotations
from abc import abstractmethod
import logging
import time
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
@ -11,6 +11,8 @@ from . import (
BluetoothChange,
BluetoothScanningMode,
BluetoothServiceInfoBleak,
async_address_present,
async_last_service_info,
async_register_callback,
async_track_unavailable,
)
@ -33,14 +35,13 @@ class BasePassiveBluetoothCoordinator:
"""Initialize the coordinator."""
self.hass = hass
self.logger = logger
self.name: str | None = None
self.address = address
self.connectable = connectable
self._cancel_track_unavailable: CALLBACK_TYPE | None = None
self._cancel_bluetooth_advertisements: CALLBACK_TYPE | None = None
self._present = False
self.mode = mode
self.last_seen = 0.0
self._last_unavailable_time = 0.0
self._last_name = address
@callback
def async_start(self) -> CALLBACK_TYPE:
@ -53,10 +54,41 @@ class BasePassiveBluetoothCoordinator:
return _async_cancel
@callback
@abstractmethod
def _async_handle_bluetooth_event(
self,
service_info: BluetoothServiceInfoBleak,
change: BluetoothChange,
) -> None:
"""Handle a bluetooth event."""
@property
def name(self) -> str:
"""Return last known name of the device."""
if service_info := async_last_service_info(
self.hass, self.address, self.connectable
):
return service_info.name
return self._last_name
@property
def last_seen(self) -> float:
"""Return the last time the device was seen."""
# If the device is unavailable it will not have a service
# info and fall through below.
if service_info := async_last_service_info(
self.hass, self.address, self.connectable
):
return service_info.time
# This is the time from the last advertisement that
# was set when the unavailable callback was called.
return self._last_unavailable_time
@property
def available(self) -> bool:
"""Return if the device is available."""
return self._present
return async_address_present(self.hass, self.address, self.connectable)
@callback
def _async_start(self) -> None:
@ -84,17 +116,9 @@ class BasePassiveBluetoothCoordinator:
self._cancel_track_unavailable = None
@callback
def _async_handle_unavailable(self, address: str) -> None:
"""Handle the device going unavailable."""
self._present = False
@callback
def _async_handle_bluetooth_event(
self,
service_info: BluetoothServiceInfoBleak,
change: BluetoothChange,
def _async_handle_unavailable(
self, service_info: BluetoothServiceInfoBleak
) -> None:
"""Handle a Bluetooth event."""
self.last_seen = time.monotonic()
self.name = service_info.name
self._present = True
"""Handle the device going unavailable."""
self._last_unavailable_time = service_info.time
self._last_name = service_info.name

View file

@ -56,7 +56,9 @@ class YALEXSBLEEntity(Entity):
self.async_write_ha_state()
@callback
def _async_device_unavailable(self, _address: str) -> None:
def _async_device_unavailable(
self, _service_info: bluetooth.BluetoothServiceInfoBleak
) -> None:
"""Handle device not longer being seen by the bluetooth stack."""
self._attr_available = False
self.async_write_ha_state()

View file

@ -1,15 +1,14 @@
"""Test the BlueMaestro sensors."""
from unittest.mock import patch
from homeassistant.components.bluemaestro.const import DOMAIN
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import BLUEMAESTRO_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 0
saved_callback(BLUEMAESTRO_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, BLUEMAESTRO_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 4

View file

@ -19,6 +19,16 @@ from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
__all__ = (
"inject_advertisement",
"inject_advertisement_with_source",
"inject_advertisement_with_time_and_source",
"inject_advertisement_with_time_and_source_connectable",
"inject_bluetooth_service_info",
"patch_all_discovered_devices",
"patch_discovered_devices",
)
def _get_manager() -> BluetoothManager:
"""Return the bluetooth manager."""
@ -80,6 +90,51 @@ def inject_advertisement_with_time_and_source_connectable(
)
def inject_bluetooth_service_info_bleak(
hass: HomeAssistant, info: models.BluetoothServiceInfoBleak
) -> None:
"""Inject an advertisement into the manager with connectable status."""
advertisement_data = AdvertisementData( # type: ignore[no-untyped-call]
local_name=None if info.name == "" else info.name,
manufacturer_data=info.manufacturer_data,
service_data=info.service_data,
service_uuids=info.service_uuids,
)
device = BLEDevice( # type: ignore[no-untyped-call]
address=info.address,
name=info.name,
details={},
rssi=info.rssi,
)
inject_advertisement_with_time_and_source_connectable(
hass,
device,
advertisement_data,
info.time,
SOURCE_LOCAL,
connectable=info.connectable,
)
def inject_bluetooth_service_info(
hass: HomeAssistant, info: models.BluetoothServiceInfo
) -> None:
"""Inject a BluetoothServiceInfo into the manager."""
advertisement_data = AdvertisementData( # type: ignore[no-untyped-call]
local_name=None if info.name == "" else info.name,
manufacturer_data=info.manufacturer_data,
service_data=info.service_data,
service_uuids=info.service_uuids,
)
device = BLEDevice( # type: ignore[no-untyped-call]
address=info.address,
name=info.name,
details={},
rssi=info.rssi,
)
inject_advertisement(hass, device, advertisement_data)
def patch_all_discovered_devices(mock_discovered: list[BLEDevice]) -> None:
"""Mock all the discovered devices from all the scanners."""
return patch.object(
@ -87,18 +142,6 @@ def patch_all_discovered_devices(mock_discovered: list[BLEDevice]) -> None:
)
def patch_history(mock_history: dict[str, models.BluetoothServiceInfoBleak]) -> None:
"""Patch the history."""
return patch.dict(_get_manager()._history, mock_history)
def patch_connectable_history(
mock_history: dict[str, models.BluetoothServiceInfoBleak]
) -> None:
"""Patch the connectable history."""
return patch.dict(_get_manager()._connectable_history, mock_history)
def patch_discovered_devices(mock_discovered: list[BLEDevice]) -> None:
"""Mock the combined best path to discovered devices from all the scanners."""
return patch.object(

View file

@ -3,13 +3,12 @@ from __future__ import annotations
import asyncio
import logging
from unittest.mock import MagicMock, call, patch
from unittest.mock import MagicMock, call
from bleak import BleakError
from homeassistant.components.bluetooth import (
DOMAIN,
BluetoothChange,
BluetoothScanningMode,
BluetoothServiceInfoBleak,
)
@ -21,6 +20,8 @@ from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from homeassistant.setup import async_setup_component
from tests.components.bluetooth import inject_bluetooth_service_info
_LOGGER = logging.getLogger(__name__)
@ -60,26 +61,14 @@ async def test_basic_usage(hass: HomeAssistant, mock_bleak_scanner_start):
poll_method=_poll,
)
assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock()
coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
cancel = coordinator.async_start()
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
cancel = coordinator.async_start()
assert saved_callback is not None
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert coordinator.available is True
@ -126,38 +115,26 @@ async def test_poll_can_be_skipped(hass: HomeAssistant, mock_bleak_scanner_start
),
)
assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock()
coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
cancel = coordinator.async_start()
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
cancel = coordinator.async_start()
assert saved_callback is not None
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": True})
flag = False
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": None})
flag = True
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": True})
@ -200,27 +177,15 @@ async def test_bleak_error_and_recover(
),
)
assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock()
coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
cancel = coordinator.async_start()
assert saved_callback is not None
cancel = coordinator.async_start()
# First poll fails
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": None})
@ -231,7 +196,7 @@ async def test_bleak_error_and_recover(
# Second poll works
flag = False
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": False})
@ -272,33 +237,21 @@ async def test_poll_failure_and_recover(hass: HomeAssistant, mock_bleak_scanner_
),
)
assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock()
coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
cancel = coordinator.async_start()
assert saved_callback is not None
cancel = coordinator.async_start()
# First poll fails
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": None})
# Second poll works
flag = False
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": False})
@ -334,29 +287,17 @@ async def test_second_poll_needed(hass: HomeAssistant, mock_bleak_scanner_start)
poll_method=_poll,
)
assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock()
coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
cancel = coordinator.async_start()
assert saved_callback is not None
cancel = coordinator.async_start()
# First poll gets queued
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Second poll gets stuck behind first poll
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": 1})
@ -392,31 +333,19 @@ async def test_rate_limit(hass: HomeAssistant, mock_bleak_scanner_start):
poll_method=_poll,
)
assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock()
coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
cancel = coordinator.async_start()
assert saved_callback is not None
cancel = coordinator.async_start()
# First poll gets queued
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Second poll gets stuck behind first poll
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Third poll gets stuck behind first poll doesn't get queued
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": 1})

View file

@ -20,9 +20,10 @@ from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from . import patch_all_discovered_devices, patch_history
from . import patch_all_discovered_devices
from tests.common import async_fire_time_changed
from tests.components.bluetooth import inject_bluetooth_service_info
_LOGGER = logging.getLogger(__name__)
@ -65,32 +66,20 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
mock_listener = MagicMock()
unregister_listener = coordinator.async_add_listener(mock_listener)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
cancel = coordinator.async_start()
cancel = coordinator.async_start()
assert saved_callback is not None
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert len(mock_listener.mock_calls) == 1
assert coordinator.data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi}
assert coordinator.available is True
unregister_listener()
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert len(mock_listener.mock_calls) == 1
assert coordinator.data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi}
@ -107,21 +96,11 @@ async def test_context_compatiblity_with_data_update_coordinator(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
mock_listener = MagicMock()
coordinator.async_add_listener(mock_listener)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
coordinator.async_start()
coordinator.async_start()
assert not set(coordinator.async_contexts())
@ -158,41 +137,27 @@ async def test_unavailable_callbacks_mark_the_coordinator_unavailable(
hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.PASSIVE
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
mock_listener = MagicMock()
coordinator.async_add_listener(mock_listener)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
coordinator.async_start()
coordinator.async_start()
assert coordinator.available is False
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert coordinator.available is True
with patch_all_discovered_devices(
[MagicMock(address="44:44:33:11:23:45")]
), patch_history({"aa:bb:cc:dd:ee:ff": MagicMock()}):
with patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]):
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS)
)
await hass.async_block_till_done()
assert coordinator.available is False
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert coordinator.available is True
with patch_all_discovered_devices(
[MagicMock(address="44:44:33:11:23:45")]
), patch_history({"aa:bb:cc:dd:ee:ff": MagicMock()}):
with patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]):
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS)
)
@ -209,21 +174,10 @@ async def test_passive_bluetooth_coordinator_entity(hass, mock_bleak_scanner_sta
entity = PassiveBluetoothCoordinatorEntity(coordinator)
assert entity.available is False
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
coordinator.async_start()
coordinator.async_start()
assert coordinator.available is False
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert coordinator.available is True
entity.hass = hass
await entity.async_update()

View file

@ -3,6 +3,7 @@ from __future__ import annotations
from datetime import timedelta
import logging
import time
from unittest.mock import MagicMock, patch
from home_assistant_bluetooth import BluetoothServiceInfo
@ -16,6 +17,7 @@ from homeassistant.components.bluetooth import (
DOMAIN,
BluetoothChange,
BluetoothScanningMode,
BluetoothServiceInfoBleak,
)
from homeassistant.components.bluetooth.const import UNAVAILABLE_TRACK_SECONDS
from homeassistant.components.bluetooth.passive_update_processor import (
@ -32,9 +34,13 @@ from homeassistant.helpers.entity import DeviceInfo
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from . import patch_all_discovered_devices, patch_connectable_history, patch_history
from . import patch_all_discovered_devices
from tests.common import MockEntityPlatform, async_fire_time_changed
from tests.components.bluetooth import (
inject_bluetooth_service_info,
inject_bluetooth_service_info_bleak,
)
_LOGGER = logging.getLogger(__name__)
@ -50,6 +56,7 @@ GENERIC_BLUETOOTH_SERVICE_INFO = BluetoothServiceInfo(
service_uuids=[],
source="local",
)
GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE = PassiveBluetoothDataUpdate(
devices={
None: DeviceInfo(
@ -105,21 +112,11 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
_mock_update_method,
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
entity_key = PassiveBluetoothEntityKey("temperature", None)
entity_key_events = []
@ -149,7 +146,7 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
mock_add_entities,
)
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Each listener should receive the same data
# since both match
@ -159,7 +156,7 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
# There should be 4 calls to create entities
assert len(mock_entity.mock_calls) == 2
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Each listener should receive the same data
# since both match
@ -174,7 +171,7 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
cancel_listener()
cancel_async_add_entities_listener()
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Each listener should not trigger any more now
# that they were cancelled
@ -217,20 +214,11 @@ async def test_unavailable_after_no_data(hass, mock_bleak_scanner_start):
_mock_update_method,
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
mock_entity = MagicMock()
mock_add_entities = MagicMock()
@ -242,38 +230,49 @@ async def test_unavailable_after_no_data(hass, mock_bleak_scanner_start):
assert coordinator.available is False
assert processor.available is False
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
now = time.monotonic()
service_info_at_time = BluetoothServiceInfoBleak(
name="Generic",
address="aa:bb:cc:dd:ee:ff",
rssi=-95,
manufacturer_data={
1: b"\x01\x01\x01\x01\x01\x01\x01\x01",
},
service_data={},
service_uuids=[],
source="local",
time=now,
device=MagicMock(),
advertisement=MagicMock(),
connectable=True,
)
inject_bluetooth_service_info_bleak(hass, service_info_at_time)
assert len(mock_add_entities.mock_calls) == 1
assert coordinator.available is True
assert processor.available is True
with patch_all_discovered_devices(
[MagicMock(address="44:44:33:11:23:45")]
), patch_history({"aa:bb:cc:dd:ee:ff": MagicMock()}), patch_connectable_history(
{"aa:bb:cc:dd:ee:ff": MagicMock()},
):
with patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]):
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS)
)
await hass.async_block_till_done()
assert coordinator.available is False
assert processor.available is False
assert coordinator.last_seen == service_info_at_time.time
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info_bleak(hass, service_info_at_time)
assert len(mock_add_entities.mock_calls) == 1
assert coordinator.available is True
assert processor.available is True
with patch_all_discovered_devices(
[MagicMock(address="44:44:33:11:23:45")]
), patch_history({"aa:bb:cc:dd:ee:ff": MagicMock()}), patch_connectable_history(
{"aa:bb:cc:dd:ee:ff": MagicMock()},
):
with patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]):
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS)
)
await hass.async_block_till_done()
assert coordinator.available is False
assert processor.available is False
assert coordinator.last_seen == service_info_at_time.time
unregister_processor()
cancel_coordinator()
@ -304,21 +303,11 @@ async def test_no_updates_once_stopping(hass, mock_bleak_scanner_start):
_mock_update_method,
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
all_events = []
@ -330,13 +319,13 @@ async def test_no_updates_once_stopping(hass, mock_bleak_scanner_start):
_all_listener,
)
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert len(all_events) == 1
hass.state = CoreState.stopping
# We should stop processing events once hass is stopping
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert len(all_events) == 1
unregister_processor()
cancel_coordinator()
@ -361,7 +350,7 @@ async def test_exception_from_update_method(hass, caplog, mock_bleak_scanner_sta
"""Generate mock data."""
nonlocal run_count
run_count += 1
if run_count == 2:
if run_count == 1:
raise Exception("Test exception")
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE
@ -390,7 +379,7 @@ async def test_exception_from_update_method(hass, caplog, mock_bleak_scanner_sta
processor.async_add_listener(MagicMock())
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert processor.available is True
# We should go unavailable once we get an exception
@ -424,7 +413,7 @@ async def test_bad_data_from_update_method(hass, mock_bleak_scanner_start):
"""Generate mock data."""
nonlocal run_count
run_count += 1
if run_count == 2:
if run_count == 1:
return "bad_data"
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE
@ -453,7 +442,7 @@ async def test_bad_data_from_update_method(hass, mock_bleak_scanner_start):
processor.async_add_listener(MagicMock())
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert processor.available is True
# We should go unavailable once we get bad data
@ -788,20 +777,11 @@ async def test_integration_with_entity(hass, mock_bleak_scanner_start):
_mock_update_method,
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
processor.async_add_listener(MagicMock())
@ -812,19 +792,19 @@ async def test_integration_with_entity(hass, mock_bleak_scanner_start):
mock_add_entities,
)
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# First call with just the remote sensor entities results in them being added
assert len(mock_add_entities.mock_calls) == 1
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Second call with just the remote sensor entities does not add them again
assert len(mock_add_entities.mock_calls) == 1
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Third call with primary and remote sensor entities adds the primary sensor entities
assert len(mock_add_entities.mock_calls) == 2
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Forth call with both primary and remote sensor entities does not add them again
assert len(mock_add_entities.mock_calls) == 2
@ -908,20 +888,11 @@ async def test_integration_with_entity_without_a_device(hass, mock_bleak_scanner
_mock_update_method,
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
mock_add_entities = MagicMock()
@ -930,11 +901,11 @@ async def test_integration_with_entity_without_a_device(hass, mock_bleak_scanner
mock_add_entities,
)
saved_callback(NO_DEVICES_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, NO_DEVICES_BLUETOOTH_SERVICE_INFO)
# First call with just the remote sensor entities results in them being added
assert len(mock_add_entities.mock_calls) == 1
saved_callback(NO_DEVICES_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, NO_DEVICES_BLUETOOTH_SERVICE_INFO)
# Second call with just the remote sensor entities does not add them again
assert len(mock_add_entities.mock_calls) == 1
@ -982,20 +953,11 @@ async def test_passive_bluetooth_entity_with_entity_platform(
_mock_update_method,
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
processor.async_add_entities_listener(
PassiveBluetoothProcessorEntity,
@ -1003,9 +965,9 @@ async def test_passive_bluetooth_entity_with_entity_platform(
entity_platform.async_add_entities(entities)
),
)
saved_callback(NO_DEVICES_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, NO_DEVICES_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
saved_callback(NO_DEVICES_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, NO_DEVICES_BLUETOOTH_SERVICE_INFO)
await hass.async_block_till_done()
assert (
hass.states.get("test_domain.test_platform_aa_bb_cc_dd_ee_ff_temperature")
@ -1079,12 +1041,6 @@ async def test_integration_multiple_entity_platforms(hass, mock_bleak_scanner_st
_mock_update_method,
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
binary_sensor_processor = PassiveBluetoothDataProcessor(
lambda service_info: BINARY_SENSOR_PASSIVE_BLUETOOTH_DATA_UPDATE
@ -1093,13 +1049,9 @@ async def test_integration_multiple_entity_platforms(hass, mock_bleak_scanner_st
lambda service_info: SENSOR_PASSIVE_BLUETOOTH_DATA_UPDATE
)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
coordinator.async_register_processor(binary_sensor_processor)
coordinator.async_register_processor(sesnor_processor)
cancel_coordinator = coordinator.async_start()
coordinator.async_register_processor(binary_sensor_processor)
coordinator.async_register_processor(sesnor_processor)
cancel_coordinator = coordinator.async_start()
binary_sensor_processor.async_add_listener(MagicMock())
sesnor_processor.async_add_listener(MagicMock())
@ -1116,7 +1068,7 @@ async def test_integration_multiple_entity_platforms(hass, mock_bleak_scanner_st
mock_add_binary_sensor_entities,
)
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# First call with just the remote sensor entities results in them being added
assert len(mock_add_binary_sensor_entities.mock_calls) == 1
assert len(mock_add_sensor_entities.mock_calls) == 1
@ -1193,33 +1145,24 @@ async def test_exception_from_coordinator_update_method(
_mock_update_method,
)
assert coordinator.available is False # no data yet
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
processor.async_add_listener(MagicMock())
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert processor.available is True
# We should go unavailable once we get an exception
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert "Test exception" in caplog.text
assert processor.available is False
# We should go available again once we get data again
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert processor.available is True
unregister_processor()
cancel_coordinator()

View file

@ -1,17 +1,16 @@
"""Test BTHome binary sensors."""
import logging
from unittest.mock import patch
import pytest
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.bthome.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_OFF, STATE_ON
from . import make_advertisement
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
_LOGGER = logging.getLogger(__name__)
@ -81,25 +80,14 @@ async def test_binary_sensors(
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(
inject_bluetooth_service_info(
hass,
advertisement,
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()

View file

@ -1,10 +1,8 @@
"""Test the BTHome sensors."""
from unittest.mock import patch
import pytest
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.bthome.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -12,6 +10,7 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import make_advertisement, make_encrypted_advertisement
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
@pytest.mark.parametrize(
@ -340,25 +339,14 @@ async def test_sensors(
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(
inject_bluetooth_service_info(
hass,
advertisement,
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == len(result)

View file

@ -1,8 +1,6 @@
"""Test the Govee BLE sensors."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.govee_ble.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -10,6 +8,7 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import GVH5075_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(GVH5075_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, GVH5075_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 3

View file

@ -1,8 +1,6 @@
"""Test the INKBIRD config flow."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.inkbird.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -10,6 +8,7 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import SPS_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(SPS_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, SPS_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 3

View file

@ -1,8 +1,6 @@
"""Test the Moat sensors."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.moat.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -10,32 +8,22 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import MOAT_S2_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass):
"""Test setting up creates the sensors."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="61DE521B-F0BF-9F44-64D4-75BBE1738105",
unique_id="aa:bb:cc:dd:ee:ff",
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(MOAT_S2_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, MOAT_S2_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4

View file

@ -1,14 +1,13 @@
"""Test the Qingping binary sensors."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.qingping.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME
from . import LIGHT_AND_SIGNAL_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_binary_sensors(hass):
@ -19,22 +18,11 @@ async def test_binary_sensors(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all("binary_sensor")) == 0
saved_callback(LIGHT_AND_SIGNAL_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, LIGHT_AND_SIGNAL_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all("binary_sensor")) == 1

View file

@ -1,8 +1,6 @@
"""Test the Qingping sensors."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.qingping.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -10,6 +8,7 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import LIGHT_AND_SIGNAL_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 0
saved_callback(LIGHT_AND_SIGNAL_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, LIGHT_AND_SIGNAL_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 1

View file

@ -1,8 +1,6 @@
"""Test the SensorPro sensors."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.components.sensorpro.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -10,6 +8,7 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import SENSORPRO_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 0
saved_callback(SENSORPRO_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, SENSORPRO_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 4

View file

@ -1,8 +1,5 @@
"""Test the SensorPush config flow."""
"""Test the SensorPush sensors."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.components.sensorpush.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -10,32 +7,22 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import HTPWX_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass):
"""Test setting up creates the sensors."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="61DE521B-F0BF-9F44-64D4-75BBE1738105",
unique_id="4125DDBA-2774-4851-9889-6AADDD4CAC3D",
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(HTPWX_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, HTPWX_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 3

View file

@ -1,8 +1,6 @@
"""Test the ThermoBeacon sensors."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.components.thermobeacon.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -10,6 +8,7 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import THERMOBEACON_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 0
saved_callback(THERMOBEACON_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, THERMOBEACON_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 4

View file

@ -1,8 +1,5 @@
"""Test the ThermoPro config flow."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.components.thermopro.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -10,32 +7,22 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import TP357_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass):
"""Test setting up creates the sensors."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="61DE521B-F0BF-9F44-64D4-75BBE1738105",
unique_id="4125DDBA-2774-4851-9889-6AADDD4CAC3D",
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(TP357_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, TP357_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2

View file

@ -2,9 +2,6 @@
from __future__ import annotations
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothCallback, BluetoothChange
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.components.tilt_ble.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -13,33 +10,22 @@ from homeassistant.core import HomeAssistant
from . import TILT_GREEN_SERVICE_INFO
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass: HomeAssistant):
"""Test setting up creates the sensors."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="61DE521B-F0BF-9F44-64D4-75BBE1738105",
unique_id="F6:0F:28:F2:1F:CB",
)
entry.add_to_hass(hass)
saved_callback: BluetoothCallback | None = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
assert saved_callback is not None
saved_callback(TILT_GREEN_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info(hass, TILT_GREEN_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2

View file

@ -1,14 +1,12 @@
"""Test Xiaomi binary sensors."""
from unittest.mock import patch
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.xiaomi_ble.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME
from . import make_advertisement
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info_bleak
async def test_smoke_sensor(hass):
@ -20,27 +18,16 @@ async def test_smoke_sensor(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"54:EF:44:E3:9C:BC",
b"XY\x97\tf\xbc\x9c\xe3D\xefT\x01" b"\x08\x12\x05\x00\x00\x00q^\xbe\x90",
),
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
@ -62,29 +49,18 @@ async def test_moisture(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
# WARNING: This test data is synthetic, rather than captured from a real device
# obj type is 0x1014, payload len is 0x2 and payload is 0xf400
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A", b"q \x5d\x01iz>j\x8d|\xc4\r\x14\x10\x02\xf4\x00"
),
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()

View file

@ -1,11 +1,6 @@
"""Test the Xiaomi config flow."""
from unittest.mock import patch
from homeassistant.components.bluetooth import (
BluetoothChange,
async_get_advertisement_callback,
)
from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.components.xiaomi_ble.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
@ -13,6 +8,7 @@ from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import MMC_T201_1_SERVICE_INFO, make_advertisement
from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info_bleak
async def test_sensors(hass):
@ -23,22 +19,11 @@ async def test_sensors(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(MMC_T201_1_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
inject_bluetooth_service_info_bleak(hass, MMC_T201_1_SERVICE_INFO)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
@ -63,29 +48,18 @@ async def test_xiaomi_formaldeyhde(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
# WARNING: This test data is synthetic, rather than captured from a real device
# obj type is 0x1010, payload len is 0x2 and payload is 0xf400
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A", b"q \x5d\x01iz>j\x8d|\xc4\r\x10\x10\x02\xf4\x00"
),
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()
@ -110,29 +84,18 @@ async def test_xiaomi_consumable(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
# WARNING: This test data is synthetic, rather than captured from a real device
# obj type is 0x1310, payload len is 0x2 and payload is 0x6000
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A", b"q \x5d\x01iz>j\x8d|\xc4\r\x13\x10\x02\x60\x00"
),
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()
@ -157,29 +120,16 @@ async def test_xiaomi_battery_voltage(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
# WARNING: This test data is synthetic, rather than captured from a real device
# obj type is 0x0a10, payload len is 0x2 and payload is 0x6400
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A", b"q \x5d\x01iz>j\x8d|\xc4\r\x0a\x10\x02\x64\x00"
),
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()
@ -211,44 +161,33 @@ async def test_xiaomi_HHCCJCY01(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A", b"q \x98\x00fz>j\x8d|\xc4\r\x07\x10\x03\x00\x00\x00"
),
BluetoothChange.ADVERTISEMENT,
)
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A", b"q \x98\x00hz>j\x8d|\xc4\r\t\x10\x02W\x02"
),
BluetoothChange.ADVERTISEMENT,
)
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A", b"q \x98\x00Gz>j\x8d|\xc4\r\x08\x10\x01@"
),
BluetoothChange.ADVERTISEMENT,
)
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A", b"q \x98\x00iz>j\x8d|\xc4\r\x04\x10\x02\xf4\x00"
),
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 5
@ -296,56 +235,45 @@ async def test_xiaomi_HHCCJCY01_not_connectable(hass):
"""This device has multiple advertisements before all sensors are visible but not connectable."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="C4:7C:8D:6A:3E:7B",
unique_id="C4:7C:8D:6A:3E:7A",
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A",
b"q \x98\x00fz>j\x8d|\xc4\r\x07\x10\x03\x00\x00\x00",
connectable=False,
),
BluetoothChange.ADVERTISEMENT,
)
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A",
b"q \x98\x00hz>j\x8d|\xc4\r\t\x10\x02W\x02",
connectable=False,
),
BluetoothChange.ADVERTISEMENT,
)
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A",
b"q \x98\x00Gz>j\x8d|\xc4\r\x08\x10\x01@",
connectable=False,
),
BluetoothChange.ADVERTISEMENT,
)
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A",
b"q \x98\x00iz>j\x8d|\xc4\r\x04\x10\x02\xf4\x00",
connectable=False,
),
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4
@ -392,34 +320,36 @@ async def test_xiaomi_HHCCJCY01_only_some_sources_connectable(hass):
)
entry.add_to_hass(hass)
saved_callback = async_get_advertisement_callback(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A",
b"q \x98\x00fz>j\x8d|\xc4\r\x07\x10\x03\x00\x00\x00",
connectable=True,
),
)
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A",
b"q \x98\x00hz>j\x8d|\xc4\r\t\x10\x02W\x02",
connectable=False,
),
)
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A",
b"q \x98\x00Gz>j\x8d|\xc4\r\x08\x10\x01@",
connectable=False,
),
)
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"C4:7C:8D:6A:3E:7A",
b"q \x98\x00iz>j\x8d|\xc4\r\x04\x10\x02\xf4\x00",
@ -477,27 +407,16 @@ async def test_xiaomi_CGDK2(hass):
)
entry.add_to_hass(hass)
saved_callback = None
def _async_register_callback(_hass, _callback, _matcher, _mode):
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
saved_callback(
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
"58:2D:34:12:20:89",
b"XXo\x06\x07\x89 \x124-X_\x17m\xd5O\x02\x00\x00/\xa4S\xfa",
),
BluetoothChange.ADVERTISEMENT,
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1