Skip to content
37 changes: 15 additions & 22 deletions homeassistant/components/energy/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
from typing import Any, Final, Literal, TypeVar, cast

from homeassistant.components.sensor import (
ATTR_LAST_RESET,
DEVICE_CLASS_MONETARY,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
SensorEntity,
)
from homeassistant.const import (
Expand All @@ -17,11 +16,10 @@
ENERGY_WATT_HOUR,
VOLUME_CUBIC_METERS,
)
from homeassistant.core import HomeAssistant, State, callback, split_entity_id
from homeassistant.core import HomeAssistant, callback, split_entity_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType

from .const import DOMAIN
from .data import EnergyManager, async_get_manager
Expand Down Expand Up @@ -203,16 +201,15 @@ def __init__(
f"{config[adapter.entity_energy_key]}_{adapter.entity_id_suffix}"
)
self._attr_device_class = DEVICE_CLASS_MONETARY
self._attr_state_class = STATE_CLASS_MEASUREMENT
self._attr_state_class = STATE_CLASS_TOTAL_INCREASING
self._config = config
self._last_energy_sensor_state: State | None = None
self._last_energy_sensor_state: StateType | None = None
self._cur_value = 0.0

def _reset(self, energy_state: State) -> None:
def _reset(self, energy_state: StateType) -> None:
"""Reset the cost sensor."""
self._attr_native_value = 0.0
self._cur_value = 0.0
self._attr_last_reset = dt_util.utcnow()
self._last_energy_sensor_state = energy_state
self.async_write_ha_state()

Expand All @@ -223,7 +220,7 @@ def _update_cost(self) -> None:
cast(str, self._config[self._adapter.entity_energy_key])
)

if energy_state is None or ATTR_LAST_RESET not in energy_state.attributes:
if energy_state is None:
return

try:
Expand Down Expand Up @@ -259,7 +256,7 @@ def _update_cost(self) -> None:

if self._last_energy_sensor_state is None:
# Initialize as it's the first time all required entities are in place.
self._reset(energy_state)
self._reset(energy_state.state)
return

energy_unit = energy_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
Expand All @@ -280,19 +277,15 @@ def _update_cost(self) -> None:
)
return

if (
energy_state.attributes[ATTR_LAST_RESET]
!= self._last_energy_sensor_state.attributes[ATTR_LAST_RESET]
):
if energy < float(self._last_energy_sensor_state):
# Energy meter was reset, reset cost sensor too
self._reset(energy_state)
else:
# Update with newly incurred cost
old_energy_value = float(self._last_energy_sensor_state.state)
self._cur_value += (energy - old_energy_value) * energy_price
self._attr_native_value = round(self._cur_value, 2)
self._reset(0)
# Update with newly incurred cost
old_energy_value = float(self._last_energy_sensor_state)
self._cur_value += (energy - old_energy_value) * energy_price
self._attr_native_value = round(self._cur_value, 2)

self._last_energy_sensor_state = energy_state
self._last_energy_sensor_state = energy_state.state

async def async_added_to_hass(self) -> None:
"""Register callbacks."""
Expand Down
26 changes: 2 additions & 24 deletions homeassistant/components/mill/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

from homeassistant.components.sensor import (
DEVICE_CLASS_ENERGY,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
SensorEntity,
)
from homeassistant.const import ENERGY_KILO_WATT_HOUR
from homeassistant.util import dt as dt_util

from .const import CONSUMPTION_TODAY, CONSUMPTION_YEAR, DOMAIN, MANUFACTURER

Expand All @@ -29,7 +28,7 @@ class MillHeaterEnergySensor(SensorEntity):

_attr_device_class = DEVICE_CLASS_ENERGY
_attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
_attr_state_class = STATE_CLASS_TOTAL
_attr_state_class = STATE_CLASS_TOTAL_INCREASING

def __init__(self, heater, mill_data_connection, sensor_type):
"""Initialize the sensor."""
Expand All @@ -45,16 +44,6 @@ def __init__(self, heater, mill_data_connection, sensor_type):
"manufacturer": MANUFACTURER,
"model": f"generation {1 if heater.is_gen1 else 2}",
}
if self._sensor_type == CONSUMPTION_TODAY:
self._attr_last_reset = dt_util.as_utc(
dt_util.now().replace(hour=0, minute=0, second=0, microsecond=0)
)
elif self._sensor_type == CONSUMPTION_YEAR:
self._attr_last_reset = dt_util.as_utc(
dt_util.now().replace(
month=1, day=1, hour=0, minute=0, second=0, microsecond=0
)
)

async def async_update(self):
"""Retrieve latest state."""
Expand All @@ -71,15 +60,4 @@ async def async_update(self):
self._attr_native_value = _state
return

if self.state is not None and _state < self.state:
if self._sensor_type == CONSUMPTION_TODAY:
self._attr_last_reset = dt_util.as_utc(
dt_util.now().replace(hour=0, minute=0, second=0, microsecond=0)
)
elif self._sensor_type == CONSUMPTION_YEAR:
self._attr_last_reset = dt_util.as_utc(
dt_util.now().replace(
month=1, day=1, hour=0, minute=0, second=0, microsecond=0
)
)
self._attr_native_value = _state
2 changes: 0 additions & 2 deletions homeassistant/components/recorder/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ class StatisticData(TypedDict, total=False):
mean: float
min: float
max: float
last_reset: datetime | None
state: float
sum: float

Expand All @@ -242,7 +241,6 @@ class Statistics(Base): # type: ignore
mean = Column(Float())
min = Column(Float())
max = Column(Float())
last_reset = Column(DATETIME_TYPE)
state = Column(Float())
sum = Column(Float())

Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/recorder/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
Statistics.mean,
Statistics.min,
Statistics.max,
Statistics.last_reset,
Statistics.state,
Statistics.sum,
]
Expand Down Expand Up @@ -375,7 +374,6 @@ def _sorted_statistics_to_dict(
"mean": convert(db_state.mean, units),
"min": convert(db_state.min, units),
"max": convert(db_state.max, units),
"last_reset": _process_timestamp_to_utc_isoformat(db_state.last_reset),
"state": convert(db_state.state, units),
"sum": convert(db_state.sum, units),
}
Expand Down
18 changes: 7 additions & 11 deletions homeassistant/components/sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

_LOGGER: Final = logging.getLogger(__name__)

ATTR_LAST_RESET: Final = "last_reset"
ATTR_LAST_RESET: Final = "last_reset" # Deprecated, to be removed in 2021.11
ATTR_STATE_CLASS: Final = "state_class"

DOMAIN: Final = "sensor"
Expand Down Expand Up @@ -91,14 +91,11 @@

# The state represents a measurement in present time
STATE_CLASS_MEASUREMENT: Final = "measurement"
# The state represents a total amount, e.g. a value of a stock portfolio
STATE_CLASS_TOTAL: Final = "total"
# The state represents a monotonically increasing total, e.g. an amount of consumed gas
STATE_CLASS_TOTAL_INCREASING: Final = "total_increasing"

STATE_CLASSES: Final[list[str]] = [
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
]

Expand Down Expand Up @@ -132,15 +129,15 @@ class SensorEntityDescription(EntityDescription):
"""A class that describes sensor entities."""

state_class: str | None = None
last_reset: datetime | None = None
last_reset: datetime | None = None # Deprecated, to be removed in 2021.11
native_unit_of_measurement: str | None = None


class SensorEntity(Entity):
"""Base class for sensor entities."""

entity_description: SensorEntityDescription
_attr_last_reset: datetime | None
_attr_last_reset: datetime | None # Deprecated, to be removed in 2021.11
_attr_native_unit_of_measurement: str | None
_attr_native_value: StateType = None
_attr_state_class: str | None
Expand All @@ -157,7 +154,7 @@ def state_class(self) -> str | None:
return None

@property
def last_reset(self) -> datetime | None:
def last_reset(self) -> datetime | None: # Deprecated, to be removed in 2021.11
"""Return the time when the sensor was last reset, if any."""
if hasattr(self, "_attr_last_reset"):
return self._attr_last_reset
Expand Down Expand Up @@ -187,10 +184,9 @@ def state_attributes(self) -> dict[str, Any] | None:
report_issue = self._suggest_report_issue()
_LOGGER.warning(
"Entity %s (%s) with state_class %s has set last_reset. Setting "
"last_reset for entities with state_class other than 'total' is "
"deprecated and will be removed from Home Assistant Core 2021.10. "
"Please update your configuration if state_class is manually "
"configured, otherwise %s",
"last_reset is deprecated and will be unsupported from Home "
"Assistant Core 2021.11. Please update your configuration if "
"state_class is manually configured, otherwise %s",
Comment on lines +188 to +189
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Assistant Core 2021.11. Please update your configuration if "
"state_class is manually configured, otherwise %s",
"Assistant Core 2021.11. Please update your configuration and set state class to 'total_increasing' if "
"state_class is manually configured, otherwise %s",

self.entity_id,
type(self),
self.state_class,
Expand Down
41 changes: 11 additions & 30 deletions homeassistant/components/sensor/recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
STATE_CLASSES,
)
Expand All @@ -43,7 +42,6 @@
VOLUME_CUBIC_METERS,
)
from homeassistant.core import HomeAssistant, State
import homeassistant.util.dt as dt_util
import homeassistant.util.pressure as pressure_util
import homeassistant.util.temperature as temperature_util
import homeassistant.util.volume as volume_util
Expand All @@ -53,26 +51,22 @@
_LOGGER = logging.getLogger(__name__)

DEVICE_CLASS_OR_UNIT_STATISTICS = {
STATE_CLASS_TOTAL: {
DEVICE_CLASS_ENERGY: {"sum"},
DEVICE_CLASS_GAS: {"sum"},
DEVICE_CLASS_MONETARY: {"sum"},
},
STATE_CLASS_MEASUREMENT: {
DEVICE_CLASS_BATTERY: {"mean", "min", "max"},
DEVICE_CLASS_HUMIDITY: {"mean", "min", "max"},
DEVICE_CLASS_POWER: {"mean", "min", "max"},
DEVICE_CLASS_PRESSURE: {"mean", "min", "max"},
DEVICE_CLASS_TEMPERATURE: {"mean", "min", "max"},
PERCENTAGE: {"mean", "min", "max"},
# Deprecated, support will be removed in Home Assistant 2021.10
# Deprecated, support will be removed in Home Assistant 2021.11
DEVICE_CLASS_ENERGY: {"sum"},
DEVICE_CLASS_GAS: {"sum"},
DEVICE_CLASS_MONETARY: {"sum"},
},
STATE_CLASS_TOTAL_INCREASING: {
DEVICE_CLASS_ENERGY: {"sum"},
DEVICE_CLASS_GAS: {"sum"},
DEVICE_CLASS_MONETARY: {"sum"},
},
}

Expand Down Expand Up @@ -279,13 +273,11 @@ def compile_statistics(
stat["mean"] = _time_weighted_average(fstates, start, end)

if "sum" in wanted_statistics:
last_reset = old_last_reset = None
new_state = old_state = None
_sum = 0
last_stats = statistics.get_last_statistics(hass, 1, entity_id)
if entity_id in last_stats:
# We have compiled history for this sensor before, use that as a starting point
last_reset = old_last_reset = last_stats[entity_id][0]["last_reset"]
new_state = old_state = last_stats[entity_id][0]["state"]
_sum = last_stats[entity_id][0]["sum"]

Expand All @@ -299,13 +291,7 @@ def compile_statistics(
continue

reset = False
if (
state_class != STATE_CLASS_TOTAL_INCREASING
and (last_reset := state.attributes.get("last_reset"))
!= old_last_reset
):
reset = True
elif old_state is None and last_reset is None:
if old_state is None:
reset = True
elif state_class == STATE_CLASS_TOTAL_INCREASING and (
old_state is None or (new_state is not None and fstate < new_state)
Expand All @@ -318,30 +304,21 @@ def compile_statistics(
_sum += new_state - old_state
# ..and update the starting point
new_state = fstate
old_last_reset = last_reset
# Force a new cycle for STATE_CLASS_TOTAL_INCREASING to start at 0
if state_class == STATE_CLASS_TOTAL_INCREASING and old_state:
old_state = 0
# Force a new cycle to start at 0
if old_state is not None:
old_state = 0.0
else:
old_state = new_state
else:
new_state = fstate

# Deprecated, will be removed in Home Assistant 2021.10
if last_reset is None and state_class == STATE_CLASS_MEASUREMENT:
# No valid updates
result.pop(entity_id)
continue

if new_state is None or old_state is None:
# No valid updates
result.pop(entity_id)
continue

# Update the sum with the last state
_sum += new_state - old_state
if last_reset is not None:
stat["last_reset"] = dt_util.parse_datetime(last_reset)
stat["sum"] = _sum
stat["state"] = new_state

Expand All @@ -365,7 +342,11 @@ def list_statistic_ids(hass: HomeAssistant, statistic_type: str | None = None) -
state = hass.states.get(entity_id)
assert state

if "sum" in provided_statistics and ATTR_LAST_RESET not in state.attributes:
if (
"sum" in provided_statistics
and ATTR_LAST_RESET not in state.attributes
and state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
):
continue

native_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
Expand Down
Loading