mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
Constant polling for Husqvarna Automower (#147957)
This commit is contained in:
parent
dcad5bbe04
commit
528daad854
@ -74,7 +74,7 @@ class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[MowerDictionary]):
|
||||
"""Subscribe for websocket and poll data from the API."""
|
||||
if not self.ws_connected:
|
||||
await self.api.connect()
|
||||
self.api.register_data_callback(self.callback)
|
||||
self.api.register_data_callback(self.handle_websocket_updates)
|
||||
self.ws_connected = True
|
||||
try:
|
||||
data = await self.api.get_status()
|
||||
@ -86,11 +86,27 @@ class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[MowerDictionary]):
|
||||
return data
|
||||
|
||||
@callback
|
||||
def callback(self, ws_data: MowerDictionary) -> None:
|
||||
def handle_websocket_updates(self, ws_data: MowerDictionary) -> None:
|
||||
"""Process websocket callbacks and write them to the DataUpdateCoordinator."""
|
||||
self.async_set_updated_data(ws_data)
|
||||
self._async_add_remove_devices_and_entities(ws_data)
|
||||
|
||||
@callback
|
||||
def async_set_updated_data(self, data: MowerDictionary) -> None:
|
||||
"""Override DataUpdateCoordinator to preserve fixed polling interval.
|
||||
|
||||
The built-in implementation resets the polling timer on every websocket
|
||||
update. Since websockets do not deliver all required data (e.g. statistics
|
||||
or work area details), we enforce a constant REST polling cadence.
|
||||
"""
|
||||
self.data = data
|
||||
self.last_update_success = True
|
||||
self.logger.debug(
|
||||
"Manually updated %s data",
|
||||
self.name,
|
||||
)
|
||||
self.async_update_listeners()
|
||||
|
||||
async def client_listen(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
|
@ -1,7 +1,9 @@
|
||||
"""Tests for init module."""
|
||||
|
||||
from asyncio import Event
|
||||
from datetime import datetime
|
||||
from collections.abc import Callable
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timedelta
|
||||
import http
|
||||
import time
|
||||
from unittest.mock import AsyncMock, patch
|
||||
@ -20,7 +22,7 @@ from syrupy.assertion import SnapshotAssertion
|
||||
from homeassistant.components.husqvarna_automower.const import DOMAIN, OAUTH2_TOKEN
|
||||
from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
@ -221,6 +223,73 @@ async def test_device_info(
|
||||
assert reg_device == snapshot
|
||||
|
||||
|
||||
async def test_constant_polling(
|
||||
hass: HomeAssistant,
|
||||
mock_automower_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
values: dict[str, MowerAttributes],
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Verify that receiving a WebSocket update does not interrupt the regular polling cycle.
|
||||
|
||||
The test simulates a WebSocket update that changes an entity's state, then advances time
|
||||
to trigger a scheduled poll to confirm polled data also arrives.
|
||||
"""
|
||||
test_values = deepcopy(values)
|
||||
callback_holder: dict[str, Callable] = {}
|
||||
|
||||
@callback
|
||||
def fake_register_websocket_response(
|
||||
cb: Callable[[dict[str, MowerAttributes]], None],
|
||||
) -> None:
|
||||
callback_holder["cb"] = cb
|
||||
|
||||
mock_automower_client.register_data_callback.side_effect = (
|
||||
fake_register_websocket_response
|
||||
)
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_automower_client.register_data_callback.called
|
||||
assert "cb" in callback_holder
|
||||
|
||||
state = hass.states.get("sensor.test_mower_1_battery")
|
||||
assert state is not None
|
||||
assert state.state == "100"
|
||||
state = hass.states.get("sensor.test_mower_1_front_lawn_progress")
|
||||
assert state is not None
|
||||
assert state.state == "40"
|
||||
|
||||
test_values[TEST_MOWER_ID].battery.battery_percent = 77
|
||||
|
||||
freezer.tick(SCAN_INTERVAL - timedelta(seconds=1))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
callback_holder["cb"](test_values)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.test_mower_1_battery")
|
||||
assert state is not None
|
||||
assert state.state == "77"
|
||||
state = hass.states.get("sensor.test_mower_1_front_lawn_progress")
|
||||
assert state is not None
|
||||
assert state.state == "40"
|
||||
|
||||
test_values[TEST_MOWER_ID].work_areas[123456].progress = 50
|
||||
mock_automower_client.get_status.return_value = test_values
|
||||
freezer.tick(timedelta(seconds=4))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
mock_automower_client.get_status.assert_awaited()
|
||||
state = hass.states.get("sensor.test_mower_1_battery")
|
||||
assert state is not None
|
||||
assert state.state == "77"
|
||||
state = hass.states.get("sensor.test_mower_1_front_lawn_progress")
|
||||
assert state is not None
|
||||
assert state.state == "50"
|
||||
|
||||
|
||||
async def test_coordinator_automatic_registry_cleanup(
|
||||
hass: HomeAssistant,
|
||||
mock_automower_client: AsyncMock,
|
||||
|
Loading…
x
Reference in New Issue
Block a user