Add support for total and total_increasing sensor state classes (#54523)

* Add support for amount and meter sensor state classes

* Ignore last_reset for STATE_CLASS_METER sensors

* Update tests

* Rename STATE_CLASS_METER to STATE_CLASS_AMOUNT_INCREASING

* Rename STATE_CLASS_AMOUNT to STATE_CLASS_TOTAL

* Fix typo

* Log warning if last_reset set together with state_class measurement

* Fix warning message
This commit is contained in:
Erik Montnemery 2021-08-13 12:35:23 +02:00 committed by GitHub
parent 821b93b0d0
commit 029873a088
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 296 additions and 28 deletions

View file

@ -17,6 +17,9 @@ from homeassistant.components.sensor import (
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL,
STATE_CLASS_TOTAL_INCREASING,
STATE_CLASSES,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
@ -50,15 +53,27 @@ from . import ATTR_LAST_RESET, DOMAIN
_LOGGER = logging.getLogger(__name__)
DEVICE_CLASS_OR_UNIT_STATISTICS = {
DEVICE_CLASS_BATTERY: {"mean", "min", "max"},
DEVICE_CLASS_ENERGY: {"sum"},
DEVICE_CLASS_HUMIDITY: {"mean", "min", "max"},
DEVICE_CLASS_MONETARY: {"sum"},
DEVICE_CLASS_POWER: {"mean", "min", "max"},
DEVICE_CLASS_PRESSURE: {"mean", "min", "max"},
DEVICE_CLASS_TEMPERATURE: {"mean", "min", "max"},
DEVICE_CLASS_GAS: {"sum"},
PERCENTAGE: {"mean", "min", "max"},
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
DEVICE_CLASS_ENERGY: {"sum"},
DEVICE_CLASS_GAS: {"sum"},
DEVICE_CLASS_MONETARY: {"sum"},
},
STATE_CLASS_TOTAL_INCREASING: {
DEVICE_CLASS_ENERGY: {"sum"},
DEVICE_CLASS_GAS: {"sum"},
},
}
# Normalized units which will be stored in the statistics table
@ -109,24 +124,28 @@ UNIT_CONVERSIONS: dict[str, dict[str, Callable]] = {
WARN_UNSUPPORTED_UNIT = set()
def _get_entities(hass: HomeAssistant) -> list[tuple[str, str]]:
"""Get (entity_id, device_class) of all sensors for which to compile statistics."""
def _get_entities(hass: HomeAssistant) -> list[tuple[str, str, str]]:
"""Get (entity_id, state_class, key) of all sensors for which to compile statistics.
Key is either a device class or a unit and is used to index the
DEVICE_CLASS_OR_UNIT_STATISTICS map.
"""
all_sensors = hass.states.all(DOMAIN)
entity_ids = []
for state in all_sensors:
if state.attributes.get(ATTR_STATE_CLASS) != STATE_CLASS_MEASUREMENT:
if (state_class := state.attributes.get(ATTR_STATE_CLASS)) not in STATE_CLASSES:
continue
if (
key := state.attributes.get(ATTR_DEVICE_CLASS)
) in DEVICE_CLASS_OR_UNIT_STATISTICS:
entity_ids.append((state.entity_id, key))
) in DEVICE_CLASS_OR_UNIT_STATISTICS[state_class]:
entity_ids.append((state.entity_id, state_class, key))
if (
key := state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
) in DEVICE_CLASS_OR_UNIT_STATISTICS:
entity_ids.append((state.entity_id, key))
) in DEVICE_CLASS_OR_UNIT_STATISTICS[state_class]:
entity_ids.append((state.entity_id, state_class, key))
return entity_ids
@ -228,8 +247,8 @@ def compile_statistics(
hass, start - datetime.timedelta.resolution, end, [i[0] for i in entities]
)
for entity_id, key in entities:
wanted_statistics = DEVICE_CLASS_OR_UNIT_STATISTICS[key]
for entity_id, state_class, key in entities:
wanted_statistics = DEVICE_CLASS_OR_UNIT_STATISTICS[state_class][key]
if entity_id not in history_list:
continue
@ -272,9 +291,28 @@ def compile_statistics(
for fstate, state in fstates:
if "last_reset" not in state.attributes:
# Deprecated, will be removed in Home Assistant 2021.10
if (
"last_reset" not in state.attributes
and state_class == STATE_CLASS_MEASUREMENT
):
continue
if (last_reset := state.attributes["last_reset"]) != old_last_reset:
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:
reset = True
elif state_class == STATE_CLASS_TOTAL_INCREASING and (
old_state is None or fstate < old_state
):
reset = True
if reset:
# The sensor has been reset, update the sum
if old_state is not None:
_sum += new_state - old_state
@ -285,14 +323,21 @@ def compile_statistics(
else:
new_state = fstate
if last_reset is None or new_state is None or old_state is None:
# 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
stat["last_reset"] = dt_util.parse_datetime(last_reset)
if last_reset is not None:
stat["last_reset"] = dt_util.parse_datetime(last_reset)
stat["sum"] = _sum
stat["state"] = new_state
@ -307,8 +352,8 @@ def list_statistic_ids(hass: HomeAssistant, statistic_type: str | None = None) -
statistic_ids = {}
for entity_id, key in entities:
provided_statistics = DEVICE_CLASS_OR_UNIT_STATISTICS[key]
for entity_id, state_class, key in entities:
provided_statistics = DEVICE_CLASS_OR_UNIT_STATISTICS[state_class][key]
if statistic_type is not None and statistic_type not in provided_statistics:
continue