Skip to content

Commit 7766e96

Browse files
Merge pull request #14 from andrew-codechimp/real-change
Real change
2 parents 0427709 + 8d2d5b9 commit 7766e96

File tree

6 files changed

+56
-12
lines changed

6 files changed

+56
-12
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Periodic Min/Max Helpers for Home Assistant
1010

1111
The helpers record the minimum or maximum of a sensor until manually reset via the reset action. The value is maintained through HA restarts.
1212

13+
A `last_modified` attribute is available to check when the min or max was really changed, this attribute does not update on HA restarts giving you an accurate indication on when the new min or max was hit. This can be useful for using as a trigger on an automation or for comparing via a template for a daily update.
14+
1315
## Example use cases
1416

1517
- Record the maximum temperature today, resetting at midnight via an automation.

config/configuration.yaml

+4
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

+2
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_MODIFIED = "last_modified"

custom_components/periodic_min_max/sensor.py

+45-10
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_MODIFIED,
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_modified: 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_MODIFIED in last_attrs:
241+
self._attr_last_modified = last_attrs[ATTR_LAST_MODIFIED]
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_MODIFIED] = self._attr_last_modified
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_modified = 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

+1-1
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

+2-1
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)