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:
parent
821b93b0d0
commit
029873a088
5 changed files with 296 additions and 28 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue