Use numerical_value when compiling statistics

This commit is contained in:
Erik
2023-02-01 14:03:33 +01:00
parent 65324431a4
commit 2f608d8650
4 changed files with 288 additions and 116 deletions

View File

@@ -6,7 +6,6 @@ from collections.abc import Iterable, MutableMapping
import datetime
import itertools
import logging
import math
from typing import Any
from sqlalchemy.orm.session import Session
@@ -37,6 +36,7 @@ from homeassistant.util import dt as dt_util
from . import (
ATTR_LAST_RESET,
ATTR_NUMERICAL_VALUE,
ATTR_OPTIONS,
ATTR_STATE_CLASS,
DOMAIN,
@@ -142,14 +142,6 @@ def _equivalent_units(units: set[str | None]) -> bool:
return len(units) == 1
def _parse_float(state: str) -> float:
"""Parse a float string, throw on inf or nan."""
fstate = float(state)
if math.isnan(fstate) or math.isinf(fstate):
raise ValueError
return fstate
def _normalize_states(
hass: HomeAssistant,
session: Session,
@@ -163,9 +155,7 @@ def _normalize_states(
fstates: list[tuple[float, State]] = []
for state in entity_history:
try:
fstate = _parse_float(state.state)
except (ValueError, TypeError): # TypeError to guard for NULL state in DB
if (fstate := state.attributes.get(ATTR_NUMERICAL_VALUE)) is None:
continue
fstates.append((fstate, state))
@@ -298,7 +288,7 @@ def warn_dip(
),
entity_id,
f"from integration {domain} " if domain else "",
state.state,
state.attributes[ATTR_NUMERICAL_VALUE],
previous_fstate,
state.last_updated.isoformat(),
_suggest_report_issue(hass, entity_id),
@@ -319,7 +309,7 @@ def warn_negative(hass: HomeAssistant, entity_id: str, state: State) -> None:
),
entity_id,
f"from integration {domain} " if domain else "",
state.state,
state.attributes[ATTR_NUMERICAL_VALUE],
state.last_updated.isoformat(),
_suggest_report_issue(hass, entity_id),
)
@@ -424,6 +414,7 @@ def _compile_statistics( # noqa: C901
start - datetime.timedelta.resolution,
end,
entity_ids=entities_significant_history,
significant_changes_only=False,
)
history_list = {**history_list, **_history_list}
# If there are no recent state changes, the sensor's state may already be pruned

View File

@@ -55,7 +55,9 @@ def test_compile_hourly_statistics(hass_recorder):
instance = recorder.get_instance(hass)
setup_component(hass, "sensor", {})
zero, four, states = record_states(hass)
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
# Should not fail if there is nothing there yet
@@ -316,7 +318,9 @@ def test_rename_entity(hass_recorder):
hass.block_till_done()
zero, four, states = record_states(hass)
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
for kwargs in ({}, {"statistic_ids": ["sensor.test1"]}):
@@ -382,7 +386,9 @@ def test_rename_entity_collision(hass_recorder, caplog):
hass.block_till_done()
zero, four, states = record_states(hass)
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
for kwargs in ({}, {"statistic_ids": ["sensor.test1"]}):
@@ -447,7 +453,9 @@ def test_statistics_duplicated(hass_recorder, caplog):
hass = hass_recorder()
setup_component(hass, "sensor", {})
zero, four, states = record_states(hass)
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
wait_recording_done(hass)
@@ -1709,6 +1717,14 @@ def record_states(hass):
wait_recording_done(hass)
return hass.states.get(entity_id)
def set_sensor_state(entity_id, numerical_value, attributes):
"""Set the state."""
hass.states.set(
entity_id, "", attributes={**attributes, "numerical_value": numerical_value}
)
wait_recording_done(hass)
return hass.states.get(entity_id)
zero = dt_util.utcnow()
one = zero + timedelta(seconds=1 * 5)
two = one + timedelta(seconds=15 * 5)
@@ -1725,25 +1741,25 @@ def record_states(hass):
states[mp].append(
set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt2)})
)
states[sns1].append(set_state(sns1, "10", attributes=sns1_attr))
states[sns2].append(set_state(sns2, "10", attributes=sns2_attr))
states[sns3].append(set_state(sns3, "10", attributes=sns3_attr))
states[sns4].append(set_state(sns4, "10", attributes=sns4_attr))
states[sns1].append(set_sensor_state(sns1, 10, attributes=sns1_attr))
states[sns2].append(set_sensor_state(sns2, 10, attributes=sns2_attr))
states[sns3].append(set_sensor_state(sns3, 10, attributes=sns3_attr))
states[sns4].append(set_sensor_state(sns4, 10, attributes=sns4_attr))
with patch(
"homeassistant.components.recorder.core.dt_util.utcnow", return_value=two
):
states[sns1].append(set_state(sns1, "15", attributes=sns1_attr))
states[sns2].append(set_state(sns2, "15", attributes=sns2_attr))
states[sns3].append(set_state(sns3, "15", attributes=sns3_attr))
states[sns4].append(set_state(sns4, "15", attributes=sns4_attr))
states[sns1].append(set_sensor_state(sns1, 15, attributes=sns1_attr))
states[sns2].append(set_sensor_state(sns2, 15, attributes=sns2_attr))
states[sns3].append(set_sensor_state(sns3, 15, attributes=sns3_attr))
states[sns4].append(set_sensor_state(sns4, 15, attributes=sns4_attr))
with patch(
"homeassistant.components.recorder.core.dt_util.utcnow", return_value=three
):
states[sns1].append(set_state(sns1, "20", attributes=sns1_attr))
states[sns2].append(set_state(sns2, "20", attributes=sns2_attr))
states[sns3].append(set_state(sns3, "20", attributes=sns3_attr))
states[sns4].append(set_state(sns4, "20", attributes=sns4_attr))
states[sns1].append(set_sensor_state(sns1, 20, attributes=sns1_attr))
states[sns2].append(set_sensor_state(sns2, 20, attributes=sns2_attr))
states[sns3].append(set_sensor_state(sns3, 20, attributes=sns3_attr))
states[sns4].append(set_sensor_state(sns4, 20, attributes=sns4_attr))
return zero, four, states

View File

@@ -17,6 +17,7 @@ from homeassistant.components.recorder.statistics import (
get_metadata,
list_statistic_ids,
)
from homeassistant.components.sensor.const import ATTR_NUMERICAL_VALUE
from homeassistant.helpers import recorder as recorder_helper
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
@@ -131,7 +132,11 @@ async def test_statistics_during_period(recorder_mock, hass, hass_ws_client):
hass.config.units = US_CUSTOMARY_SYSTEM
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test", 10, attributes=POWER_SENSOR_KW_ATTRIBUTES)
hass.states.async_set(
"sensor.test",
"",
attributes=POWER_SENSOR_KW_ATTRIBUTES | {ATTR_NUMERICAL_VALUE: 10},
)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
@@ -892,7 +897,9 @@ async def test_statistics_during_period_unit_conversion(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test", state, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: state}
)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
@@ -983,8 +990,12 @@ async def test_sum_statistics_during_period_unit_conversion(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test", 0, attributes=attributes)
hass.states.async_set("sensor.test", state, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: 0}
)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: state}
)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
@@ -1112,7 +1123,11 @@ async def test_statistics_during_period_in_the_past(
past = now - timedelta(days=3)
with freeze_time(past):
hass.states.async_set("sensor.test", 10, attributes=POWER_SENSOR_KW_ATTRIBUTES)
hass.states.async_set(
"sensor.test",
"",
attributes=POWER_SENSOR_KW_ATTRIBUTES | {ATTR_NUMERICAL_VALUE: 10},
)
await async_wait_recording_done(hass)
sensor_state = hass.states.get("sensor.test")
@@ -1340,7 +1355,9 @@ async def test_list_statistic_ids(
assert response["success"]
assert response["result"] == []
hass.states.async_set("sensor.test", 10, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: 10}
)
await async_wait_recording_done(hass)
await client.send_json({"id": 2, "type": "recorder/list_statistic_ids"})
@@ -1503,7 +1520,9 @@ async def test_list_statistic_ids_unit_change(
assert response["success"]
assert response["result"] == []
hass.states.async_set("sensor.test", 10, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: 10}
)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
@@ -1526,7 +1545,9 @@ async def test_list_statistic_ids_unit_change(
]
# Change the state unit
hass.states.async_set("sensor.test", 10, attributes=attributes2)
hass.states.async_set(
"sensor.test", "", attributes=attributes2 | {ATTR_NUMERICAL_VALUE: 10}
)
await client.send_json({"id": 3, "type": "recorder/list_statistic_ids"})
response = await client.receive_json()
@@ -1579,9 +1600,15 @@ async def test_clear_statistics(recorder_mock, hass, hass_ws_client):
hass.config.units = units
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test1", state, attributes=attributes)
hass.states.async_set("sensor.test2", state * 2, attributes=attributes)
hass.states.async_set("sensor.test3", state * 3, attributes=attributes)
hass.states.async_set(
"sensor.test1", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: state}
)
hass.states.async_set(
"sensor.test2", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: state * 2}
)
hass.states.async_set(
"sensor.test3", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: state * 3}
)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, start=now)
@@ -1704,7 +1731,9 @@ async def test_update_statistics_metadata(
hass.config.units = units
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test", state, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: state}
)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, period="hourly", start=now)
@@ -1795,7 +1824,9 @@ async def test_change_statistics_unit(recorder_mock, hass, hass_ws_client):
hass.config.units = units
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test", state, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: state}
)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, period="hourly", start=now)
@@ -1968,7 +1999,9 @@ async def test_change_statistics_unit_errors(
hass.config.units = units
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.test", state, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: state}
)
await async_wait_recording_done(hass)
do_adhoc_statistics(hass, period="hourly", start=now)
@@ -2311,10 +2344,14 @@ async def test_get_statistics_metadata(
}
]
hass.states.async_set("sensor.test", 10, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: 10}
)
await async_wait_recording_done(hass)
hass.states.async_set("sensor.test2", 10, attributes=attributes)
hass.states.async_set(
"sensor.test2", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: 10}
)
await async_wait_recording_done(hass)
await client.send_json(

View File

@@ -25,7 +25,11 @@ from homeassistant.components.recorder.statistics import (
list_statistic_ids,
)
from homeassistant.components.recorder.util import get_instance, session_scope
from homeassistant.components.sensor import ATTR_OPTIONS, DOMAIN
from homeassistant.components.sensor.const import (
ATTR_NUMERICAL_VALUE,
ATTR_OPTIONS,
DOMAIN,
)
from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant, State
from homeassistant.setup import async_setup_component, setup_component
@@ -140,7 +144,9 @@ def test_compile_hourly_statistics(
"unit_of_measurement": state_unit,
}
four, states = record_states(hass, zero, "sensor.test1", attributes)
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=zero)
@@ -202,10 +208,12 @@ def test_compile_hourly_statistics_purged_state_changes(
"unit_of_measurement": state_unit,
}
four, states = record_states(hass, zero, "sensor.test1", attributes)
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
mean = min = max = float(hist["sensor.test1"][-1].state)
mean = min = max = hist["sensor.test1"][-1].attributes[ATTR_NUMERICAL_VALUE]
# Purge all states from the database
with patch(
@@ -214,7 +222,9 @@ def test_compile_hourly_statistics_purged_state_changes(
hass.services.call("recorder", "purge", {"keep_days": 0})
hass.block_till_done()
wait_recording_done(hass)
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert not hist
do_adhoc_statistics(hass, start=zero)
@@ -283,7 +293,9 @@ def test_compile_hourly_statistics_wrong_unit(hass_recorder, caplog, attributes)
_, _states = record_states(hass, zero, "sensor.test7", attributes_tmp)
states = {**states, **_states}
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=zero)
@@ -474,7 +486,10 @@ async def test_compile_hourly_sum_statistics_amount(
)
await async_wait_recording_done(hass)
hist = history.get_significant_states(
hass, period0 - timedelta.resolution, eight + timedelta.resolution
hass,
period0 - timedelta.resolution,
eight + timedelta.resolution,
significant_changes_only=False,
)
assert_multiple_states_equal_without_context_and_last_changed(
dict(states)["sensor.test1"], dict(hist)["sensor.test1"]
@@ -857,6 +872,11 @@ def test_compile_hourly_sum_statistics_nan_inf_state(
one + timedelta.resolution,
significant_changes_only=False,
)
# Recorder will record state attributes with nan and inf replaced with null
for state in states["sensor.test1"]:
if not math.isfinite(state.attributes[ATTR_NUMERICAL_VALUE]):
state.attributes = state.attributes | {ATTR_NUMERICAL_VALUE: None}
assert_multiple_states_equal_without_context_and_last_changed(
dict(states)["sensor.test1"], dict(hist)["sensor.test1"]
)
@@ -1019,7 +1039,7 @@ def test_compile_hourly_sum_statistics_negative_state(
},
]
assert "Error while processing event StatisticsTask" not in caplog.text
state = states[entity_id][offending_state].state
state = states[entity_id][offending_state].attributes[ATTR_NUMERICAL_VALUE]
last_updated = states[entity_id][offending_state].last_updated.isoformat()
assert (
f"Entity {entity_id} {warning_1}has state class total_increasing, but its state "
@@ -1070,7 +1090,10 @@ def test_compile_hourly_sum_statistics_total_no_reset(
)
wait_recording_done(hass)
hist = history.get_significant_states(
hass, period0 - timedelta.resolution, eight + timedelta.resolution
hass,
period0 - timedelta.resolution,
eight + timedelta.resolution,
significant_changes_only=False,
)
assert_multiple_states_equal_without_context_and_last_changed(
dict(states)["sensor.test1"], dict(hist)["sensor.test1"]
@@ -1172,7 +1195,10 @@ def test_compile_hourly_sum_statistics_total_increasing(
)
wait_recording_done(hass)
hist = history.get_significant_states(
hass, period0 - timedelta.resolution, eight + timedelta.resolution
hass,
period0 - timedelta.resolution,
eight + timedelta.resolution,
significant_changes_only=False,
)
assert_multiple_states_equal_without_context_and_last_changed(
dict(states)["sensor.test1"], dict(hist)["sensor.test1"]
@@ -1272,7 +1298,10 @@ def test_compile_hourly_sum_statistics_total_increasing_small_dip(
)
wait_recording_done(hass)
hist = history.get_significant_states(
hass, period0 - timedelta.resolution, eight + timedelta.resolution
hass,
period0 - timedelta.resolution,
eight + timedelta.resolution,
significant_changes_only=False,
)
assert_multiple_states_equal_without_context_and_last_changed(
dict(states)["sensor.test1"], dict(hist)["sensor.test1"]
@@ -1288,8 +1317,8 @@ def test_compile_hourly_sum_statistics_total_increasing_small_dip(
) not in caplog.text
do_adhoc_statistics(hass, start=period2)
wait_recording_done(hass)
state = states["sensor.test1"][6].state
previous_state = float(states["sensor.test1"][5].state)
state = states["sensor.test1"][6].attributes[ATTR_NUMERICAL_VALUE]
previous_state = float(states["sensor.test1"][5].attributes[ATTR_NUMERICAL_VALUE])
last_updated = states["sensor.test1"][6].last_updated.isoformat()
assert (
"Entity sensor.test1 has state class total_increasing, but its state is not "
@@ -1379,7 +1408,10 @@ def test_compile_hourly_energy_statistics_unsupported(hass_recorder, caplog):
wait_recording_done(hass)
hist = history.get_significant_states(
hass, period0 - timedelta.resolution, eight + timedelta.resolution
hass,
period0 - timedelta.resolution,
eight + timedelta.resolution,
significant_changes_only=False,
)
assert_multiple_states_equal_without_context_and_last_changed(
dict(states)["sensor.test1"], dict(hist)["sensor.test1"]
@@ -1471,7 +1503,10 @@ def test_compile_hourly_energy_statistics_multiple(hass_recorder, caplog):
states = {**states, **_states}
wait_recording_done(hass)
hist = history.get_significant_states(
hass, period0 - timedelta.resolution, eight + timedelta.resolution
hass,
period0 - timedelta.resolution,
eight + timedelta.resolution,
significant_changes_only=False,
)
assert_multiple_states_equal_without_context_and_last_changed(
dict(states)["sensor.test1"], dict(hist)["sensor.test1"]
@@ -1656,7 +1691,9 @@ def test_compile_hourly_statistics_unchanged(
"unit_of_measurement": state_unit,
}
four, states = record_states(hass, zero, "sensor.test1", attributes)
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=four)
@@ -1688,7 +1725,9 @@ def test_compile_hourly_statistics_partially_unavailable(hass_recorder, caplog):
four, states = record_states_partially_unavailable(
hass, zero, "sensor.test1", TEMPERATURE_SENSOR_ATTRIBUTES
)
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=zero)
@@ -1757,7 +1796,9 @@ def test_compile_hourly_statistics_unavailable(
)
_, _states = record_states(hass, zero, "sensor.test2", attributes)
states = {**states, **_states}
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=four)
@@ -1965,7 +2006,9 @@ def test_compile_hourly_statistics_changing_units_1(
hass, zero + timedelta(minutes=10), "sensor.test1", attributes
)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=zero)
@@ -2075,7 +2118,9 @@ def test_compile_hourly_statistics_changing_units_2(
hass, zero + timedelta(minutes=5), "sensor.test1", attributes
)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=zero + timedelta(seconds=30 * 5))
@@ -2143,7 +2188,9 @@ def test_compile_hourly_statistics_changing_units_3(
hass, zero + timedelta(minutes=10), "sensor.test1", attributes
)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=zero)
@@ -2291,7 +2338,9 @@ def test_compile_hourly_statistics_convert_units_1(
hass, zero + timedelta(minutes=10), "sensor.test1", attributes
)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=zero + timedelta(minutes=10))
wait_recording_done(hass)
@@ -2383,7 +2432,9 @@ def test_compile_hourly_statistics_equivalent_units_1(
hass, zero + timedelta(minutes=10), "sensor.test1", attributes
)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=zero)
@@ -2497,7 +2548,9 @@ def test_compile_hourly_statistics_equivalent_units_2(
hass, zero + timedelta(minutes=5), "sensor.test1", attributes
)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=zero + timedelta(seconds=30 * 5))
@@ -2612,7 +2665,9 @@ def test_compile_hourly_statistics_changing_device_class_1(
hass, zero + timedelta(minutes=10), "sensor.test1", attributes
)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
# Run statistics again, additional statistics is generated
@@ -2667,7 +2722,9 @@ def test_compile_hourly_statistics_changing_device_class_1(
hass, zero + timedelta(minutes=20), "sensor.test1", attributes
)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
# Run statistics again, additional statistics is generated
@@ -2802,7 +2859,9 @@ def test_compile_hourly_statistics_changing_device_class_2(
hass, zero + timedelta(minutes=10), "sensor.test1", attributes
)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, zero, four)
hist = history.get_significant_states(
hass, zero, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
# Run statistics again, additional statistics is generated
@@ -2918,7 +2977,9 @@ def test_compile_hourly_statistics_changing_state_class(
# Add more states, with changed state class
four, _states = record_states(hass, period1, "sensor.test1", attributes_2)
states["sensor.test1"] += _states["sensor.test1"]
hist = history.get_significant_states(hass, period0, four)
hist = history.get_significant_states(
hass, period0, four, significant_changes_only=False
)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
do_adhoc_statistics(hass, start=period1)
@@ -3401,9 +3462,11 @@ def record_states(hass, zero, entity_id, attributes, seq=None):
if seq is None:
seq = [-10, 15, 30]
def set_state(entity_id, state, **kwargs):
def set_state(entity_id, numerical_value, attributes):
"""Set the state."""
hass.states.set(entity_id, state, **kwargs)
hass.states.set(
entity_id, "", attributes={**attributes, "numerical_value": numerical_value}
)
wait_recording_done(hass)
return hass.states.get(entity_id)
@@ -3416,23 +3479,17 @@ def record_states(hass, zero, entity_id, attributes, seq=None):
with patch(
"homeassistant.components.recorder.core.dt_util.utcnow", return_value=one
):
states[entity_id].append(
set_state(entity_id, str(seq[0]), attributes=attributes)
)
states[entity_id].append(set_state(entity_id, seq[0], attributes=attributes))
with patch(
"homeassistant.components.recorder.core.dt_util.utcnow", return_value=two
):
states[entity_id].append(
set_state(entity_id, str(seq[1]), attributes=attributes)
)
states[entity_id].append(set_state(entity_id, seq[1], attributes=attributes))
with patch(
"homeassistant.components.recorder.core.dt_util.utcnow", return_value=three
):
states[entity_id].append(
set_state(entity_id, str(seq[2]), attributes=attributes)
)
states[entity_id].append(set_state(entity_id, seq[2], attributes=attributes))
return four, states
@@ -3504,14 +3561,19 @@ async def test_validate_unit_change_convertible(
# No statistics, unit in state matching device class - empty response
hass.states.async_set(
"sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit}}
"sensor.test",
"",
attributes=attributes | {"unit_of_measurement": unit, ATTR_NUMERICAL_VALUE: 10},
)
await async_recorder_block_till_done(hass)
await assert_validation_result(client, {})
# No statistics, unit in state not matching device class - empty response
hass.states.async_set(
"sensor.test", 11, attributes={**attributes, **{"unit_of_measurement": "dogs"}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": "dogs", ATTR_NUMERICAL_VALUE: 11},
)
await async_recorder_block_till_done(hass)
await assert_validation_result(client, {})
@@ -3520,7 +3582,10 @@ async def test_validate_unit_change_convertible(
await async_recorder_block_till_done(hass)
do_adhoc_statistics(hass, start=now)
hass.states.async_set(
"sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": "dogs"}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": "dogs", ATTR_NUMERICAL_VALUE: 12},
)
await async_recorder_block_till_done(hass)
expected = {
@@ -3540,7 +3605,9 @@ async def test_validate_unit_change_convertible(
# Valid state - empty response
hass.states.async_set(
"sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit}}
"sensor.test",
"",
attributes=attributes | {"unit_of_measurement": unit, ATTR_NUMERICAL_VALUE: 13},
)
await async_recorder_block_till_done(hass)
await assert_validation_result(client, {})
@@ -3552,7 +3619,10 @@ async def test_validate_unit_change_convertible(
# Valid state in compatible unit - empty response
hass.states.async_set(
"sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit2}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit2, ATTR_NUMERICAL_VALUE: 14},
)
await async_recorder_block_till_done(hass)
await assert_validation_result(client, {})
@@ -3615,7 +3685,9 @@ async def test_validate_statistics_unit_ignore_device_class(
# No statistics, no device class - empty response
initial_attributes = {"state_class": "measurement", "unit_of_measurement": "dogs"}
hass.states.async_set("sensor.test", 10, attributes=initial_attributes)
hass.states.async_set(
"sensor.test", "", attributes=initial_attributes | {ATTR_NUMERICAL_VALUE: 10}
)
await hass.async_block_till_done()
await assert_validation_result(client, {})
@@ -3623,7 +3695,10 @@ async def test_validate_statistics_unit_ignore_device_class(
do_adhoc_statistics(hass, start=now)
await async_recorder_block_till_done(hass)
hass.states.async_set(
"sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": "dogs"}}
"sensor.test",
"",
attributes=initial_attributes
| {"unit_of_measurement": "dogs", ATTR_NUMERICAL_VALUE: 12},
)
await hass.async_block_till_done()
await assert_validation_result(client, {})
@@ -3703,14 +3778,19 @@ async def test_validate_statistics_unit_change_no_device_class(
# No statistics, sensor state set - empty response
hass.states.async_set(
"sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit}}
"sensor.test",
"",
attributes=attributes | {"unit_of_measurement": unit, ATTR_NUMERICAL_VALUE: 10},
)
await async_recorder_block_till_done(hass)
await assert_validation_result(client, {})
# No statistics, sensor state set to an incompatible unit - empty response
hass.states.async_set(
"sensor.test", 11, attributes={**attributes, **{"unit_of_measurement": "dogs"}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": "dogs", ATTR_NUMERICAL_VALUE: 11},
)
await async_recorder_block_till_done(hass)
await assert_validation_result(client, {})
@@ -3719,7 +3799,10 @@ async def test_validate_statistics_unit_change_no_device_class(
await async_recorder_block_till_done(hass)
do_adhoc_statistics(hass, start=now)
hass.states.async_set(
"sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": "dogs"}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": "dogs", ATTR_NUMERICAL_VALUE: 12},
)
await async_recorder_block_till_done(hass)
expected = {
@@ -3739,7 +3822,9 @@ async def test_validate_statistics_unit_change_no_device_class(
# Valid state - empty response
hass.states.async_set(
"sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit}}
"sensor.test",
"",
attributes=attributes | {"unit_of_measurement": unit, ATTR_NUMERICAL_VALUE: 13},
)
await async_recorder_block_till_done(hass)
await assert_validation_result(client, {})
@@ -3751,7 +3836,10 @@ async def test_validate_statistics_unit_change_no_device_class(
# Valid state in compatible unit - empty response
hass.states.async_set(
"sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit2}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit2, ATTR_NUMERICAL_VALUE: 14},
)
await async_recorder_block_till_done(hass)
await assert_validation_result(client, {})
@@ -3810,7 +3898,9 @@ async def test_validate_statistics_unsupported_state_class(
await assert_validation_result(client, {})
# No statistics, valid state - empty response
hass.states.async_set("sensor.test", 10, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: 10}
)
await hass.async_block_till_done()
await assert_validation_result(client, {})
@@ -3822,7 +3912,9 @@ async def test_validate_statistics_unsupported_state_class(
# State update with invalid state class, expect error
_attributes = dict(attributes)
_attributes.pop("state_class")
hass.states.async_set("sensor.test", 12, attributes=_attributes)
hass.states.async_set(
"sensor.test", "", attributes=_attributes | {ATTR_NUMERICAL_VALUE: 12}
)
await hass.async_block_till_done()
expected = {
"sensor.test": [
@@ -3874,7 +3966,9 @@ async def test_validate_statistics_sensor_no_longer_recorded(
await assert_validation_result(client, {})
# No statistics, valid state - empty response
hass.states.async_set("sensor.test", 10, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: 10}
)
await hass.async_block_till_done()
await assert_validation_result(client, {})
@@ -3947,7 +4041,9 @@ async def test_validate_statistics_sensor_not_recorded(
"homeassistant.components.sensor.recorder.is_entity_recorded",
return_value=False,
):
hass.states.async_set("sensor.test", 10, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: 10}
)
await hass.async_block_till_done()
await assert_validation_result(client, expected)
@@ -3993,7 +4089,9 @@ async def test_validate_statistics_sensor_removed(
await assert_validation_result(client, {})
# No statistics, valid state - empty response
hass.states.async_set("sensor.test", 10, attributes=attributes)
hass.states.async_set(
"sensor.test", "", attributes=attributes | {ATTR_NUMERICAL_VALUE: 10}
)
await hass.async_block_till_done()
await assert_validation_result(client, {})
@@ -4063,13 +4161,19 @@ async def test_validate_statistics_unit_change_no_conversion(
# No statistics, original unit - empty response
hass.states.async_set(
"sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit1}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit1, ATTR_NUMERICAL_VALUE: 10},
)
await assert_validation_result(client, {})
# No statistics, changed unit - empty response
hass.states.async_set(
"sensor.test", 11, attributes={**attributes, **{"unit_of_measurement": unit2}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit2, ATTR_NUMERICAL_VALUE: 11},
)
await assert_validation_result(client, {})
@@ -4081,7 +4185,10 @@ async def test_validate_statistics_unit_change_no_conversion(
# No statistics, original unit - empty response
hass.states.async_set(
"sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": unit1}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit1, ATTR_NUMERICAL_VALUE: 12},
)
await assert_validation_result(client, {})
@@ -4096,7 +4203,10 @@ async def test_validate_statistics_unit_change_no_conversion(
# Change unit - expect error
hass.states.async_set(
"sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit2}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit2, ATTR_NUMERICAL_VALUE: 13},
)
await async_recorder_block_till_done(hass)
expected = {
@@ -4193,7 +4303,10 @@ async def test_validate_statistics_unit_change_equivalent_units(
# No statistics, original unit - empty response
hass.states.async_set(
"sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit1}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit1, ATTR_NUMERICAL_VALUE: 10},
)
await assert_validation_result(client, {})
@@ -4207,7 +4320,10 @@ async def test_validate_statistics_unit_change_equivalent_units(
# Units changed to an equivalent unit - empty response
hass.states.async_set(
"sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": unit2}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit2, ATTR_NUMERICAL_VALUE: 12},
)
await assert_validation_result(client, {})
@@ -4273,7 +4389,10 @@ async def test_validate_statistics_unit_change_equivalent_units_2(
# No statistics, original unit - empty response
hass.states.async_set(
"sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit1}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit1, ATTR_NUMERICAL_VALUE: 10},
)
await assert_validation_result(client, {})
@@ -4287,7 +4406,10 @@ async def test_validate_statistics_unit_change_equivalent_units_2(
# Units changed to an equivalent unit which is not known by the unit converters
hass.states.async_set(
"sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": unit2}}
"sensor.test",
"",
attributes=attributes
| {"unit_of_measurement": unit2, ATTR_NUMERICAL_VALUE: 12},
)
expected = {
"sensor.test": [
@@ -4366,9 +4488,11 @@ def record_meter_states(hass, zero, entity_id, _attributes, seq):
We inject a bunch of state updates for meter sensors.
"""
def set_state(entity_id, state, **kwargs):
def set_state(entity_id, numerical_value, attributes):
"""Set the state."""
hass.states.set(entity_id, state, **kwargs)
hass.states.set(
entity_id, "", attributes={**attributes, "numerical_value": numerical_value}
)
return hass.states.get(entity_id)
one = zero + timedelta(seconds=15 * 5) # 00:01:15
@@ -4443,9 +4567,11 @@ def record_meter_state(hass, zero, entity_id, attributes, seq):
We inject a state update for meter sensor.
"""
def set_state(entity_id, state, **kwargs):
def set_state(entity_id, numerical_value, attributes):
"""Set the state."""
hass.states.set(entity_id, state, **kwargs)
hass.states.set(
entity_id, "", attributes={**attributes, "numerical_value": numerical_value}
)
wait_recording_done(hass)
return hass.states.get(entity_id)
@@ -4464,9 +4590,11 @@ def record_states_partially_unavailable(hass, zero, entity_id, attributes):
We inject a bunch of state updates temperature sensors.
"""
def set_state(entity_id, state, **kwargs):
def set_state(entity_id, numerical_value, attributes):
"""Set the state."""
hass.states.set(entity_id, state, **kwargs)
hass.states.set(
entity_id, "", attributes={**attributes, "numerical_value": numerical_value}
)
wait_recording_done(hass)
return hass.states.get(entity_id)