From 085abc74ee7dab777149d8937cffc8aaa6883880 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 16 Sep 2022 12:24:20 +0200 Subject: [PATCH] Reduce overhead to update passive bluetooth devices (#78545) --- .../components/bluetooth/__init__.py | 12 +- homeassistant/components/bluetooth/manager.py | 26 ++- .../bluetooth/passive_update_coordinator.py | 7 +- .../bluetooth/passive_update_processor.py | 6 +- .../bluetooth/update_coordinator.py | 60 +++-- homeassistant/components/yalexs_ble/entity.py | 4 +- tests/components/bluemaestro/test_sensor.py | 20 +- tests/components/bluetooth/__init__.py | 67 +++++- .../test_active_update_coordinator.py | 115 ++-------- .../test_passive_update_coordinator.py | 72 ++---- .../test_passive_update_processor.py | 205 +++++++----------- tests/components/bthome/test_binary_sensor.py | 22 +- tests/components/bthome/test_sensor.py | 22 +- tests/components/govee_ble/test_sensor.py | 20 +- tests/components/inkbird/test_sensor.py | 20 +- tests/components/moat/test_sensor.py | 22 +- .../components/qingping/test_binary_sensor.py | 20 +- tests/components/qingping/test_sensor.py | 20 +- tests/components/sensorpro/test_sensor.py | 20 +- tests/components/sensorpush/test_sensor.py | 25 +-- tests/components/thermobeacon/test_sensor.py | 20 +- tests/components/thermopro/test_sensor.py | 23 +- tests/components/tilt_ble/test_sensor.py | 24 +- .../xiaomi_ble/test_binary_sensor.py | 42 +--- tests/components/xiaomi_ble/test_sensor.py | 179 +++++---------- 25 files changed, 364 insertions(+), 709 deletions(-) diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index 8ba3a503a30..2132e1c8b66 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -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]: diff --git a/homeassistant/components/bluetooth/manager.py b/homeassistant/components/bluetooth/manager.py index 672ef91542e..47941c8d5c1 100644 --- a/homeassistant/components/bluetooth/manager.py +++ b/homeassistant/components/bluetooth/manager.py @@ -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 diff --git a/homeassistant/components/bluetooth/passive_update_coordinator.py b/homeassistant/components/bluetooth/passive_update_coordinator.py index 296e49e2fa0..1eae49a6cab 100644 --- a/homeassistant/components/bluetooth/passive_update_coordinator.py +++ b/homeassistant/components/bluetooth/passive_update_coordinator.py @@ -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() diff --git a/homeassistant/components/bluetooth/passive_update_processor.py b/homeassistant/components/bluetooth/passive_update_processor.py index 5ea2f3f0742..b04447cc4ee 100644 --- a/homeassistant/components/bluetooth/passive_update_processor.py +++ b/homeassistant/components/bluetooth/passive_update_processor.py @@ -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() diff --git a/homeassistant/components/bluetooth/update_coordinator.py b/homeassistant/components/bluetooth/update_coordinator.py index 9348095f2b1..2c99f189852 100644 --- a/homeassistant/components/bluetooth/update_coordinator.py +++ b/homeassistant/components/bluetooth/update_coordinator.py @@ -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 diff --git a/homeassistant/components/yalexs_ble/entity.py b/homeassistant/components/yalexs_ble/entity.py index fa80698831b..d2395f3e39e 100644 --- a/homeassistant/components/yalexs_ble/entity.py +++ b/homeassistant/components/yalexs_ble/entity.py @@ -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() diff --git a/tests/components/bluemaestro/test_sensor.py b/tests/components/bluemaestro/test_sensor.py index 2f964e65481..e1c7b27673c 100644 --- a/tests/components/bluemaestro/test_sensor.py +++ b/tests/components/bluemaestro/test_sensor.py @@ -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 diff --git a/tests/components/bluetooth/__init__.py b/tests/components/bluetooth/__init__.py index 7c559e00adc..a836740bb9b 100644 --- a/tests/components/bluetooth/__init__.py +++ b/tests/components/bluetooth/__init__.py @@ -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( diff --git a/tests/components/bluetooth/test_active_update_coordinator.py b/tests/components/bluetooth/test_active_update_coordinator.py index 7677584e890..ecec5f12171 100644 --- a/tests/components/bluetooth/test_active_update_coordinator.py +++ b/tests/components/bluetooth/test_active_update_coordinator.py @@ -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}) diff --git a/tests/components/bluetooth/test_passive_update_coordinator.py b/tests/components/bluetooth/test_passive_update_coordinator.py index 2335bf51485..bf7c0a48467 100644 --- a/tests/components/bluetooth/test_passive_update_coordinator.py +++ b/tests/components/bluetooth/test_passive_update_coordinator.py @@ -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() diff --git a/tests/components/bluetooth/test_passive_update_processor.py b/tests/components/bluetooth/test_passive_update_processor.py index 2ae6f77b28d..99b16131ddc 100644 --- a/tests/components/bluetooth/test_passive_update_processor.py +++ b/tests/components/bluetooth/test_passive_update_processor.py @@ -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() diff --git a/tests/components/bthome/test_binary_sensor.py b/tests/components/bthome/test_binary_sensor.py index fc7b128d124..64b19b17a81 100644 --- a/tests/components/bthome/test_binary_sensor.py +++ b/tests/components/bthome/test_binary_sensor.py @@ -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() diff --git a/tests/components/bthome/test_sensor.py b/tests/components/bthome/test_sensor.py index bb0c5b3f459..78b247aa393 100644 --- a/tests/components/bthome/test_sensor.py +++ b/tests/components/bthome/test_sensor.py @@ -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) diff --git a/tests/components/govee_ble/test_sensor.py b/tests/components/govee_ble/test_sensor.py index e7828fdc496..0e52d2278c3 100644 --- a/tests/components/govee_ble/test_sensor.py +++ b/tests/components/govee_ble/test_sensor.py @@ -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 diff --git a/tests/components/inkbird/test_sensor.py b/tests/components/inkbird/test_sensor.py index c54c6e3c242..38ff15b6bb1 100644 --- a/tests/components/inkbird/test_sensor.py +++ b/tests/components/inkbird/test_sensor.py @@ -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 diff --git a/tests/components/moat/test_sensor.py b/tests/components/moat/test_sensor.py index 6424144106b..45b9dbc0e8a 100644 --- a/tests/components/moat/test_sensor.py +++ b/tests/components/moat/test_sensor.py @@ -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 diff --git a/tests/components/qingping/test_binary_sensor.py b/tests/components/qingping/test_binary_sensor.py index 54e863cd158..fc7f79ae8d9 100644 --- a/tests/components/qingping/test_binary_sensor.py +++ b/tests/components/qingping/test_binary_sensor.py @@ -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 diff --git a/tests/components/qingping/test_sensor.py b/tests/components/qingping/test_sensor.py index 0f7e7c6a58e..135844c0728 100644 --- a/tests/components/qingping/test_sensor.py +++ b/tests/components/qingping/test_sensor.py @@ -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 diff --git a/tests/components/sensorpro/test_sensor.py b/tests/components/sensorpro/test_sensor.py index 0d27d07995f..242c845a9ce 100644 --- a/tests/components/sensorpro/test_sensor.py +++ b/tests/components/sensorpro/test_sensor.py @@ -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 diff --git a/tests/components/sensorpush/test_sensor.py b/tests/components/sensorpush/test_sensor.py index 34179985d78..ae3cbe047de 100644 --- a/tests/components/sensorpush/test_sensor.py +++ b/tests/components/sensorpush/test_sensor.py @@ -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 diff --git a/tests/components/thermobeacon/test_sensor.py b/tests/components/thermobeacon/test_sensor.py index 147f37787b8..69619c94bbe 100644 --- a/tests/components/thermobeacon/test_sensor.py +++ b/tests/components/thermobeacon/test_sensor.py @@ -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 diff --git a/tests/components/thermopro/test_sensor.py b/tests/components/thermopro/test_sensor.py index 908101faf56..34b2b1a5aeb 100644 --- a/tests/components/thermopro/test_sensor.py +++ b/tests/components/thermopro/test_sensor.py @@ -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 diff --git a/tests/components/tilt_ble/test_sensor.py b/tests/components/tilt_ble/test_sensor.py index cfd83cb6573..28454034864 100644 --- a/tests/components/tilt_ble/test_sensor.py +++ b/tests/components/tilt_ble/test_sensor.py @@ -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 diff --git a/tests/components/xiaomi_ble/test_binary_sensor.py b/tests/components/xiaomi_ble/test_binary_sensor.py index dd49b4d181d..eb369f20268 100644 --- a/tests/components/xiaomi_ble/test_binary_sensor.py +++ b/tests/components/xiaomi_ble/test_binary_sensor.py @@ -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() diff --git a/tests/components/xiaomi_ble/test_sensor.py b/tests/components/xiaomi_ble/test_sensor.py index 25c118bed49..17f9254b5ff 100644 --- a/tests/components/xiaomi_ble/test_sensor.py +++ b/tests/components/xiaomi_ble/test_sensor.py @@ -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