1
0
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:
Robin Lintermann
2026-02-24 23:25:57 +01:00
committed by GitHub
parent 6a91771f04
commit e505ad9003
4 changed files with 114 additions and 3 deletions
+25 -1
View File
@@ -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
+1
View File
@@ -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 = {
+86
View File
@@ -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