mirror of
https://github.com/home-assistant/core.git
synced 2025-08-30 09:51:37 +02:00
Make event entity dependend on websocket in Husqvarna Automower (#151203)
This commit is contained in:
@@ -62,6 +62,7 @@ class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[MowerDictionary]):
|
||||
self.new_areas_callbacks: list[Callable[[str, set[int]], None]] = []
|
||||
self.pong: datetime | None = None
|
||||
self.websocket_alive: bool = False
|
||||
self.websocket_callbacks: list[Callable[[bool], None]] = []
|
||||
self._watchdog_task: asyncio.Task | None = None
|
||||
|
||||
@override
|
||||
@@ -198,12 +199,17 @@ class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[MowerDictionary]):
|
||||
)
|
||||
|
||||
async def _pong_watchdog(self) -> None:
|
||||
"""Watchdog to check for pong messages."""
|
||||
_LOGGER.debug("Watchdog started")
|
||||
try:
|
||||
while True:
|
||||
_LOGGER.debug("Sending ping")
|
||||
self.websocket_alive = await self.api.send_empty_message()
|
||||
_LOGGER.debug("Ping result: %s", self.websocket_alive)
|
||||
is_alive = await self.api.send_empty_message()
|
||||
_LOGGER.debug("Ping result: %s", is_alive)
|
||||
if self.websocket_alive != is_alive:
|
||||
self.websocket_alive = is_alive
|
||||
for ws_callback in self.websocket_callbacks:
|
||||
ws_callback(is_alive)
|
||||
|
||||
await asyncio.sleep(PING_INTERVAL)
|
||||
_LOGGER.debug("Websocket alive %s", self.websocket_alive)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
"""Creates the event entities for supported mowers."""
|
||||
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
|
||||
from aioautomower.model import SingleMessageData
|
||||
|
||||
@@ -18,6 +19,7 @@ from .const import ERROR_KEYS
|
||||
from .coordinator import AutomowerDataUpdateCoordinator
|
||||
from .entity import AutomowerBaseEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
ATTR_SEVERITY = "severity"
|
||||
@@ -80,6 +82,12 @@ class AutomowerMessageEventEntity(AutomowerBaseEntity, EventEntity):
|
||||
"""Initialize Automower message event entity."""
|
||||
super().__init__(mower_id, coordinator)
|
||||
self._attr_unique_id = f"{mower_id}_message"
|
||||
self.websocket_alive: bool = coordinator.websocket_alive
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if the entity is available."""
|
||||
return self.websocket_alive and self.mower_id in self.coordinator.data
|
||||
|
||||
@callback
|
||||
def _handle(self, msg: SingleMessageData) -> None:
|
||||
@@ -102,7 +110,17 @@ class AutomowerMessageEventEntity(AutomowerBaseEntity, EventEntity):
|
||||
"""Register callback when entity is added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
self.coordinator.api.register_single_message_callback(self._handle)
|
||||
self.coordinator.websocket_callbacks.append(self._handle_websocket_update)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Unregister WebSocket callback when entity is removed."""
|
||||
self.coordinator.api.unregister_single_message_callback(self._handle)
|
||||
self.coordinator.websocket_callbacks.remove(self._handle_websocket_update)
|
||||
|
||||
def _handle_websocket_update(self, is_alive: bool) -> None:
|
||||
"""Handle websocket status changes."""
|
||||
if self.websocket_alive == is_alive:
|
||||
return
|
||||
self.websocket_alive = is_alive
|
||||
_LOGGER.debug("WebSocket status changed to %s, updating entity state", is_alive)
|
||||
self.async_write_ha_state()
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"""Test helpers for Husqvarna Automower."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Callable, Generator
|
||||
import time
|
||||
from unittest.mock import AsyncMock, create_autospec, patch
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.components.application_credentials import (
|
||||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.components.husqvarna_automower.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
@@ -137,3 +137,21 @@ def mock_automower_client(
|
||||
spec_set=True,
|
||||
)
|
||||
yield mock_instance
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def automower_ws_ready(mock_automower_client: AsyncMock) -> list[Callable[[], None]]:
|
||||
"""Fixture to capture ws_ready_callbacks."""
|
||||
|
||||
ws_ready_callbacks: list[Callable[[], None]] = []
|
||||
|
||||
@callback
|
||||
def fake_register_ws_ready_callback(cb: Callable[[], None]) -> None:
|
||||
ws_ready_callbacks.append(cb)
|
||||
|
||||
mock_automower_client.register_ws_ready_callback.side_effect = (
|
||||
fake_register_ws_ready_callback
|
||||
)
|
||||
mock_automower_client.send_empty_message.return_value = True
|
||||
|
||||
return ws_ready_callbacks
|
||||
|
@@ -33,6 +33,7 @@ async def test_event(
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
values: dict[str, MowerAttributes],
|
||||
automower_ws_ready: list[Callable[[], None]],
|
||||
) -> None:
|
||||
"""Test that a new message arriving over the websocket creates and updates the sensor."""
|
||||
callbacks: list[Callable[[SingleMessageData], None]] = []
|
||||
@@ -46,11 +47,17 @@ async def test_event(
|
||||
mock_automower_client.register_single_message_callback.side_effect = (
|
||||
fake_register_websocket_response
|
||||
)
|
||||
mock_automower_client.send_empty_message.return_value = True
|
||||
|
||||
# Set up integration
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Start the watchdog and let it run once to set websocket_alive=True
|
||||
for cb in automower_ws_ready:
|
||||
cb()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Ensure callback was registered for the test mower
|
||||
assert mock_automower_client.register_single_message_callback.called
|
||||
|
||||
@@ -76,6 +83,7 @@ async def test_event(
|
||||
for cb in callbacks:
|
||||
cb(message)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("event.test_mower_1_message")
|
||||
assert state is not None
|
||||
assert state.attributes[ATTR_EVENT_TYPE] == "wheel_motor_overloaded_rear_left"
|
||||
@@ -84,6 +92,12 @@ async def test_event(
|
||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
# Start the new watchdog and let it run
|
||||
for cb in automower_ws_ready:
|
||||
cb()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("event.test_mower_1_message")
|
||||
assert state is not None
|
||||
assert state.attributes[ATTR_EVENT_TYPE] == "wheel_motor_overloaded_rear_left"
|
||||
@@ -129,6 +143,7 @@ async def test_event(
|
||||
for cb in callbacks:
|
||||
cb(message)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entry = entity_registry.async_get("event.test_mower_1_message")
|
||||
assert entry is not None
|
||||
assert state.attributes[ATTR_EVENT_TYPE] == "alarm_mower_lifted"
|
||||
@@ -154,9 +169,9 @@ async def test_event_snapshot(
|
||||
hass: HomeAssistant,
|
||||
mock_automower_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
automower_ws_ready: list[Callable[[], None]],
|
||||
) -> None:
|
||||
"""Test that a new message arriving over the websocket updates the sensor."""
|
||||
with patch(
|
||||
@@ -179,6 +194,11 @@ async def test_event_snapshot(
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Start the watchdog and let it run once
|
||||
for cb in automower_ws_ready:
|
||||
cb()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Ensure callback was registered for the test mower
|
||||
assert mock_automower_client.register_single_message_callback.called
|
||||
|
||||
|
Reference in New Issue
Block a user