Add WS API to adjust incorrect energy statistics (#65147)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
c5a3ba4065
commit
b5c5da96ac
5 changed files with 280 additions and 41 deletions
|
@ -1,6 +1,7 @@
|
|||
"""The tests for sensor recorder platform."""
|
||||
# pylint: disable=protected-access,invalid-name
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
import math
|
||||
from statistics import mean
|
||||
from unittest.mock import patch
|
||||
|
@ -26,8 +27,15 @@ from homeassistant.setup import setup_component
|
|||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
||||
|
||||
from tests.common import async_setup_component, init_recorder_component
|
||||
from tests.components.recorder.common import wait_recording_done
|
||||
from tests.common import (
|
||||
async_init_recorder_component,
|
||||
async_setup_component,
|
||||
init_recorder_component,
|
||||
)
|
||||
from tests.components.recorder.common import (
|
||||
async_wait_recording_done_without_instance,
|
||||
wait_recording_done,
|
||||
)
|
||||
|
||||
BATTERY_SENSOR_ATTRIBUTES = {
|
||||
"device_class": "battery",
|
||||
|
@ -307,34 +315,44 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes
|
|||
|
||||
@pytest.mark.parametrize("state_class", ["total"])
|
||||
@pytest.mark.parametrize(
|
||||
"units,device_class,unit,display_unit,factor",
|
||||
"units,device_class,unit,display_unit,factor,factor2",
|
||||
[
|
||||
(IMPERIAL_SYSTEM, "energy", "kWh", "kWh", 1),
|
||||
(IMPERIAL_SYSTEM, "energy", "Wh", "kWh", 1 / 1000),
|
||||
(IMPERIAL_SYSTEM, "monetary", "EUR", "EUR", 1),
|
||||
(IMPERIAL_SYSTEM, "monetary", "SEK", "SEK", 1),
|
||||
(IMPERIAL_SYSTEM, "gas", "m³", "ft³", 35.314666711),
|
||||
(IMPERIAL_SYSTEM, "gas", "ft³", "ft³", 1),
|
||||
(METRIC_SYSTEM, "energy", "kWh", "kWh", 1),
|
||||
(METRIC_SYSTEM, "energy", "Wh", "kWh", 1 / 1000),
|
||||
(METRIC_SYSTEM, "monetary", "EUR", "EUR", 1),
|
||||
(METRIC_SYSTEM, "monetary", "SEK", "SEK", 1),
|
||||
(METRIC_SYSTEM, "gas", "m³", "m³", 1),
|
||||
(METRIC_SYSTEM, "gas", "ft³", "m³", 0.0283168466),
|
||||
(IMPERIAL_SYSTEM, "energy", "kWh", "kWh", 1, 1),
|
||||
(IMPERIAL_SYSTEM, "energy", "Wh", "kWh", 1 / 1000, 1),
|
||||
(IMPERIAL_SYSTEM, "monetary", "EUR", "EUR", 1, 1),
|
||||
(IMPERIAL_SYSTEM, "monetary", "SEK", "SEK", 1, 1),
|
||||
(IMPERIAL_SYSTEM, "gas", "m³", "ft³", 35.314666711, 35.314666711),
|
||||
(IMPERIAL_SYSTEM, "gas", "ft³", "ft³", 1, 35.314666711),
|
||||
(METRIC_SYSTEM, "energy", "kWh", "kWh", 1, 1),
|
||||
(METRIC_SYSTEM, "energy", "Wh", "kWh", 1 / 1000, 1),
|
||||
(METRIC_SYSTEM, "monetary", "EUR", "EUR", 1, 1),
|
||||
(METRIC_SYSTEM, "monetary", "SEK", "SEK", 1, 1),
|
||||
(METRIC_SYSTEM, "gas", "m³", "m³", 1, 1),
|
||||
(METRIC_SYSTEM, "gas", "ft³", "m³", 0.0283168466, 1),
|
||||
],
|
||||
)
|
||||
def test_compile_hourly_sum_statistics_amount(
|
||||
hass_recorder, caplog, units, state_class, device_class, unit, display_unit, factor
|
||||
async def test_compile_hourly_sum_statistics_amount(
|
||||
hass,
|
||||
hass_ws_client,
|
||||
caplog,
|
||||
units,
|
||||
state_class,
|
||||
device_class,
|
||||
unit,
|
||||
display_unit,
|
||||
factor,
|
||||
factor2,
|
||||
):
|
||||
"""Test compiling hourly statistics."""
|
||||
period0 = dt_util.utcnow()
|
||||
period0_end = period1 = period0 + timedelta(minutes=5)
|
||||
period1_end = period2 = period0 + timedelta(minutes=10)
|
||||
period2_end = period0 + timedelta(minutes=15)
|
||||
hass = hass_recorder()
|
||||
client = await hass_ws_client()
|
||||
await async_init_recorder_component(hass)
|
||||
hass.config.units = units
|
||||
recorder = hass.data[DATA_INSTANCE]
|
||||
setup_component(hass, "sensor", {})
|
||||
await async_setup_component(hass, "sensor", {})
|
||||
attributes = {
|
||||
"device_class": device_class,
|
||||
"state_class": state_class,
|
||||
|
@ -343,21 +361,28 @@ def test_compile_hourly_sum_statistics_amount(
|
|||
}
|
||||
seq = [10, 15, 20, 10, 30, 40, 50, 60, 70]
|
||||
|
||||
four, eight, states = record_meter_states(
|
||||
hass, period0, "sensor.test1", attributes, seq
|
||||
four, eight, states = await hass.async_add_executor_job(
|
||||
record_meter_states, hass, period0, "sensor.test1", attributes, seq
|
||||
)
|
||||
await async_wait_recording_done_without_instance(hass)
|
||||
hist = history.get_significant_states(
|
||||
hass, period0 - timedelta.resolution, eight + timedelta.resolution
|
||||
)
|
||||
assert dict(states)["sensor.test1"] == dict(hist)["sensor.test1"]
|
||||
|
||||
recorder.do_adhoc_statistics(start=period0)
|
||||
wait_recording_done(hass)
|
||||
recorder.do_adhoc_statistics(start=period1)
|
||||
wait_recording_done(hass)
|
||||
recorder.do_adhoc_statistics(start=period2)
|
||||
wait_recording_done(hass)
|
||||
statistic_ids = list_statistic_ids(hass)
|
||||
await hass.async_add_executor_job(
|
||||
partial(recorder.do_adhoc_statistics, start=period0)
|
||||
)
|
||||
await async_wait_recording_done_without_instance(hass)
|
||||
await hass.async_add_executor_job(
|
||||
partial(recorder.do_adhoc_statistics, start=period1)
|
||||
)
|
||||
await async_wait_recording_done_without_instance(hass)
|
||||
await hass.async_add_executor_job(
|
||||
partial(recorder.do_adhoc_statistics, start=period2)
|
||||
)
|
||||
await async_wait_recording_done_without_instance(hass)
|
||||
statistic_ids = await hass.async_add_executor_job(list_statistic_ids, hass)
|
||||
assert statistic_ids == [
|
||||
{
|
||||
"statistic_id": "sensor.test1",
|
||||
|
@ -416,20 +441,57 @@ def test_compile_hourly_sum_statistics_amount(
|
|||
stats = statistics_during_period(
|
||||
hass, period0 + timedelta(minutes=5), period="5minute"
|
||||
)
|
||||
expected_stats["sensor.test1"] = expected_stats["sensor.test1"][1:3]
|
||||
assert stats == expected_stats
|
||||
assert stats == {"sensor.test1": expected_stats["sensor.test1"][1:3]}
|
||||
|
||||
# With an offset of 6 minutes, we expect to get the 2nd and 3rd periods
|
||||
stats = statistics_during_period(
|
||||
hass, period0 + timedelta(minutes=6), period="5minute"
|
||||
)
|
||||
assert stats == expected_stats
|
||||
assert stats == {"sensor.test1": expected_stats["sensor.test1"][1:3]}
|
||||
|
||||
assert "Error while processing event StatisticsTask" not in caplog.text
|
||||
assert "Detected new cycle for sensor.test1, last_reset set to" in caplog.text
|
||||
assert "Compiling initial sum statistics for sensor.test1" in caplog.text
|
||||
assert "Detected new cycle for sensor.test1, value dropped" not in caplog.text
|
||||
|
||||
# Adjust the inserted statistics
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": "recorder/adjust_sum_statistics",
|
||||
"statistic_id": "sensor.test1",
|
||||
"start_time": period1.isoformat(),
|
||||
"adjustment": 100.0,
|
||||
}
|
||||
)
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
await async_wait_recording_done_without_instance(hass)
|
||||
|
||||
expected_stats["sensor.test1"][1]["sum"] = approx(factor * 40.0 + factor2 * 100)
|
||||
expected_stats["sensor.test1"][2]["sum"] = approx(factor * 70.0 + factor2 * 100)
|
||||
stats = statistics_during_period(hass, period0, period="5minute")
|
||||
assert stats == expected_stats
|
||||
|
||||
# Adjust the inserted statistics
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 2,
|
||||
"type": "recorder/adjust_sum_statistics",
|
||||
"statistic_id": "sensor.test1",
|
||||
"start_time": period2.isoformat(),
|
||||
"adjustment": -400.0,
|
||||
}
|
||||
)
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
await async_wait_recording_done_without_instance(hass)
|
||||
|
||||
expected_stats["sensor.test1"][1]["sum"] = approx(factor * 40.0 + factor2 * 100)
|
||||
expected_stats["sensor.test1"][2]["sum"] = approx(factor * 70.0 - factor2 * 300)
|
||||
stats = statistics_during_period(hass, period0, period="5minute")
|
||||
assert stats == expected_stats
|
||||
|
||||
|
||||
@pytest.mark.parametrize("state_class", ["total"])
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -836,6 +898,7 @@ def test_compile_hourly_sum_statistics_total_no_reset(
|
|||
four, eight, states = record_meter_states(
|
||||
hass, period0, "sensor.test1", attributes, seq
|
||||
)
|
||||
wait_recording_done(hass)
|
||||
hist = history.get_significant_states(
|
||||
hass, period0 - timedelta.resolution, eight + timedelta.resolution
|
||||
)
|
||||
|
@ -927,6 +990,7 @@ def test_compile_hourly_sum_statistics_total_increasing(
|
|||
four, eight, states = record_meter_states(
|
||||
hass, period0, "sensor.test1", attributes, seq
|
||||
)
|
||||
wait_recording_done(hass)
|
||||
hist = history.get_significant_states(
|
||||
hass, period0 - timedelta.resolution, eight + timedelta.resolution
|
||||
)
|
||||
|
@ -1016,6 +1080,7 @@ def test_compile_hourly_sum_statistics_total_increasing_small_dip(
|
|||
four, eight, states = record_meter_states(
|
||||
hass, period0, "sensor.test1", attributes, seq
|
||||
)
|
||||
wait_recording_done(hass)
|
||||
hist = history.get_significant_states(
|
||||
hass, period0 - timedelta.resolution, eight + timedelta.resolution
|
||||
)
|
||||
|
@ -1118,6 +1183,7 @@ def test_compile_hourly_energy_statistics_unsupported(hass_recorder, caplog):
|
|||
states = {**states, **_states}
|
||||
_, _, _states = record_meter_states(hass, period0, "sensor.test3", sns3_attr, seq3)
|
||||
states = {**states, **_states}
|
||||
wait_recording_done(hass)
|
||||
|
||||
hist = history.get_significant_states(
|
||||
hass, period0 - timedelta.resolution, eight + timedelta.resolution
|
||||
|
@ -1207,6 +1273,7 @@ def test_compile_hourly_energy_statistics_multiple(hass_recorder, caplog):
|
|||
states = {**states, **_states}
|
||||
_, _, _states = record_meter_states(hass, period0, "sensor.test3", sns3_attr, seq3)
|
||||
states = {**states, **_states}
|
||||
wait_recording_done(hass)
|
||||
hist = history.get_significant_states(
|
||||
hass, period0 - timedelta.resolution, eight + timedelta.resolution
|
||||
)
|
||||
|
@ -3164,7 +3231,6 @@ def record_meter_states(hass, zero, entity_id, _attributes, seq):
|
|||
def set_state(entity_id, state, **kwargs):
|
||||
"""Set the state."""
|
||||
hass.states.set(entity_id, state, **kwargs)
|
||||
wait_recording_done(hass)
|
||||
return hass.states.get(entity_id)
|
||||
|
||||
one = zero + timedelta(seconds=15 * 5) # 00:01:15
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue