diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 2c401829c30..49e33dfd5e1 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -92,3 +92,6 @@ KELVIN_MIN_VALUE_WHITE: Final = 2700 KELVIN_MIN_VALUE_COLOR: Final = 3000 UPTIME_DEVIATION: Final = 5 + +LAST_RESET_UPTIME: Final = "uptime" +LAST_RESET_NEVER: Final = "never" diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index c8b23f71bd7..a1ce2e671d1 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio from dataclasses import dataclass -from datetime import datetime import logging from typing import Any, Callable, Final, cast @@ -180,7 +179,7 @@ class BlockAttributeDescription: # Callable (settings, block), return true if entity should be removed removal_condition: Callable[[dict, aioshelly.Block], bool] | None = None extra_state_attributes: Callable[[aioshelly.Block], dict | None] | None = None - last_reset: datetime | None = None + last_reset: str | None = None @dataclass @@ -286,6 +285,7 @@ class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity): self._unit: None | str | Callable[[dict], str] = unit self._unique_id: str = f"{super().unique_id}-{self.attribute}" self._name = get_entity_name(wrapper.device, block, self.description.name) + self._last_value: str | None = None @property def unique_id(self) -> str: diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 7c2ffdbc470..56e4f63bc75 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -23,7 +23,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.util import dt -from .const import SHAIR_MAX_WORK_HOURS +from .const import LAST_RESET_NEVER, LAST_RESET_UPTIME, SHAIR_MAX_WORK_HOURS from .entity import ( BlockAttributeDescription, RestAttributeDescription, @@ -114,6 +114,7 @@ SENSORS: Final = { value=lambda value: round(value / 60 / 1000, 2), device_class=sensor.DEVICE_CLASS_ENERGY, state_class=sensor.STATE_CLASS_MEASUREMENT, + last_reset=LAST_RESET_UPTIME, ), ("emeter", "energy"): BlockAttributeDescription( name="Energy", @@ -121,7 +122,7 @@ SENSORS: Final = { value=lambda value: round(value / 1000, 2), device_class=sensor.DEVICE_CLASS_ENERGY, state_class=sensor.STATE_CLASS_MEASUREMENT, - last_reset=dt.utc_from_timestamp(0), + last_reset=LAST_RESET_NEVER, ), ("emeter", "energyReturned"): BlockAttributeDescription( name="Energy Returned", @@ -129,7 +130,7 @@ SENSORS: Final = { value=lambda value: round(value / 1000, 2), device_class=sensor.DEVICE_CLASS_ENERGY, state_class=sensor.STATE_CLASS_MEASUREMENT, - last_reset=dt.utc_from_timestamp(0), + last_reset=LAST_RESET_NEVER, ), ("light", "energy"): BlockAttributeDescription( name="Energy", @@ -138,6 +139,7 @@ SENSORS: Final = { device_class=sensor.DEVICE_CLASS_ENERGY, state_class=sensor.STATE_CLASS_MEASUREMENT, default_enabled=False, + last_reset=LAST_RESET_UPTIME, ), ("relay", "energy"): BlockAttributeDescription( name="Energy", @@ -145,6 +147,7 @@ SENSORS: Final = { value=lambda value: round(value / 60 / 1000, 2), device_class=sensor.DEVICE_CLASS_ENERGY, state_class=sensor.STATE_CLASS_MEASUREMENT, + last_reset=LAST_RESET_UPTIME, ), ("roller", "rollerEnergy"): BlockAttributeDescription( name="Energy", @@ -152,6 +155,7 @@ SENSORS: Final = { value=lambda value: round(value / 60 / 1000, 2), device_class=sensor.DEVICE_CLASS_ENERGY, state_class=sensor.STATE_CLASS_MEASUREMENT, + last_reset=LAST_RESET_UPTIME, ), ("sensor", "concentration"): BlockAttributeDescription( name="Gas Concentration", @@ -264,7 +268,16 @@ class ShellySensor(ShellyBlockAttributeEntity, SensorEntity): @property def last_reset(self) -> datetime | None: """State class of sensor.""" - return self.description.last_reset + if self.description.last_reset == LAST_RESET_UPTIME: + self._last_value = get_device_uptime( + self.wrapper.device.status, self._last_value + ) + return dt.parse_datetime(self._last_value) + + if self.description.last_reset == LAST_RESET_NEVER: + return dt.utc_from_timestamp(0) + + return None @property def unit_of_measurement(self) -> str | None: diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index d8ce5ae9e45..d1e2947d5ac 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -136,7 +136,7 @@ def is_momentary_input(settings: dict[str, Any], block: aioshelly.Block) -> bool return button_type in ["momentary", "momentary_on_release"] -def get_device_uptime(status: dict[str, Any], last_uptime: str) -> str: +def get_device_uptime(status: dict[str, Any], last_uptime: str | None) -> str: """Return device uptime string, tolerate up to 5 seconds deviation.""" delta_uptime = utcnow() - timedelta(seconds=status["uptime"])