diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index ed88ca55ceb..f3b9a24a15d 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -2,9 +2,9 @@ from __future__ import annotations from collections.abc import Mapping -from datetime import timedelta +from datetime import datetime, timedelta import logging -from typing import Any, cast +from typing import Any, cast, final import voluptuous as vol @@ -36,6 +36,7 @@ from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) +ATTR_LAST_RESET = "last_reset" ATTR_STATE_CLASS = "state_class" DOMAIN = "sensor" @@ -100,10 +101,24 @@ class SensorEntity(Entity): """Return the state class of this entity, from STATE_CLASSES, if any.""" return None + @property + def last_reset(self) -> datetime | None: + """Return the time when the sensor was last reset, if any.""" + return None + @property def capability_attributes(self) -> Mapping[str, Any] | None: """Return the capability attributes.""" - if self.state_class: - return {ATTR_STATE_CLASS: self.state_class} + if state_class := self.state_class: + return {ATTR_STATE_CLASS: state_class} + + return None + + @final + @property + def state_attributes(self) -> dict[str, Any] | None: + """Return state attributes.""" + if last_reset := self.last_reset: + return {ATTR_LAST_RESET: last_reset} return None diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index b8e7cef111c..1d244c970ff 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -5,7 +5,7 @@ import logging import voluptuous as vol -from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import ATTR_LAST_RESET, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, @@ -51,7 +51,6 @@ ATTR_SOURCE_ID = "source" ATTR_STATUS = "status" ATTR_PERIOD = "meter_period" ATTR_LAST_PERIOD = "last_period" -ATTR_LAST_RESET = "last_reset" ATTR_TARIFF = "tariff" ICON = "mdi:counter" @@ -331,7 +330,6 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): ATTR_SOURCE_ID: self._sensor_source_id, ATTR_STATUS: PAUSED if self._collecting is None else COLLECTING, ATTR_LAST_PERIOD: self._last_period, - ATTR_LAST_RESET: self._last_reset, } if self._period is not None: state_attr[ATTR_PERIOD] = self._period @@ -343,3 +341,8 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): def icon(self): """Return the icon to use in the frontend, if any.""" return ICON + + @property + def last_reset(self): + """Return the time when the sensor was last reset.""" + return self._last_reset diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index 24938b1e818..9157ba738c7 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -161,6 +161,7 @@ async def test_state(hass): async def test_restore_state(hass): """Test utility sensor restore state.""" + last_reset = "2020-12-21T00:00:00.013073+00:00" config = { "utility_meter": { "energy_bill": { @@ -177,7 +178,7 @@ async def test_restore_state(hass): "3", attributes={ ATTR_STATUS: PAUSED, - ATTR_LAST_RESET: "2020-12-21T00:00:00.013073+00:00", + ATTR_LAST_RESET: last_reset, }, ), State( @@ -185,7 +186,7 @@ async def test_restore_state(hass): "6", attributes={ ATTR_STATUS: COLLECTING, - ATTR_LAST_RESET: "2020-12-21T00:00:00.013073+00:00", + ATTR_LAST_RESET: last_reset, }, ), ], @@ -199,10 +200,12 @@ async def test_restore_state(hass): state = hass.states.get("sensor.energy_bill_onpeak") assert state.state == "3" assert state.attributes.get("status") == PAUSED + assert state.attributes.get("last_reset") == dt_util.parse_datetime(last_reset) state = hass.states.get("sensor.energy_bill_offpeak") assert state.state == "6" assert state.attributes.get("status") == COLLECTING + assert state.attributes.get("last_reset") == dt_util.parse_datetime(last_reset) # utility_meter is loaded, now set sensors according to utility_meter: hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -304,15 +307,15 @@ def gen_config(cycle, offset=None): async def _test_self_reset(hass, config, start_time, expect_reset=True): """Test energy sensor self reset.""" - assert await async_setup_component(hass, DOMAIN, config) - assert await async_setup_component(hass, SENSOR_DOMAIN, config) - await hass.async_block_till_done() - - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) - entity_id = config[DOMAIN]["energy_bill"]["source"] - now = dt_util.parse_datetime(start_time) with alter_time(now): + assert await async_setup_component(hass, DOMAIN, config) + assert await async_setup_component(hass, SENSOR_DOMAIN, config) + await hass.async_block_till_done() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + entity_id = config[DOMAIN]["energy_bill"]["source"] + async_fire_time_changed(hass, now) hass.states.async_set( entity_id, 1, {ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR} @@ -345,10 +348,12 @@ async def _test_self_reset(hass, config, start_time, expect_reset=True): state = hass.states.get("sensor.energy_bill") if expect_reset: assert state.attributes.get("last_period") == "2" + assert state.attributes.get("last_reset") == now assert state.state == "3" else: assert state.attributes.get("last_period") == 0 assert state.state == "5" + assert state.attributes.get("last_reset") == dt_util.parse_datetime(start_time) async def test_self_reset_quarter_hourly(hass, legacy_patchable_time):