Skip to content

Commit df9510b

Browse files
Add last changed value attribute
1 parent 7bbf401 commit df9510b

File tree

5 files changed

+54
-12
lines changed

5 files changed

+54
-12
lines changed

config/configuration.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# https://www.home-assistant.io/integrations/default_config/
22
default_config:
33

4+
automation: !include automations.yaml
5+
script: !include scripts.yaml
6+
scene: !include scenes.yaml
7+
48
# https://www.home-assistant.io/integrations/logger/
59
logger:
610
default: warning

custom_components/periodic_min_max/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@
2323
PLATFORMS = [Platform.SENSOR]
2424

2525
CONF_ENTITY_ID = "entity_id"
26+
27+
ATTR_LAST_CHANGED_VALUE = "last_changed_value"

custom_components/periodic_min_max/sensor.py

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,15 @@
3636
from homeassistant.helpers.reload import async_setup_reload_service
3737
from homeassistant.helpers.restore_state import RestoreEntity
3838
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
39-
40-
from .const import CONF_ENTITY_ID, DOMAIN, LOGGER, PLATFORMS
39+
from homeassistant.util import dt as dt_util
40+
41+
from .const import (
42+
ATTR_LAST_CHANGED_VALUE,
43+
CONF_ENTITY_ID,
44+
DOMAIN,
45+
LOGGER,
46+
PLATFORMS,
47+
)
4148

4249
ATTR_MIN_VALUE = "min_value"
4350
ATTR_MAX_VALUE = "max_value"
@@ -189,6 +196,8 @@ class PeriodicMinMaxSensor(SensorEntity, RestoreEntity):
189196
_attr_icon = ICON
190197
_attr_should_poll = False
191198
_attr_state_class = SensorStateClass.MEASUREMENT
199+
_state_had_real_change = False
200+
_attr_last_changed_value: datetime = dt_util.utcnow().isoformat()
192201

193202
def __init__(
194203
self,
@@ -223,6 +232,14 @@ def __init__(
223232
async def async_added_to_hass(self) -> None:
224233
"""Handle added to Hass."""
225234

235+
await super().async_added_to_hass()
236+
237+
last_state = await self.async_get_last_state()
238+
if last_state:
239+
last_attrs = last_state.attributes
240+
if last_attrs and ATTR_LAST_CHANGED_VALUE in last_attrs:
241+
self._attr_last_changed_value = last_attrs[ATTR_LAST_CHANGED_VALUE]
242+
226243
self.async_on_remove(
227244
async_track_state_change_event(
228245
self.hass,
@@ -291,6 +308,15 @@ def native_unit_of_measurement(self) -> str | None:
291308
return "ERR"
292309
return self._unit_of_measurement
293310

311+
@property
312+
def extra_state_attributes(self) -> dict[str, Any]:
313+
"""Return the device specific state attributes."""
314+
attributes: dict[str, Any] = {}
315+
316+
attributes[ATTR_LAST_CHANGED_VALUE] = self._attr_last_changed_value
317+
318+
return attributes
319+
294320
@callback
295321
def _async_min_max_sensor_state_listener(
296322
self, event: Event[EventStateChangedData], update_state: bool = True
@@ -337,23 +363,32 @@ def _async_min_max_sensor_state_listener(
337363
return
338364

339365
self._calc_values()
366+
367+
if self._state_had_real_change:
368+
self._attr_last_changed_value = dt_util.utcnow().isoformat(sep=" ")
369+
340370
self.async_write_ha_state()
341371

342372
@callback
343373
def _calc_values(self) -> None:
344374
"""Calculate the values."""
375+
self._state_had_real_change = False
345376

346377
"""Calculate min value, honoring unknown states."""
347-
if self._state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and (
348-
self.min_value is None or self.min_value > self._state
349-
):
350-
self.min_value = self._state
378+
if self._sensor_attr == ATTR_MIN_VALUE:
379+
if self._state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and (
380+
self.min_value is None or self.min_value > self._state
381+
):
382+
self.min_value = self._state
383+
self._state_had_real_change = True
351384

352385
"""Calculate max value, honoring unknown states."""
353-
if self._state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and (
354-
self.max_value is None or self.max_value < self._state
355-
):
356-
self.max_value = self._state
386+
if self._sensor_attr == ATTR_MAX_VALUE:
387+
if self._state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and (
388+
self.max_value is None or self.max_value < self._state
389+
):
390+
self.max_value = self._state
391+
self._state_had_real_change = True
357392

358393
async def handle_reset(self) -> None:
359394
"""Set the min & max to current state."""

tests/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ This will install `homeassistant`, `pytest`, and `pytest-homeassistant-custom-co
2020
Command | Description
2121
------- | -----------
2222
`pytest tests/` | This will run all tests in `tests/` and tell you how many passed/failed
23-
`pytest --durations=10 --cov-report term-missing --cov=custom_components.periodic_min_max tests` | This tells `pytest` that your target module to test is `custom_components.periodic_min_max` so that it can give you a [code coverage](https://en.wikipedia.org/wiki/Code_coverage) summary, including % of code that was executed and the line numbers of missed executions.
23+
`pytest --cov-report term-missing --cov=custom_components.periodic_min_max tests` | This tells `pytest` that your target module to test is `custom_components.periodic_min_max` so that it can give you a [code coverage](https://en.wikipedia.org/wiki/Code_coverage) summary, including % of code that was executed and the line numbers of missed executions.
2424
`pytest tests/test_init.py -k test_setup_unload_and_reload_entry` | Runs the `test_setup_unload_and_reload_entry` test function located in `tests/test_init.py`

tests/test_sensor.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ async def test_max_sensor(
8282
entity = entity_registry.async_get("sensor.test_max")
8383
assert entity.unique_id == "very_unique_id"
8484

85+
8586
async def test_value_error(
8687
hass: HomeAssistant, entity_registry: er.EntityRegistry
8788
) -> None:
@@ -113,4 +114,4 @@ async def test_value_error(
113114
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
114115

115116
entity = entity_registry.async_get("sensor.test_max")
116-
assert entity.unique_id == "very_unique_id"
117+
assert entity.unique_id == "very_unique_id"

0 commit comments

Comments
 (0)