Correct how unit used for statistics is determined (#79725)

This commit is contained in:
Erik Montnemery 2022-10-06 20:01:54 +02:00 committed by GitHub
parent e2c1a36e24
commit 0a59d37e62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 33 additions and 26 deletions

View File

@ -149,13 +149,20 @@ def _normalize_states(
state_unit = fstates[0][1].attributes.get(ATTR_UNIT_OF_MEASUREMENT) state_unit = fstates[0][1].attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if state_unit not in statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER or ( statistics_unit: str | None
old_metadata if not old_metadata:
and old_metadata["unit_of_measurement"] # We've not seen this sensor before, the first valid state determines the unit
not in statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER # used for statistics
statistics_unit = state_unit
else:
# We have seen this sensor before, use the unit from metadata
statistics_unit = old_metadata["unit_of_measurement"]
if (
not statistics_unit
or statistics_unit not in statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER
): ):
# We're either not normalizing this device class or this entity is not stored # The unit used by this sensor doesn't support unit conversion
# in a unit which can be converted, return the states as they are
all_units = _get_units(fstates) all_units = _get_units(fstates)
if len(all_units) > 1: if len(all_units) > 1:
@ -182,13 +189,9 @@ def _normalize_states(
state_unit = fstates[0][1].attributes.get(ATTR_UNIT_OF_MEASUREMENT) state_unit = fstates[0][1].attributes.get(ATTR_UNIT_OF_MEASUREMENT)
return state_unit, state_unit, fstates return state_unit, state_unit, fstates
converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER[state_unit] converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER[statistics_unit]
valid_fstates: list[tuple[float, State]] = [] valid_fstates: list[tuple[float, State]] = []
statistics_unit: str | None = None
if old_metadata:
statistics_unit = old_metadata["unit_of_measurement"]
for fstate, state in fstates: for fstate, state in fstates:
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
# Exclude states with unsupported unit from statistics # Exclude states with unsupported unit from statistics
@ -198,14 +201,18 @@ def _normalize_states(
if entity_id not in hass.data[WARN_UNSUPPORTED_UNIT]: if entity_id not in hass.data[WARN_UNSUPPORTED_UNIT]:
hass.data[WARN_UNSUPPORTED_UNIT].add(entity_id) hass.data[WARN_UNSUPPORTED_UNIT].add(entity_id)
_LOGGER.warning( _LOGGER.warning(
"%s has unit %s which can't be converted to %s", "The unit of %s (%s) can not be converted to the unit of previously "
"compiled statistics (%s). Generation of long term statistics "
"will be suppressed unless the unit changes back to %s or a "
"compatible unit. "
"Go to %s to fix this",
entity_id, entity_id,
state_unit, state_unit,
statistics_unit, statistics_unit,
statistics_unit,
LINK_DEV_STATISTICS,
) )
continue continue
if statistics_unit is None:
statistics_unit = state_unit
valid_fstates.append( valid_fstates.append(
( (

View File

@ -1900,12 +1900,13 @@ def test_list_statistic_ids_unsupported(hass_recorder, caplog, _attributes):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"device_class, state_unit, display_unit, statistics_unit, unit_class, mean, min, max", "device_class, state_unit, state_unit2, unit_class, mean, min, max",
[ [
(None, None, None, None, None, 13.050847, -10, 30), (None, None, "cats", None, 13.050847, -10, 30),
(None, "%", "%", "%", None, 13.050847, -10, 30), (None, "%", "cats", None, 13.050847, -10, 30),
("battery", "%", "%", "%", None, 13.050847, -10, 30), ("battery", "%", "cats", None, 13.050847, -10, 30),
("battery", None, None, None, None, 13.050847, -10, 30), ("battery", None, "cats", None, 13.050847, -10, 30),
(None, "kW", "Wh", "power", 13.050847, -10, 30),
], ],
) )
def test_compile_hourly_statistics_changing_units_1( def test_compile_hourly_statistics_changing_units_1(
@ -1913,8 +1914,7 @@ def test_compile_hourly_statistics_changing_units_1(
caplog, caplog,
device_class, device_class,
state_unit, state_unit,
display_unit, state_unit2,
statistics_unit,
unit_class, unit_class,
mean, mean,
min, min,
@ -1931,7 +1931,7 @@ def test_compile_hourly_statistics_changing_units_1(
"unit_of_measurement": state_unit, "unit_of_measurement": state_unit,
} }
four, states = record_states(hass, zero, "sensor.test1", attributes) four, states = record_states(hass, zero, "sensor.test1", attributes)
attributes["unit_of_measurement"] = "cats" attributes["unit_of_measurement"] = state_unit2
four, _states = record_states( four, _states = record_states(
hass, zero + timedelta(minutes=5), "sensor.test1", attributes hass, zero + timedelta(minutes=5), "sensor.test1", attributes
) )
@ -1954,7 +1954,7 @@ def test_compile_hourly_statistics_changing_units_1(
"has_sum": False, "has_sum": False,
"name": None, "name": None,
"source": "recorder", "source": "recorder",
"statistics_unit_of_measurement": statistics_unit, "statistics_unit_of_measurement": state_unit,
"unit_class": unit_class, "unit_class": unit_class,
}, },
] ]
@ -1978,8 +1978,8 @@ def test_compile_hourly_statistics_changing_units_1(
do_adhoc_statistics(hass, start=zero + timedelta(minutes=10)) do_adhoc_statistics(hass, start=zero + timedelta(minutes=10))
wait_recording_done(hass) wait_recording_done(hass)
assert ( assert (
"The unit of sensor.test1 (cats) can not be converted to the unit of " f"The unit of sensor.test1 ({state_unit2}) can not be converted to the unit of "
f"previously compiled statistics ({display_unit})" in caplog.text f"previously compiled statistics ({state_unit})" in caplog.text
) )
statistic_ids = list_statistic_ids(hass) statistic_ids = list_statistic_ids(hass)
assert statistic_ids == [ assert statistic_ids == [
@ -1989,7 +1989,7 @@ def test_compile_hourly_statistics_changing_units_1(
"has_sum": False, "has_sum": False,
"name": None, "name": None,
"source": "recorder", "source": "recorder",
"statistics_unit_of_measurement": statistics_unit, "statistics_unit_of_measurement": state_unit,
"unit_class": unit_class, "unit_class": unit_class,
}, },
] ]