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) 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 @hass_callback
def async_ble_device_from_address( def async_ble_device_from_address(
hass: HomeAssistant, address: str, connectable: bool = True hass: HomeAssistant, address: str, connectable: bool = True
@ -173,7 +183,7 @@ async def async_process_advertisements(
@hass_callback @hass_callback
def async_track_unavailable( def async_track_unavailable(
hass: HomeAssistant, hass: HomeAssistant,
callback: Callable[[str], None], callback: Callable[[BluetoothServiceInfoBleak], None],
address: str, address: str,
connectable: bool = True, connectable: bool = True,
) -> Callable[[], None]: ) -> Callable[[], None]:

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
"""Update coordinator for the Bluetooth integration.""" """Update coordinator for the Bluetooth integration."""
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod
import logging import logging
import time
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
@ -11,6 +11,8 @@ from . import (
BluetoothChange, BluetoothChange,
BluetoothScanningMode, BluetoothScanningMode,
BluetoothServiceInfoBleak, BluetoothServiceInfoBleak,
async_address_present,
async_last_service_info,
async_register_callback, async_register_callback,
async_track_unavailable, async_track_unavailable,
) )
@ -33,14 +35,13 @@ class BasePassiveBluetoothCoordinator:
"""Initialize the coordinator.""" """Initialize the coordinator."""
self.hass = hass self.hass = hass
self.logger = logger self.logger = logger
self.name: str | None = None
self.address = address self.address = address
self.connectable = connectable self.connectable = connectable
self._cancel_track_unavailable: CALLBACK_TYPE | None = None self._cancel_track_unavailable: CALLBACK_TYPE | None = None
self._cancel_bluetooth_advertisements: CALLBACK_TYPE | None = None self._cancel_bluetooth_advertisements: CALLBACK_TYPE | None = None
self._present = False
self.mode = mode self.mode = mode
self.last_seen = 0.0 self._last_unavailable_time = 0.0
self._last_name = address
@callback @callback
def async_start(self) -> CALLBACK_TYPE: def async_start(self) -> CALLBACK_TYPE:
@ -53,10 +54,41 @@ class BasePassiveBluetoothCoordinator:
return _async_cancel 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 @property
def available(self) -> bool: def available(self) -> bool:
"""Return if the device is available.""" """Return if the device is available."""
return self._present return async_address_present(self.hass, self.address, self.connectable)
@callback @callback
def _async_start(self) -> None: def _async_start(self) -> None:
@ -84,17 +116,9 @@ class BasePassiveBluetoothCoordinator:
self._cancel_track_unavailable = None self._cancel_track_unavailable = None
@callback @callback
def _async_handle_unavailable(self, address: str) -> None: def _async_handle_unavailable(
"""Handle the device going unavailable.""" self, service_info: BluetoothServiceInfoBleak
self._present = False
@callback
def _async_handle_bluetooth_event(
self,
service_info: BluetoothServiceInfoBleak,
change: BluetoothChange,
) -> None: ) -> None:
"""Handle a Bluetooth event.""" """Handle the device going unavailable."""
self.last_seen = time.monotonic() self._last_unavailable_time = service_info.time
self.name = service_info.name self._last_name = service_info.name
self._present = True

View file

@ -56,7 +56,9 @@ class YALEXSBLEEntity(Entity):
self.async_write_ha_state() self.async_write_ha_state()
@callback @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.""" """Handle device not longer being seen by the bluetooth stack."""
self._attr_available = False self._attr_available = False
self.async_write_ha_state() self.async_write_ha_state()

View file

@ -1,15 +1,14 @@
"""Test the BlueMaestro sensors.""" """Test the BlueMaestro sensors."""
from unittest.mock import patch
from homeassistant.components.bluemaestro.const import DOMAIN from homeassistant.components.bluemaestro.const import DOMAIN
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
from . import BLUEMAESTRO_SERVICE_INFO from . import BLUEMAESTRO_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass): async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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("sensor")) == 0 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() await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 4 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 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: def _get_manager() -> BluetoothManager:
"""Return the bluetooth manager.""" """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: def patch_all_discovered_devices(mock_discovered: list[BLEDevice]) -> None:
"""Mock all the discovered devices from all the scanners.""" """Mock all the discovered devices from all the scanners."""
return patch.object( 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: def patch_discovered_devices(mock_discovered: list[BLEDevice]) -> None:
"""Mock the combined best path to discovered devices from all the scanners.""" """Mock the combined best path to discovered devices from all the scanners."""
return patch.object( return patch.object(

View file

@ -3,13 +3,12 @@ from __future__ import annotations
import asyncio import asyncio
import logging import logging
from unittest.mock import MagicMock, call, patch from unittest.mock import MagicMock, call
from bleak import BleakError from bleak import BleakError
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
DOMAIN, DOMAIN,
BluetoothChange,
BluetoothScanningMode, BluetoothScanningMode,
BluetoothServiceInfoBleak, BluetoothServiceInfoBleak,
) )
@ -21,6 +20,8 @@ from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from tests.components.bluetooth import inject_bluetooth_service_info
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -60,26 +61,14 @@ async def test_basic_usage(hass: HomeAssistant, mock_bleak_scanner_start):
poll_method=_poll, poll_method=_poll,
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock() processor = MagicMock()
coordinator.async_register_processor(processor) coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode): cancel = coordinator.async_start()
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch( inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
"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)
await hass.async_block_till_done() await hass.async_block_till_done()
assert coordinator.available is True 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 assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock() processor = MagicMock()
coordinator.async_register_processor(processor) coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode): cancel = coordinator.async_start()
nonlocal saved_callback
saved_callback = _callback
return lambda: None
with patch( inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
"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)
await hass.async_block_till_done() await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": True}) assert async_handle_update.mock_calls[-1] == call({"testdata": True})
flag = False 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() await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": None}) assert async_handle_update.mock_calls[-1] == call({"testdata": None})
flag = True 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() await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": True}) 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 assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock() processor = MagicMock()
coordinator.async_register_processor(processor) coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode): cancel = coordinator.async_start()
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
# First poll fails # 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() await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": None}) assert async_handle_update.mock_calls[-1] == call({"testdata": None})
@ -231,7 +196,7 @@ async def test_bleak_error_and_recover(
# Second poll works # Second poll works
flag = False 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() await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": False}) 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 assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock() processor = MagicMock()
coordinator.async_register_processor(processor) coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode): cancel = coordinator.async_start()
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
# First poll fails # 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() await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": None}) assert async_handle_update.mock_calls[-1] == call({"testdata": None})
# Second poll works # Second poll works
flag = False 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() await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": False}) 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, poll_method=_poll,
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock() processor = MagicMock()
coordinator.async_register_processor(processor) coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode): cancel = coordinator.async_start()
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
# First poll gets queued # 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 # 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() await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": 1}) 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, poll_method=_poll,
) )
assert coordinator.available is False # no data yet assert coordinator.available is False # no data yet
saved_callback = None
processor = MagicMock() processor = MagicMock()
coordinator.async_register_processor(processor) coordinator.async_register_processor(processor)
async_handle_update = processor.async_handle_update async_handle_update = processor.async_handle_update
def _async_register_callback(_hass, _callback, _matcher, _mode): cancel = coordinator.async_start()
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
# First poll gets queued # 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 # 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 # 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() await hass.async_block_till_done()
assert async_handle_update.mock_calls[-1] == call({"testdata": 1}) 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.setup import async_setup_component
from homeassistant.util import dt as dt_util 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.common import async_fire_time_changed
from tests.components.bluetooth import inject_bluetooth_service_info
_LOGGER = logging.getLogger(__name__) _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 hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet 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() mock_listener = MagicMock()
unregister_listener = coordinator.async_add_listener(mock_listener) unregister_listener = coordinator.async_add_listener(mock_listener)
with patch( cancel = coordinator.async_start()
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
cancel = coordinator.async_start()
assert saved_callback is not None inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
assert len(mock_listener.mock_calls) == 1 assert len(mock_listener.mock_calls) == 1
assert coordinator.data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} assert coordinator.data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi}
assert coordinator.available is True assert coordinator.available is True
unregister_listener() 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 len(mock_listener.mock_calls) == 1
assert coordinator.data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} 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 hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE
) )
assert coordinator.available is False # no data yet 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() mock_listener = MagicMock()
coordinator.async_add_listener(mock_listener) coordinator.async_add_listener(mock_listener)
with patch( coordinator.async_start()
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
coordinator.async_start()
assert not set(coordinator.async_contexts()) 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 hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.PASSIVE
) )
assert coordinator.available is False # no data yet 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() mock_listener = MagicMock()
coordinator.async_add_listener(mock_listener) coordinator.async_add_listener(mock_listener)
with patch( coordinator.async_start()
"homeassistant.components.bluetooth.update_coordinator.async_register_callback",
_async_register_callback,
):
coordinator.async_start()
assert coordinator.available is False 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 assert coordinator.available is True
with patch_all_discovered_devices( with patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]):
[MagicMock(address="44:44:33:11:23:45")]
), patch_history({"aa:bb:cc:dd:ee:ff": MagicMock()}):
async_fire_time_changed( async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS) hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS)
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert coordinator.available is False 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 assert coordinator.available is True
with patch_all_discovered_devices( with patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]):
[MagicMock(address="44:44:33:11:23:45")]
), patch_history({"aa:bb:cc:dd:ee:ff": MagicMock()}):
async_fire_time_changed( async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS) 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) entity = PassiveBluetoothCoordinatorEntity(coordinator)
assert entity.available is False assert entity.available is False
saved_callback = None coordinator.async_start()
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()
assert coordinator.available is False 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 assert coordinator.available is True
entity.hass = hass entity.hass = hass
await entity.async_update() await entity.async_update()

View file

@ -3,6 +3,7 @@ from __future__ import annotations
from datetime import timedelta from datetime import timedelta
import logging import logging
import time
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from home_assistant_bluetooth import BluetoothServiceInfo from home_assistant_bluetooth import BluetoothServiceInfo
@ -16,6 +17,7 @@ from homeassistant.components.bluetooth import (
DOMAIN, DOMAIN,
BluetoothChange, BluetoothChange,
BluetoothScanningMode, BluetoothScanningMode,
BluetoothServiceInfoBleak,
) )
from homeassistant.components.bluetooth.const import UNAVAILABLE_TRACK_SECONDS from homeassistant.components.bluetooth.const import UNAVAILABLE_TRACK_SECONDS
from homeassistant.components.bluetooth.passive_update_processor import ( 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.setup import async_setup_component
from homeassistant.util import dt as dt_util 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.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__) _LOGGER = logging.getLogger(__name__)
@ -50,6 +56,7 @@ GENERIC_BLUETOOTH_SERVICE_INFO = BluetoothServiceInfo(
service_uuids=[], service_uuids=[],
source="local", source="local",
) )
GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE = PassiveBluetoothDataUpdate( GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE = PassiveBluetoothDataUpdate(
devices={ devices={
None: DeviceInfo( None: DeviceInfo(
@ -105,21 +112,11 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
_mock_update_method, _mock_update_method,
) )
assert coordinator.available is False # no data yet 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) processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch( unregister_processor = coordinator.async_register_processor(processor)
"homeassistant.components.bluetooth.update_coordinator.async_register_callback", cancel_coordinator = coordinator.async_start()
_async_register_callback,
):
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
entity_key = PassiveBluetoothEntityKey("temperature", None) entity_key = PassiveBluetoothEntityKey("temperature", None)
entity_key_events = [] entity_key_events = []
@ -149,7 +146,7 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
mock_add_entities, 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 # Each listener should receive the same data
# since both match # 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 # There should be 4 calls to create entities
assert len(mock_entity.mock_calls) == 2 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 # Each listener should receive the same data
# since both match # since both match
@ -174,7 +171,7 @@ async def test_basic_usage(hass, mock_bleak_scanner_start):
cancel_listener() cancel_listener()
cancel_async_add_entities_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 # Each listener should not trigger any more now
# that they were cancelled # that they were cancelled
@ -217,20 +214,11 @@ async def test_unavailable_after_no_data(hass, mock_bleak_scanner_start):
_mock_update_method, _mock_update_method,
) )
assert coordinator.available is False # no data yet 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) processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback", unregister_processor = coordinator.async_register_processor(processor)
_async_register_callback, cancel_coordinator = coordinator.async_start()
):
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
mock_entity = MagicMock() mock_entity = MagicMock()
mock_add_entities = 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 coordinator.available is False
assert processor.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 len(mock_add_entities.mock_calls) == 1
assert coordinator.available is True assert coordinator.available is True
assert processor.available is True assert processor.available is True
with patch_all_discovered_devices( with patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]):
[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()},
):
async_fire_time_changed( async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS) hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS)
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert coordinator.available is False assert coordinator.available is False
assert processor.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 len(mock_add_entities.mock_calls) == 1
assert coordinator.available is True assert coordinator.available is True
assert processor.available is True assert processor.available is True
with patch_all_discovered_devices( with patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]):
[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()},
):
async_fire_time_changed( async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS) hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS)
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert coordinator.available is False assert coordinator.available is False
assert processor.available is False assert processor.available is False
assert coordinator.last_seen == service_info_at_time.time
unregister_processor() unregister_processor()
cancel_coordinator() cancel_coordinator()
@ -304,21 +303,11 @@ async def test_no_updates_once_stopping(hass, mock_bleak_scanner_start):
_mock_update_method, _mock_update_method,
) )
assert coordinator.available is False # no data yet 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) processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch( unregister_processor = coordinator.async_register_processor(processor)
"homeassistant.components.bluetooth.update_coordinator.async_register_callback", cancel_coordinator = coordinator.async_start()
_async_register_callback,
):
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
all_events = [] all_events = []
@ -330,13 +319,13 @@ async def test_no_updates_once_stopping(hass, mock_bleak_scanner_start):
_all_listener, _all_listener,
) )
saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
assert len(all_events) == 1 assert len(all_events) == 1
hass.state = CoreState.stopping hass.state = CoreState.stopping
# We should stop processing events once hass is 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 assert len(all_events) == 1
unregister_processor() unregister_processor()
cancel_coordinator() cancel_coordinator()
@ -361,7 +350,7 @@ async def test_exception_from_update_method(hass, caplog, mock_bleak_scanner_sta
"""Generate mock data.""" """Generate mock data."""
nonlocal run_count nonlocal run_count
run_count += 1 run_count += 1
if run_count == 2: if run_count == 1:
raise Exception("Test exception") raise Exception("Test exception")
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE 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()) 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 assert processor.available is True
# We should go unavailable once we get an exception # 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.""" """Generate mock data."""
nonlocal run_count nonlocal run_count
run_count += 1 run_count += 1
if run_count == 2: if run_count == 1:
return "bad_data" return "bad_data"
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE 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()) 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 assert processor.available is True
# We should go unavailable once we get bad data # 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, _mock_update_method,
) )
assert coordinator.available is False # no data yet 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) processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback", coordinator.async_register_processor(processor)
_async_register_callback, cancel_coordinator = coordinator.async_start()
):
coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
processor.async_add_listener(MagicMock()) processor.async_add_listener(MagicMock())
@ -812,19 +792,19 @@ async def test_integration_with_entity(hass, mock_bleak_scanner_start):
mock_add_entities, 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 # First call with just the remote sensor entities results in them being added
assert len(mock_add_entities.mock_calls) == 1 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 # Second call with just the remote sensor entities does not add them again
assert len(mock_add_entities.mock_calls) == 1 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 # Third call with primary and remote sensor entities adds the primary sensor entities
assert len(mock_add_entities.mock_calls) == 2 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 # Forth call with both primary and remote sensor entities does not add them again
assert len(mock_add_entities.mock_calls) == 2 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, _mock_update_method,
) )
assert coordinator.available is False # no data yet 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) processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback", coordinator.async_register_processor(processor)
_async_register_callback, cancel_coordinator = coordinator.async_start()
):
coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
mock_add_entities = MagicMock() mock_add_entities = MagicMock()
@ -930,11 +901,11 @@ async def test_integration_with_entity_without_a_device(hass, mock_bleak_scanner
mock_add_entities, 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 # First call with just the remote sensor entities results in them being added
assert len(mock_add_entities.mock_calls) == 1 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 # Second call with just the remote sensor entities does not add them again
assert len(mock_add_entities.mock_calls) == 1 assert len(mock_add_entities.mock_calls) == 1
@ -982,20 +953,11 @@ async def test_passive_bluetooth_entity_with_entity_platform(
_mock_update_method, _mock_update_method,
) )
assert coordinator.available is False # no data yet 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) processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback", coordinator.async_register_processor(processor)
_async_register_callback, cancel_coordinator = coordinator.async_start()
):
coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
processor.async_add_entities_listener( processor.async_add_entities_listener(
PassiveBluetoothProcessorEntity, PassiveBluetoothProcessorEntity,
@ -1003,9 +965,9 @@ async def test_passive_bluetooth_entity_with_entity_platform(
entity_platform.async_add_entities(entities) 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() 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() await hass.async_block_till_done()
assert ( assert (
hass.states.get("test_domain.test_platform_aa_bb_cc_dd_ee_ff_temperature") 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, _mock_update_method,
) )
assert coordinator.available is False # no data yet 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( binary_sensor_processor = PassiveBluetoothDataProcessor(
lambda service_info: BINARY_SENSOR_PASSIVE_BLUETOOTH_DATA_UPDATE 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 lambda service_info: SENSOR_PASSIVE_BLUETOOTH_DATA_UPDATE
) )
with patch( coordinator.async_register_processor(binary_sensor_processor)
"homeassistant.components.bluetooth.update_coordinator.async_register_callback", coordinator.async_register_processor(sesnor_processor)
_async_register_callback, 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()) binary_sensor_processor.async_add_listener(MagicMock())
sesnor_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, 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 # 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_binary_sensor_entities.mock_calls) == 1
assert len(mock_add_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, _mock_update_method,
) )
assert coordinator.available is False # no data yet 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) processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
with patch(
"homeassistant.components.bluetooth.update_coordinator.async_register_callback", unregister_processor = coordinator.async_register_processor(processor)
_async_register_callback, cancel_coordinator = coordinator.async_start()
):
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
processor.async_add_listener(MagicMock()) 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 assert processor.available is True
# We should go unavailable once we get an exception # 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 "Test exception" in caplog.text
assert processor.available is False assert processor.available is False
# We should go available again once we get data again # 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 assert processor.available is True
unregister_processor() unregister_processor()
cancel_coordinator() cancel_coordinator()

View file

@ -1,17 +1,16 @@
"""Test BTHome binary sensors.""" """Test BTHome binary sensors."""
import logging import logging
from unittest.mock import patch
import pytest import pytest
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.bthome.const import DOMAIN from homeassistant.components.bthome.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_OFF, STATE_ON from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_OFF, STATE_ON
from . import make_advertisement from . import make_advertisement
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -81,25 +80,14 @@ async def test_binary_sensors(
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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 len(hass.states.async_all()) == 0
saved_callback( inject_bluetooth_service_info(
hass,
advertisement, advertisement,
BluetoothChange.ADVERTISEMENT,
) )
await hass.async_block_till_done() await hass.async_block_till_done()

View file

@ -1,10 +1,8 @@
"""Test the BTHome sensors.""" """Test the BTHome sensors."""
from unittest.mock import patch
import pytest import pytest
from homeassistant.components.bluetooth import BluetoothChange
from homeassistant.components.bthome.const import DOMAIN from homeassistant.components.bthome.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import make_advertisement, make_encrypted_advertisement
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -340,25 +339,14 @@ async def test_sensors(
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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 len(hass.states.async_all()) == 0
saved_callback( inject_bluetooth_service_info(
hass,
advertisement, advertisement,
BluetoothChange.ADVERTISEMENT,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(hass.states.async_all()) == len(result) assert len(hass.states.async_all()) == len(result)

View file

@ -1,8 +1,6 @@
"""Test the Govee BLE sensors.""" """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.govee_ble.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import GVH5075_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass): async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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 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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 3 assert len(hass.states.async_all()) == 3

View file

@ -1,8 +1,6 @@
"""Test the INKBIRD config flow.""" """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.inkbird.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import SPS_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass): async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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 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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 3 assert len(hass.states.async_all()) == 3

View file

@ -1,8 +1,6 @@
"""Test the Moat sensors.""" """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.moat.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import MOAT_S2_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass): async def test_sensors(hass):
"""Test setting up creates the sensors.""" """Test setting up creates the sensors."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id="61DE521B-F0BF-9F44-64D4-75BBE1738105", unique_id="aa:bb:cc:dd:ee:ff",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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 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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4 assert len(hass.states.async_all()) == 4

View file

@ -1,14 +1,13 @@
"""Test the Qingping binary sensors.""" """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.components.qingping.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME from homeassistant.const import ATTR_FRIENDLY_NAME
from . import LIGHT_AND_SIGNAL_SERVICE_INFO from . import LIGHT_AND_SIGNAL_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_binary_sensors(hass): async def test_binary_sensors(hass):
@ -19,22 +18,11 @@ async def test_binary_sensors(hass):
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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("binary_sensor")) == 0 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() await hass.async_block_till_done()
assert len(hass.states.async_all("binary_sensor")) == 1 assert len(hass.states.async_all("binary_sensor")) == 1

View file

@ -1,8 +1,6 @@
"""Test the Qingping sensors.""" """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.qingping.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.sensor import ATTR_STATE_CLASS
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import LIGHT_AND_SIGNAL_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass): async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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("sensor")) == 0 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() await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 1 assert len(hass.states.async_all("sensor")) == 1

View file

@ -1,8 +1,6 @@
"""Test the SensorPro sensors.""" """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.sensor import ATTR_STATE_CLASS
from homeassistant.components.sensorpro.const import DOMAIN from homeassistant.components.sensorpro.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import SENSORPRO_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass): async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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("sensor")) == 0 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() await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 4 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.sensor import ATTR_STATE_CLASS
from homeassistant.components.sensorpush.const import DOMAIN from homeassistant.components.sensorpush.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import HTPWX_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass): async def test_sensors(hass):
"""Test setting up creates the sensors.""" """Test setting up creates the sensors."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id="61DE521B-F0BF-9F44-64D4-75BBE1738105", unique_id="4125DDBA-2774-4851-9889-6AADDD4CAC3D",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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 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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 3 assert len(hass.states.async_all()) == 3

View file

@ -1,8 +1,6 @@
"""Test the ThermoBeacon sensors.""" """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.sensor import ATTR_STATE_CLASS
from homeassistant.components.thermobeacon.const import DOMAIN from homeassistant.components.thermobeacon.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import THERMOBEACON_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass): async def test_sensors(hass):
@ -20,22 +19,11 @@ async def test_sensors(hass):
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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("sensor")) == 0 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() await hass.async_block_till_done()
assert len(hass.states.async_all("sensor")) == 4 assert len(hass.states.async_all("sensor")) == 4

View file

@ -1,8 +1,5 @@
"""Test the ThermoPro config flow.""" """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.sensor import ATTR_STATE_CLASS
from homeassistant.components.thermopro.const import DOMAIN from homeassistant.components.thermopro.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import TP357_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass): async def test_sensors(hass):
"""Test setting up creates the sensors.""" """Test setting up creates the sensors."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id="61DE521B-F0BF-9F44-64D4-75BBE1738105", unique_id="4125DDBA-2774-4851-9889-6AADDD4CAC3D",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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 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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2 assert len(hass.states.async_all()) == 2

View file

@ -2,9 +2,6 @@
from __future__ import annotations 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.sensor import ATTR_STATE_CLASS
from homeassistant.components.tilt_ble.const import DOMAIN from homeassistant.components.tilt_ble.const import DOMAIN
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT 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 . import TILT_GREEN_SERVICE_INFO
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.bluetooth import inject_bluetooth_service_info
async def test_sensors(hass: HomeAssistant): async def test_sensors(hass: HomeAssistant):
"""Test setting up creates the sensors.""" """Test setting up creates the sensors."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id="61DE521B-F0BF-9F44-64D4-75BBE1738105", unique_id="F6:0F:28:F2:1F:CB",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
saved_callback: BluetoothCallback | None = None assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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 len(hass.states.async_all()) == 0
assert saved_callback is not None inject_bluetooth_service_info(hass, TILT_GREEN_SERVICE_INFO)
saved_callback(TILT_GREEN_SERVICE_INFO, BluetoothChange.ADVERTISEMENT)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2 assert len(hass.states.async_all()) == 2

View file

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

View file

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