1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Restore KNX sensor entity states (#154318)

This commit is contained in:
Matthias Alphart
2025-10-14 00:18:41 +02:00
committed by GitHub
parent c9b9f05f4b
commit 07d6ebef4c
5 changed files with 80 additions and 23 deletions
@@ -84,11 +84,11 @@ class _KnxBinarySensor(BinarySensorEntity, RestoreEntity):
async def async_added_to_hass(self) -> None:
"""Restore last state."""
await super().async_added_to_hass()
if (
last_state := await self.async_get_last_state()
) and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
self._device.remote_value.update_value(last_state.state == STATE_ON)
await super().async_added_to_hass()
@property
def is_on(self) -> bool:
+27 -15
View File
@@ -6,15 +6,15 @@ from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime, timedelta
from functools import partial
from typing import Any
from xknx import XKNX
from xknx.core.connection_state import XknxConnectionState, XknxConnectionType
from xknx.devices import Sensor as XknxSensor
from xknx.devices import Device as XknxDevice, Sensor as XknxSensor
from homeassistant import config_entries
from homeassistant.components.sensor import (
CONF_STATE_CLASS,
RestoreSensor,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
@@ -25,6 +25,8 @@ from homeassistant.const import (
CONF_ENTITY_CATEGORY,
CONF_NAME,
CONF_TYPE,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
EntityCategory,
Platform,
)
@@ -141,7 +143,7 @@ def _create_sensor(xknx: XKNX, config: ConfigType) -> XknxSensor:
)
class KNXSensor(KnxYamlEntity, SensorEntity):
class KNXSensor(KnxYamlEntity, RestoreSensor):
"""Representation of a KNX sensor."""
_device: XknxSensor
@@ -164,20 +166,30 @@ class KNXSensor(KnxYamlEntity, SensorEntity):
self._attr_unique_id = str(self._device.sensor_value.group_address_state)
self._attr_native_unit_of_measurement = self._device.unit_of_measurement()
self._attr_state_class = config.get(CONF_STATE_CLASS)
self._attr_extra_state_attributes = {}
@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self._device.resolve_state()
async def async_added_to_hass(self) -> None:
"""Restore last state."""
if (
(last_state := await self.async_get_last_state())
and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
and (
(last_sensor_data := await self.async_get_last_sensor_data())
is not None
)
):
self._attr_native_value = last_sensor_data.native_value
self._attr_extra_state_attributes.update(last_state.attributes)
await super().async_added_to_hass()
@property
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return device specific state attributes."""
attr: dict[str, Any] = {}
if self._device.last_telegram is not None:
attr[ATTR_SOURCE] = str(self._device.last_telegram.source_address)
return attr
def after_update_callback(self, device: XknxDevice) -> None:
"""Call after device was updated."""
self._attr_native_value = self._device.resolve_state()
if telegram := self._device.last_telegram:
self._attr_extra_state_attributes[ATTR_SOURCE] = str(
telegram.source_address
)
super().after_update_callback(device)
class KNXSystemSensor(SensorEntity):
+5 -3
View File
@@ -284,8 +284,8 @@ async def test_binary_sensor_reset(
assert state.state is STATE_OFF
async def test_binary_sensor_restore_and_respond(hass: HomeAssistant, knx) -> None:
"""Test restoring KNX binary sensor state and respond to read."""
async def test_binary_sensor_restore(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test restoring KNX binary sensor state."""
_ADDRESS = "2/2/2"
fake_state = State("binary_sensor.test", STATE_ON)
mock_restore_cache(hass, (fake_state,))
@@ -312,7 +312,9 @@ async def test_binary_sensor_restore_and_respond(hass: HomeAssistant, knx) -> No
assert state.state is STATE_OFF
async def test_binary_sensor_restore_invert(hass: HomeAssistant, knx) -> None:
async def test_binary_sensor_restore_invert(
hass: HomeAssistant, knx: KNXTestKit
) -> None:
"""Test restoring KNX binary sensor state with invert."""
_ADDRESS = "2/2/2"
fake_state = State("binary_sensor.test", STATE_ON)
+46 -3
View File
@@ -2,14 +2,22 @@
from freezegun.api import FrozenDateTimeFactory
from homeassistant.components.knx.const import CONF_STATE_ADDRESS, CONF_SYNC_STATE
from homeassistant.components.knx.const import (
ATTR_SOURCE,
CONF_STATE_ADDRESS,
CONF_SYNC_STATE,
)
from homeassistant.components.knx.schema import SensorSchema
from homeassistant.const import CONF_NAME, CONF_TYPE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, State
from .conftest import KNXTestKit
from tests.common import async_capture_events, async_fire_time_changed
from tests.common import (
async_capture_events,
async_fire_time_changed,
mock_restore_cache_with_extra_data,
)
async def test_sensor(hass: HomeAssistant, knx: KNXTestKit) -> None:
@@ -43,6 +51,41 @@ async def test_sensor(hass: HomeAssistant, knx: KNXTestKit) -> None:
await knx.assert_no_telegram()
async def test_sensor_restore(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test restoring KNX sensor state."""
ADDRESS = "2/2/2"
RAW_FLOAT_21_0 = (0x0C, 0x1A)
RESTORED_STATE = "21.0"
RESTORED_STATE_ATTRIBUTES = {ATTR_SOURCE: knx.INDIVIDUAL_ADDRESS}
fake_state = State(
"sensor.test", "ignored in favour of native_value", RESTORED_STATE_ATTRIBUTES
)
extra_data = {"native_value": RESTORED_STATE, "native_unit_of_measurement": "°C"}
mock_restore_cache_with_extra_data(hass, [(fake_state, extra_data)])
await knx.setup_integration(
{
SensorSchema.PLATFORM: [
{
CONF_NAME: "test",
CONF_STATE_ADDRESS: ADDRESS,
CONF_TYPE: "temperature", # 2 byte float
CONF_SYNC_STATE: False,
},
]
}
)
# restored state - no read-response due to sync_state False
knx.assert_state("sensor.test", RESTORED_STATE, **RESTORED_STATE_ATTRIBUTES)
await knx.assert_telegram_count(0)
# receiving the restored value from restored source does not trigger state_changed event
events = async_capture_events(hass, "state_changed")
await knx.receive_write(ADDRESS, RAW_FLOAT_21_0)
assert not events
async def test_last_reported(
hass: HomeAssistant,
knx: KNXTestKit,
+1 -1
View File
@@ -111,7 +111,7 @@ async def test_switch_state(hass: HomeAssistant, knx: KNXTestKit) -> None:
await knx.assert_telegram_count(0)
async def test_switch_restore_and_respond(hass: HomeAssistant, knx) -> None:
async def test_switch_restore_and_respond(hass: HomeAssistant, knx: KNXTestKit) -> None:
"""Test restoring KNX switch state and respond to read."""
_ADDRESS = "1/1/1"
fake_state = State("switch.test", "on")