mirror of
https://github.com/home-assistant/core.git
synced 2026-07-13 17:44:45 +01:00
Update availability of entities when connection changes (#163252)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""Common base for entities."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pysmarlaapi import Federwiege
|
||||
@@ -10,6 +11,8 @@ from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
|
||||
from .const import DEVICE_MODEL_NAME, DOMAIN, MANUFACTURER_NAME
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SmarlaEntityDescription(EntityDescription):
|
||||
@@ -30,6 +33,7 @@ class SmarlaBaseEntity(Entity):
|
||||
def __init__(self, federwiege: Federwiege, desc: SmarlaEntityDescription) -> None:
|
||||
"""Initialise the entity."""
|
||||
self.entity_description = desc
|
||||
self._federwiege = federwiege
|
||||
self._property = federwiege.get_property(desc.service, desc.property)
|
||||
self._attr_unique_id = f"{federwiege.serial_number}-{desc.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
@@ -39,15 +43,35 @@ class SmarlaBaseEntity(Entity):
|
||||
manufacturer=MANUFACTURER_NAME,
|
||||
serial_number=federwiege.serial_number,
|
||||
)
|
||||
self._unavailable_logged = False
|
||||
|
||||
async def on_change(self, value: Any):
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self._federwiege.available
|
||||
|
||||
async def on_availability_change(self, available: bool) -> None:
|
||||
"""Handle availability changes."""
|
||||
if not self.available and not self._unavailable_logged:
|
||||
_LOGGER.info("Entity %s is unavailable", self.entity_id)
|
||||
self._unavailable_logged = True
|
||||
elif self.available and self._unavailable_logged:
|
||||
_LOGGER.info("Entity %s is back online", self.entity_id)
|
||||
self._unavailable_logged = False
|
||||
|
||||
# Notify ha that state changed
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def on_change(self, value: Any) -> None:
|
||||
"""Notify ha when state changes."""
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Run when this Entity has been added to HA."""
|
||||
await self._federwiege.add_listener(self.on_availability_change)
|
||||
await self._property.add_listener(self.on_change)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Entity being removed from hass."""
|
||||
await self._property.remove_listener(self.on_change)
|
||||
await self._federwiege.remove_listener(self.on_availability_change)
|
||||
|
||||
@@ -24,9 +24,9 @@ rules:
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters: done
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable: todo
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: todo
|
||||
log-when-unavailable: done
|
||||
parallel-updates: done
|
||||
reauthentication-flow: done
|
||||
test-coverage: done
|
||||
|
||||
@@ -66,6 +66,7 @@ def mock_federwiege_cls(mock_connection: MagicMock) -> Generator[MagicMock]:
|
||||
) as mock_federwiege_cls:
|
||||
mock_federwiege = mock_federwiege_cls.return_value
|
||||
mock_federwiege.serial_number = MOCK_ACCESS_TOKEN_JSON["serialNumber"]
|
||||
mock_federwiege.available = True
|
||||
|
||||
mock_babywiege_service = MagicMock(spec=Service)
|
||||
mock_babywiege_service.props = {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Test Smarla entities."""
|
||||
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration, update_property_listeners
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
TEST_ENTITY_ID = "switch.smarla"
|
||||
|
||||
|
||||
async def test_entity_availability(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_federwiege: MagicMock,
|
||||
) -> None:
|
||||
"""Test entity state when device becomes unavailable/available."""
|
||||
assert await setup_integration(hass, mock_config_entry)
|
||||
|
||||
# Initially available
|
||||
state = hass.states.get(TEST_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
# Simulate device becoming unavailable
|
||||
mock_federwiege.available = False
|
||||
await update_property_listeners(mock_federwiege)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify state reflects unavailable
|
||||
state = hass.states.get(TEST_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Simulate device becoming available again
|
||||
mock_federwiege.available = True
|
||||
await update_property_listeners(mock_federwiege)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify state reflects available again
|
||||
state = hass.states.get(TEST_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_entity_unavailable_logging(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
mock_federwiege: MagicMock,
|
||||
) -> None:
|
||||
"""Test logging when device becomes unavailable/available."""
|
||||
assert await setup_integration(hass, mock_config_entry)
|
||||
|
||||
caplog.set_level(logging.INFO)
|
||||
caplog.clear()
|
||||
|
||||
# Verify that log exists when device becomes unavailable
|
||||
mock_federwiege.available = False
|
||||
await update_property_listeners(mock_federwiege)
|
||||
await hass.async_block_till_done()
|
||||
assert "is unavailable" in caplog.text
|
||||
|
||||
# Verify that we only log once
|
||||
caplog.clear()
|
||||
await update_property_listeners(mock_federwiege)
|
||||
await hass.async_block_till_done()
|
||||
assert "is unavailable" not in caplog.text
|
||||
|
||||
# Verify that log exists when device comes back online
|
||||
mock_federwiege.available = True
|
||||
await update_property_listeners(mock_federwiege)
|
||||
await hass.async_block_till_done()
|
||||
assert "back online" in caplog.text
|
||||
|
||||
# Verify that we only log once
|
||||
caplog.clear()
|
||||
await update_property_listeners(mock_federwiege)
|
||||
await hass.async_block_till_done()
|
||||
assert "back online" not in caplog.text
|
||||
Reference in New Issue
Block a user