Add async_set_updated_data method to PassiveBluetoothProcessorCoordinator (#142879)

This commit is contained in:
J. Nick Koston 2025-04-13 11:08:22 -10:00 committed by GitHub
parent 8ab59bee47
commit 8b88272bc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 126 additions and 0 deletions

View File

@ -374,6 +374,27 @@ class PassiveBluetoothProcessorCoordinator[_DataT](BasePassiveBluetoothCoordinat
self.logger.exception("Unexpected error updating %s data", self.name)
return
self._process_update(update, was_available)
@callback
def async_set_updated_data(self, update: _DataT) -> None:
"""Manually update the processor with new data.
If the data comes in via a different method, like a
notification, this method can be used to update the
processor with the new data.
This is useful for devices that retrieve
some of their data via notifications.
"""
was_available = self._available
self._available = True
self._process_update(update, was_available)
def _process_update(
self, update: _DataT, was_available: bool | None = None
) -> None:
"""Process the update from the bluetooth device."""
if not self.last_update_success:
self.last_update_success = True
self.logger.info("Coordinator %s recovered", self.name)

View File

@ -273,6 +273,111 @@ async def test_basic_usage(hass: HomeAssistant) -> None:
cancel_coordinator()
@pytest.mark.usefixtures("mock_bleak_scanner_start", "mock_bluetooth_adapters")
async def test_async_set_updated_data_usage(hass: HomeAssistant) -> None:
"""Test async_set_updated_data of the PassiveBluetoothProcessorCoordinator."""
await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
@callback
def _mock_update_method(
service_info: BluetoothServiceInfo,
) -> dict[str, str]:
return {"test": "data"}
@callback
def _async_generate_mock_data(
data: dict[str, str],
) -> PassiveBluetoothDataUpdate:
"""Generate mock data."""
assert data == {"test": "data"}
return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE
coordinator = PassiveBluetoothProcessorCoordinator(
hass,
_LOGGER,
"aa:bb:cc:dd:ee:ff",
BluetoothScanningMode.ACTIVE,
_mock_update_method,
)
assert coordinator.available is False # no data yet
processor = PassiveBluetoothDataProcessor(_async_generate_mock_data)
unregister_processor = coordinator.async_register_processor(processor)
cancel_coordinator = coordinator.async_start()
entity_key = PassiveBluetoothEntityKey("temperature", None)
entity_key_events = []
all_events = []
mock_entity = MagicMock()
mock_add_entities = MagicMock()
def _async_entity_key_listener(data: PassiveBluetoothDataUpdate | None) -> None:
"""Mock entity key listener."""
entity_key_events.append(data)
cancel_async_add_entity_key_listener = processor.async_add_entity_key_listener(
_async_entity_key_listener,
entity_key,
)
def _all_listener(data: PassiveBluetoothDataUpdate | None) -> None:
"""Mock an all listener."""
all_events.append(data)
cancel_listener = processor.async_add_listener(
_all_listener,
)
cancel_async_add_entities_listener = processor.async_add_entities_listener(
mock_entity,
mock_add_entities,
)
assert coordinator.available is False
coordinator.async_set_updated_data({"test": "data"})
assert coordinator.available is True
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Each listener should receive the same data
# since both match, and an additional all_events
# for the async_set_updated_data call
assert len(entity_key_events) == 1
assert len(all_events) == 2
# There should be 4 calls to create entities
assert len(mock_entity.mock_calls) == 2
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2)
# Only the all listener should receive the new data
# since temperature is not in the new data, and an additional all_events
# for the async_set_updated_data call
assert len(entity_key_events) == 1
assert len(all_events) == 3
# On the second, the entities should already be created
# so the mock should not be called again
assert len(mock_entity.mock_calls) == 2
cancel_async_add_entity_key_listener()
cancel_listener()
cancel_async_add_entities_listener()
inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO)
# Each listener should not trigger any more now
# that they were cancelled
assert len(entity_key_events) == 1
assert len(all_events) == 3
assert len(mock_entity.mock_calls) == 2
assert coordinator.available is True
unregister_processor()
cancel_coordinator()
@pytest.mark.usefixtures("mock_bleak_scanner_start", "mock_bluetooth_adapters")
async def test_entity_key_is_dispatched_on_entity_key_change(
hass: HomeAssistant,