Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions homeassistant/components/sensor/recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ class _StatisticsConfig:
# Keep track of entities for which a warning about unsupported unit has been logged
WARN_UNSUPPORTED_UNIT: HassKey[set[str]] = HassKey(f"{DOMAIN}_warn_unsupported_unit")
WARN_UNSTABLE_UNIT: HassKey[set[str]] = HassKey(f"{DOMAIN}_warn_unstable_unit")
# Keep track of entities for which a change in unit has been observed
SEEN_CHANGED_UNIT: HassKey[set[str]] = HassKey(f"{DOMAIN}_seen_changed_unit")
# Keep track of entities for which a warning about statistics mean algorithm change has been logged
WARN_STATISTICS_MEAN_CHANGED: HassKey[set[str]] = HassKey(
f"{DOMAIN}_warn_statistics_mean_change"
Expand Down Expand Up @@ -279,11 +281,12 @@ def _normalize_states(
"""Normalize units."""
state_unit: str | None = None
statistics_unit: str | None
state_unit = fstates[0][1].attributes.get(ATTR_UNIT_OF_MEASUREMENT)
device_class = fstates[0][1].attributes.get(ATTR_DEVICE_CLASS)
state_unit = fstates[-1][1].attributes.get(ATTR_UNIT_OF_MEASUREMENT)
all_units = _get_units(fstates)
device_class = fstates[-1][1].attributes.get(ATTR_DEVICE_CLASS)
old_metadata = old_metadatas[entity_id][1] if entity_id in old_metadatas else None
if not old_metadata:
# We've not seen this sensor before, the first valid state determines the unit
# We've not seen this sensor before, the most recent valid state determines the unit
# used for statistics
statistics_unit = state_unit
unit_class = _get_unit_class(device_class, state_unit)
Expand All @@ -305,7 +308,6 @@ def _normalize_states(
if not (converter := _get_unit_converter(unit_class)):
# The unit used by this sensor doesn't support unit conversion

all_units = _get_units(fstates)
if not _equivalent_units(all_units):
if WARN_UNSTABLE_UNIT not in hass.data:
hass.data[WARN_UNSTABLE_UNIT] = set()
Expand All @@ -332,20 +334,34 @@ def _normalize_states(

if state_unit != statistics_unit:
unit_class = _get_unit_class(
fstates[0][1].attributes.get(ATTR_DEVICE_CLASS),
fstates[-1][1].attributes.get(ATTR_DEVICE_CLASS),
state_unit,
)
return unit_class, state_unit, fstates

valid_fstates: list[tuple[float, State]] = []
convert: Callable[[float], float] | None = None
last_unit: str | None | UndefinedType = UNDEFINED
valid_units = converter.VALID_UNITS

for fstate, state in fstates:
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
# Exclude states with unsupported unit from statistics
if state_unit not in valid_units:
if any(unit not in valid_units for unit in all_units):
states_by_unit: list[list[tuple[float, State]]] = [
list(states)
for _, states in itertools.groupby(
fstates, key=lambda x: x[1].attributes.get(ATTR_UNIT_OF_MEASUREMENT)
)
]

if SEEN_CHANGED_UNIT not in hass.data:
hass.data[SEEN_CHANGED_UNIT] = set()

if (
len(states_by_unit) == 2
and state_unit == statistics_unit
and entity_id not in hass.data[SEEN_CHANGED_UNIT]
):
# Potential unit migration, silently drop states with unexpected units once
hass.data[SEEN_CHANGED_UNIT].add(entity_id)
fstates = states_by_unit[1]
else:
# Exclude states with unsupported unit from statistics
if WARN_UNSUPPORTED_UNIT not in hass.data:
hass.data[WARN_UNSUPPORTED_UNIT] = set()
if entity_id not in hass.data[WARN_UNSUPPORTED_UNIT]:
Expand All @@ -363,8 +379,18 @@ def _normalize_states(
statistics_unit,
LINK_DEV_STATISTICS,
)
continue
fstates = [
(fstate, state)
for fstate, state in fstates
if state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) in valid_units
]

valid_fstates: list[tuple[float, State]] = []
convert: Callable[[float], float] | None = None
last_unit: str | None | UndefinedType = UNDEFINED

for fstate, state in fstates:
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if state_unit != last_unit:
# The unit of measurement has changed since the last state change
# recreate the converter factory
Expand Down
Loading