Don't create statistics issues when sensor is unavailable or unknown (#127226)

pull/127191/head
Erik Montnemery 2024-10-01 22:08:48 +02:00 committed by GitHub
parent f4ab741445
commit 6d65d6bcf6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 87 additions and 3 deletions

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable, Iterable
from contextlib import suppress
import datetime
from functools import partial
import itertools
@ -179,6 +180,14 @@ def _entity_history_to_float_and_state(
return float_states
def _is_numeric(state: State) -> bool:
"""Return if the state is numeric."""
with suppress(ValueError, TypeError):
if (num_state := float(state.state)) is not None and math.isfinite(num_state):
return True
return False
def _normalize_states(
hass: HomeAssistant,
old_metadatas: dict[str, tuple[int, StatisticMetaData]],
@ -684,13 +693,14 @@ def _update_issues(
"""Update repair issues."""
for state in sensor_states:
entity_id = state.entity_id
numeric = _is_numeric(state)
state_class = try_parse_enum(
SensorStateClass, state.attributes.get(ATTR_STATE_CLASS)
)
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if metadata := metadatas.get(entity_id):
if state_class is None:
if numeric and state_class is None:
# Sensor no longer has a valid state class
report_issue(
"state_class_removed",
@ -703,7 +713,7 @@ def _update_issues(
metadata_unit = metadata[1]["unit_of_measurement"]
converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER.get(metadata_unit)
if not converter:
if not _equivalent_units({state_unit, metadata_unit}):
if numeric and not _equivalent_units({state_unit, metadata_unit}):
# The unit has changed, and it's not possible to convert
report_issue(
"units_changed",
@ -717,7 +727,7 @@ def _update_issues(
)
else:
clear_issue("units_changed", entity_id)
elif state_unit not in converter.VALID_UNITS:
elif numeric and state_unit not in converter.VALID_UNITS:
# The state unit can't be converted to the unit in metadata
valid_units = (unit or "<None>" for unit in converter.VALID_UNITS)
valid_units_str = ", ".join(sorted(valid_units))

View File

@ -4332,6 +4332,26 @@ async def test_validate_unit_change_convertible(
}
await assert_validation_result(hass, client, expected, {"units_changed"})
# Unavailable state - empty response
hass.states.async_set(
"sensor.test",
"unavailable",
attributes={**attributes, "unit_of_measurement": "dogs"},
timestamp=now.timestamp(),
)
await async_recorder_block_till_done(hass)
await assert_validation_result(hass, client, {}, {})
# Unknown state - empty response
hass.states.async_set(
"sensor.test",
"unknown",
attributes={**attributes, "unit_of_measurement": "dogs"},
timestamp=now.timestamp(),
)
await async_recorder_block_till_done(hass)
await assert_validation_result(hass, client, {}, {})
# Valid state - empty response
hass.states.async_set(
"sensor.test",
@ -4531,6 +4551,26 @@ async def test_validate_statistics_unit_change_no_device_class(
}
await assert_validation_result(hass, client, expected, {"units_changed"})
# Unavailable state - empty response
hass.states.async_set(
"sensor.test",
"unavailable",
attributes={**attributes, "unit_of_measurement": "dogs"},
timestamp=now.timestamp(),
)
await async_recorder_block_till_done(hass)
await assert_validation_result(hass, client, {}, {})
# Unknown state - empty response
hass.states.async_set(
"sensor.test",
"unknown",
attributes={**attributes, "unit_of_measurement": "dogs"},
timestamp=now.timestamp(),
)
await async_recorder_block_till_done(hass)
await assert_validation_result(hass, client, {}, {})
# Valid state - empty response
hass.states.async_set(
"sensor.test",
@ -4627,6 +4667,20 @@ async def test_validate_statistics_state_class_removed(
}
await assert_validation_result(hass, client, expected, {"state_class_removed"})
# Unavailable state - empty response
hass.states.async_set(
"sensor.test", "unavailable", attributes=_attributes, timestamp=now.timestamp()
)
await async_recorder_block_till_done(hass)
await assert_validation_result(hass, client, {}, {})
# Unknown state - empty response
hass.states.async_set(
"sensor.test", "unknown", attributes=_attributes, timestamp=now.timestamp()
)
await async_recorder_block_till_done(hass)
await assert_validation_result(hass, client, {}, {})
@pytest.mark.parametrize(
("units", "attributes", "unit"),
@ -4871,6 +4925,26 @@ async def test_validate_statistics_unit_change_no_conversion(
}
await assert_validation_result(hass, client, expected, {"units_changed"})
# Unavailable state - empty response
hass.states.async_set(
"sensor.test",
"unavailable",
attributes={**attributes, "unit_of_measurement": unit2},
timestamp=now.timestamp(),
)
await async_recorder_block_till_done(hass)
await assert_validation_result(hass, client, {}, {})
# Unknown state - empty response
hass.states.async_set(
"sensor.test",
"unknown",
attributes={**attributes, "unit_of_measurement": unit2},
timestamp=now.timestamp(),
)
await async_recorder_block_till_done(hass)
await assert_validation_result(hass, client, {}, {})
# Original unit - empty response
hass.states.async_set(
"sensor.test",