2016-10-04 08:04:00 +00:00
|
|
|
"""The test for the statistics sensor platform."""
|
2019-12-09 13:38:01 +00:00
|
|
|
from datetime import datetime, timedelta
|
2016-10-04 08:04:00 +00:00
|
|
|
import statistics
|
2019-12-09 13:38:01 +00:00
|
|
|
import unittest
|
2021-01-01 21:31:56 +00:00
|
|
|
from unittest.mock import patch
|
2016-10-04 08:04:00 +00:00
|
|
|
|
2020-06-29 16:39:24 +00:00
|
|
|
import pytest
|
|
|
|
|
2020-08-26 12:52:19 +00:00
|
|
|
from homeassistant import config as hass_config
|
2019-12-09 13:38:01 +00:00
|
|
|
from homeassistant.components import recorder
|
2021-11-17 11:31:32 +00:00
|
|
|
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
2020-08-26 12:52:19 +00:00
|
|
|
from homeassistant.components.statistics.sensor import DOMAIN, StatisticsSensor
|
|
|
|
from homeassistant.const import (
|
|
|
|
ATTR_UNIT_OF_MEASUREMENT,
|
|
|
|
SERVICE_RELOAD,
|
2021-11-11 16:16:59 +00:00
|
|
|
STATE_UNAVAILABLE,
|
2020-08-26 12:52:19 +00:00
|
|
|
STATE_UNKNOWN,
|
|
|
|
TEMP_CELSIUS,
|
|
|
|
)
|
2021-12-04 08:50:47 +00:00
|
|
|
from homeassistant.helpers import entity_registry as er
|
2020-08-26 12:52:19 +00:00
|
|
|
from homeassistant.setup import async_setup_component, setup_component
|
2017-08-30 15:13:36 +00:00
|
|
|
from homeassistant.util import dt as dt_util
|
2019-12-09 13:38:01 +00:00
|
|
|
|
2020-01-09 13:03:27 +00:00
|
|
|
from tests.common import (
|
|
|
|
fire_time_changed,
|
2021-11-02 03:47:05 +00:00
|
|
|
get_fixture_path,
|
2020-01-09 13:03:27 +00:00
|
|
|
get_test_home_assistant,
|
|
|
|
init_recorder_component,
|
|
|
|
)
|
2020-06-22 04:58:57 +00:00
|
|
|
from tests.components.recorder.common import wait_recording_done
|
2016-10-04 08:04:00 +00:00
|
|
|
|
|
|
|
|
2020-06-29 16:39:24 +00:00
|
|
|
@pytest.fixture(autouse=True)
|
|
|
|
def mock_legacy_time(legacy_patchable_time):
|
|
|
|
"""Make time patchable for all the tests."""
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
2021-12-04 08:50:47 +00:00
|
|
|
async def test_unique_id(hass):
|
|
|
|
"""Test configuration defined unique_id."""
|
|
|
|
assert await async_setup_component(
|
|
|
|
hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
|
|
|
"unique_id": "uniqueid_sensor_test",
|
|
|
|
},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
entity_reg = er.async_get(hass)
|
|
|
|
entity_id = entity_reg.async_get_entity_id("sensor", DOMAIN, "uniqueid_sensor_test")
|
|
|
|
assert entity_id == "sensor.test"
|
|
|
|
|
|
|
|
|
2016-10-04 08:04:00 +00:00
|
|
|
class TestStatisticsSensor(unittest.TestCase):
|
|
|
|
"""Test the Statistics sensor."""
|
|
|
|
|
|
|
|
def setup_method(self, method):
|
2018-08-19 20:29:08 +00:00
|
|
|
"""Set up things to be run when tests are started."""
|
2016-10-04 08:04:00 +00:00
|
|
|
self.hass = get_test_home_assistant()
|
2021-12-02 08:03:24 +00:00
|
|
|
self.values_binary = ["on", "off", "on", "off", "on", "off", "on", "off", "on"]
|
|
|
|
self.mean_binary = round(
|
|
|
|
100 / len(self.values_binary) * self.values_binary.count("on"), 2
|
|
|
|
)
|
2016-10-04 08:04:00 +00:00
|
|
|
self.values = [17, 20, 15.2, 5, 3.8, 9.2, 6.7, 14, 6]
|
|
|
|
self.mean = round(sum(self.values) / len(self.values), 2)
|
2020-06-29 16:39:24 +00:00
|
|
|
self.addCleanup(self.hass.stop)
|
2016-10-04 08:04:00 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
def test_sensor_defaults_numeric(self):
|
|
|
|
"""Test the general behavior of the sensor, with numeric source sensor."""
|
2019-07-31 19:25:30 +00:00
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
2021-11-24 12:42:44 +00:00
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
|
|
|
},
|
|
|
|
]
|
2019-07-31 19:25:30 +00:00
|
|
|
},
|
|
|
|
)
|
2016-10-04 08:04:00 +00:00
|
|
|
|
2020-06-01 05:18:30 +00:00
|
|
|
self.hass.block_till_done()
|
2018-11-14 14:13:32 +00:00
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2016-10-04 08:04:00 +00:00
|
|
|
for value in self.values:
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.states.set(
|
2021-11-08 22:26:00 +00:00
|
|
|
"sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2016-10-04 08:04:00 +00:00
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2019-08-21 22:54:04 +00:00
|
|
|
state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
assert state.state == str(self.mean)
|
2021-11-17 11:31:32 +00:00
|
|
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS
|
|
|
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
2021-11-24 12:42:44 +00:00
|
|
|
assert state.attributes.get("buffer_usage_ratio") == round(9 / 20, 2)
|
|
|
|
assert state.attributes.get("source_value_valid") is True
|
|
|
|
assert "age_coverage_ratio" not in state.attributes
|
2021-11-17 11:31:32 +00:00
|
|
|
|
2021-11-11 16:16:59 +00:00
|
|
|
# Source sensor turns unavailable, then available with valid value,
|
|
|
|
# statistics sensor should follow
|
|
|
|
state = self.hass.states.get("sensor.test")
|
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
|
|
|
STATE_UNAVAILABLE,
|
|
|
|
)
|
2021-10-20 18:28:48 +00:00
|
|
|
self.hass.block_till_done()
|
|
|
|
new_state = self.hass.states.get("sensor.test")
|
2021-11-11 16:16:59 +00:00
|
|
|
assert new_state.state == STATE_UNAVAILABLE
|
2021-11-24 12:42:44 +00:00
|
|
|
assert new_state.attributes.get("source_value_valid") is None
|
2021-11-11 16:16:59 +00:00
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
|
|
|
0,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
|
|
|
new_state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
new_mean = round(sum(self.values) / (len(self.values) + 1), 2)
|
|
|
|
assert new_state.state == str(new_mean)
|
|
|
|
assert new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS
|
|
|
|
assert new_state.attributes.get("buffer_usage_ratio") == round(10 / 20, 2)
|
|
|
|
assert new_state.attributes.get("source_value_valid") is True
|
2021-10-20 18:28:48 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
# Source sensor has a nonnumerical state, unit and state should not change
|
2021-11-11 16:16:59 +00:00
|
|
|
state = self.hass.states.get("sensor.test")
|
2021-10-20 18:28:48 +00:00
|
|
|
self.hass.states.set("sensor.test_monitored", "beer", {})
|
|
|
|
self.hass.block_till_done()
|
|
|
|
new_state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
assert new_state.state == str(new_mean)
|
|
|
|
assert new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS
|
|
|
|
assert new_state.attributes.get("source_value_valid") is False
|
|
|
|
|
|
|
|
# Source sensor has the STATE_UNKNOWN state, unit and state should not change
|
|
|
|
state = self.hass.states.get("sensor.test")
|
|
|
|
self.hass.states.set("sensor.test_monitored", STATE_UNKNOWN, {})
|
|
|
|
self.hass.block_till_done()
|
|
|
|
new_state = self.hass.states.get("sensor.test")
|
|
|
|
assert new_state.state == str(new_mean)
|
|
|
|
assert new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS
|
|
|
|
assert new_state.attributes.get("source_value_valid") is False
|
2021-10-20 18:28:48 +00:00
|
|
|
|
2021-11-08 22:25:19 +00:00
|
|
|
# Source sensor is removed, unit and state should not change
|
|
|
|
# This is equal to a None value being published
|
|
|
|
self.hass.states.remove("sensor.test_monitored")
|
|
|
|
self.hass.block_till_done()
|
|
|
|
new_state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
assert new_state.state == str(new_mean)
|
|
|
|
assert new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS
|
|
|
|
assert new_state.attributes.get("source_value_valid") is False
|
2021-11-08 22:25:19 +00:00
|
|
|
|
2021-12-02 08:03:24 +00:00
|
|
|
def test_sensor_defaults_binary(self):
|
|
|
|
"""Test the general behavior of the sensor, with binary source sensor."""
|
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "binary_sensor.test_monitored",
|
|
|
|
},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
for value in self.values_binary:
|
|
|
|
self.hass.states.set(
|
|
|
|
"binary_sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
state = self.hass.states.get("sensor.test")
|
|
|
|
assert state.state == str(len(self.values_binary))
|
|
|
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
|
|
|
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
|
|
|
assert state.attributes.get("buffer_usage_ratio") == round(9 / 20, 2)
|
|
|
|
assert state.attributes.get("source_value_valid") is True
|
|
|
|
assert "age_coverage_ratio" not in state.attributes
|
|
|
|
|
|
|
|
def test_sensor_source_with_force_update(self):
|
|
|
|
"""Test the behavior of the sensor when the source sensor force-updates with same value."""
|
|
|
|
repeating_values = [18, 0, 0, 0, 0, 0, 0, 0, 9]
|
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_normal",
|
|
|
|
"entity_id": "sensor.test_monitored_normal",
|
|
|
|
"state_characteristic": "mean",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_force",
|
|
|
|
"entity_id": "sensor.test_monitored_force",
|
|
|
|
"state_characteristic": "mean",
|
|
|
|
},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
for value in repeating_values:
|
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored_normal",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored_force",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
force_update=True,
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
state_normal = self.hass.states.get("sensor.test_normal")
|
|
|
|
state_force = self.hass.states.get("sensor.test_force")
|
|
|
|
assert state_normal.state == str(round(sum(repeating_values) / 3, 2))
|
|
|
|
assert state_force.state == str(round(sum(repeating_values) / 9, 2))
|
|
|
|
assert state_normal.attributes.get("buffer_usage_ratio") == round(3 / 20, 2)
|
|
|
|
assert state_force.attributes.get("buffer_usage_ratio") == round(9 / 20, 2)
|
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
def test_sampling_size_non_default(self):
|
2016-10-04 08:04:00 +00:00
|
|
|
"""Test rotation."""
|
2019-07-31 19:25:30 +00:00
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
2021-11-24 12:42:44 +00:00
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
2021-12-02 08:03:24 +00:00
|
|
|
"state_characteristic": "mean",
|
2021-11-24 12:42:44 +00:00
|
|
|
"sampling_size": 5,
|
|
|
|
},
|
|
|
|
]
|
2019-07-31 19:25:30 +00:00
|
|
|
},
|
|
|
|
)
|
2016-10-04 08:04:00 +00:00
|
|
|
|
2020-06-01 05:18:30 +00:00
|
|
|
self.hass.block_till_done()
|
2018-11-14 14:13:32 +00:00
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2016-10-04 08:04:00 +00:00
|
|
|
for value in self.values:
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.states.set(
|
2021-11-08 22:26:00 +00:00
|
|
|
"sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2016-10-04 08:04:00 +00:00
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2019-08-21 22:54:04 +00:00
|
|
|
state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
new_mean = round(sum(self.values[-5:]) / len(self.values[-5:]), 2)
|
|
|
|
assert state.state == str(new_mean)
|
|
|
|
assert state.attributes.get("buffer_usage_ratio") == round(5 / 5, 2)
|
2017-08-30 15:13:36 +00:00
|
|
|
|
2017-12-18 20:21:27 +00:00
|
|
|
def test_sampling_size_1(self):
|
|
|
|
"""Test validity of stats requiring only one sample."""
|
2019-07-31 19:25:30 +00:00
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
2021-11-24 12:42:44 +00:00
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
2021-12-02 08:03:24 +00:00
|
|
|
"state_characteristic": "mean",
|
2021-11-24 12:42:44 +00:00
|
|
|
"sampling_size": 1,
|
|
|
|
},
|
|
|
|
]
|
2019-07-31 19:25:30 +00:00
|
|
|
},
|
|
|
|
)
|
2017-12-18 20:21:27 +00:00
|
|
|
|
2020-06-01 05:18:30 +00:00
|
|
|
self.hass.block_till_done()
|
2018-11-14 14:13:32 +00:00
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2017-12-18 20:21:27 +00:00
|
|
|
for value in self.values[-3:]: # just the last 3 will do
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.states.set(
|
2021-11-08 22:26:00 +00:00
|
|
|
"sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2017-12-18 20:21:27 +00:00
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2019-08-21 22:54:04 +00:00
|
|
|
state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
new_mean = float(self.values[-1])
|
|
|
|
assert state.state == str(new_mean)
|
|
|
|
assert state.attributes.get("buffer_usage_ratio") == round(1 / 1, 2)
|
2017-12-18 20:21:27 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
def test_age_limit_expiry(self):
|
|
|
|
"""Test that values are removed after certain age."""
|
2020-07-15 05:25:48 +00:00
|
|
|
now = dt_util.utcnow()
|
|
|
|
mock_data = {
|
|
|
|
"return_time": datetime(now.year + 1, 8, 2, 12, 23, tzinfo=dt_util.UTC)
|
|
|
|
}
|
2017-08-30 15:13:36 +00:00
|
|
|
|
|
|
|
def mock_now():
|
2019-07-31 19:25:30 +00:00
|
|
|
return mock_data["return_time"]
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"homeassistant.components.statistics.sensor.dt_util.utcnow", new=mock_now
|
|
|
|
):
|
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
2021-11-24 12:42:44 +00:00
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
2021-12-02 08:03:24 +00:00
|
|
|
"state_characteristic": "mean",
|
2021-11-24 12:42:44 +00:00
|
|
|
"max_age": {"minutes": 4},
|
|
|
|
},
|
|
|
|
]
|
2019-07-31 19:25:30 +00:00
|
|
|
},
|
|
|
|
)
|
2017-08-30 15:13:36 +00:00
|
|
|
|
2020-06-01 05:18:30 +00:00
|
|
|
self.hass.block_till_done()
|
2018-11-14 14:13:32 +00:00
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2017-08-30 15:13:36 +00:00
|
|
|
for value in self.values:
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
2017-08-30 15:13:36 +00:00
|
|
|
self.hass.block_till_done()
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_data["return_time"] += timedelta(minutes=1)
|
2017-08-30 15:13:36 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
# After adding all values, we should only see 5 values in memory
|
2020-01-09 13:03:27 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
state = self.hass.states.get("sensor.test")
|
|
|
|
new_mean = round(sum(self.values[-5:]) / len(self.values[-5:]), 2)
|
|
|
|
assert state.state == str(new_mean)
|
|
|
|
assert state.attributes.get("buffer_usage_ratio") == round(5 / 20, 2)
|
|
|
|
assert state.attributes.get("age_coverage_ratio") == 1.0
|
2020-01-09 13:03:27 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
# Values expire over time. Only two are left
|
2020-01-09 13:03:27 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
mock_data["return_time"] += timedelta(minutes=2)
|
|
|
|
fire_time_changed(self.hass, mock_data["return_time"])
|
2020-01-09 13:03:27 +00:00
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
new_mean = round(sum(self.values[-2:]) / len(self.values[-2:]), 2)
|
|
|
|
assert state.state == str(new_mean)
|
|
|
|
assert state.attributes.get("buffer_usage_ratio") == round(2 / 20, 2)
|
|
|
|
assert state.attributes.get("age_coverage_ratio") == 1 / 4
|
2020-01-09 13:03:27 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
# Values expire over time. Only one is left
|
2020-01-09 13:03:27 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
mock_data["return_time"] += timedelta(minutes=1)
|
2020-01-09 13:03:27 +00:00
|
|
|
fire_time_changed(self.hass, mock_data["return_time"])
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
new_mean = float(self.values[-1])
|
|
|
|
assert state.state == str(new_mean)
|
|
|
|
assert state.attributes.get("buffer_usage_ratio") == round(1 / 20, 2)
|
|
|
|
assert state.attributes.get("age_coverage_ratio") == 0
|
2020-01-09 13:03:27 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
# Values expire over time. Memory is empty
|
2018-09-07 23:10:08 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
mock_data["return_time"] += timedelta(minutes=1)
|
|
|
|
fire_time_changed(self.hass, mock_data["return_time"])
|
2020-06-01 05:18:30 +00:00
|
|
|
self.hass.block_till_done()
|
2018-09-07 23:10:08 +00:00
|
|
|
|
2019-08-21 22:54:04 +00:00
|
|
|
state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
assert state.state == STATE_UNKNOWN
|
|
|
|
assert state.attributes.get("buffer_usage_ratio") == round(0 / 20, 2)
|
2021-11-25 14:13:55 +00:00
|
|
|
assert state.attributes.get("age_coverage_ratio") is None
|
2018-09-07 23:10:08 +00:00
|
|
|
|
2021-11-08 22:26:00 +00:00
|
|
|
def test_precision_0(self):
|
|
|
|
"""Test correct result with precision=0 as integer."""
|
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
2021-11-24 12:42:44 +00:00
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
2021-12-02 08:03:24 +00:00
|
|
|
"state_characteristic": "mean",
|
2021-11-24 12:42:44 +00:00
|
|
|
"precision": 0,
|
|
|
|
},
|
|
|
|
]
|
2021-11-08 22:26:00 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
for value in self.values:
|
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
state = self.hass.states.get("sensor.test")
|
2021-11-24 12:42:44 +00:00
|
|
|
assert state.state == str(int(round(self.mean)))
|
2021-11-08 22:26:00 +00:00
|
|
|
|
|
|
|
def test_precision_1(self):
|
|
|
|
"""Test correct result with precision=1 rounded to one decimal."""
|
2021-11-17 11:31:32 +00:00
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "test",
|
2021-11-17 11:31:32 +00:00
|
|
|
"entity_id": "sensor.test_monitored",
|
2021-12-02 08:03:24 +00:00
|
|
|
"state_characteristic": "mean",
|
2021-11-24 12:42:44 +00:00
|
|
|
"precision": 1,
|
2021-11-17 11:31:32 +00:00
|
|
|
},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
for value in self.values:
|
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
state = self.hass.states.get("sensor.test")
|
|
|
|
assert state.state == str(round(sum(self.values) / len(self.values), 1))
|
2021-11-17 11:31:32 +00:00
|
|
|
|
|
|
|
def test_state_class(self):
|
|
|
|
"""Test state class, which depends on the characteristic configured."""
|
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_normal",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
|
|
|
"state_characteristic": "count",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_nan",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
2021-11-24 12:42:44 +00:00
|
|
|
"state_characteristic": "datetime_oldest",
|
2021-11-17 11:31:32 +00:00
|
|
|
},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
for value in self.values:
|
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
state = self.hass.states.get("sensor.test_normal")
|
|
|
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
|
|
|
state = self.hass.states.get("sensor.test_nan")
|
|
|
|
assert state.attributes.get(ATTR_STATE_CLASS) is None
|
|
|
|
|
|
|
|
def test_unitless_source_sensor(self):
|
|
|
|
"""Statistics for a unitless source sensor should never have a unit."""
|
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_unitless_1",
|
|
|
|
"entity_id": "sensor.test_monitored_unitless",
|
|
|
|
"state_characteristic": "count",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_unitless_2",
|
|
|
|
"entity_id": "sensor.test_monitored_unitless",
|
|
|
|
"state_characteristic": "mean",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_unitless_3",
|
|
|
|
"entity_id": "sensor.test_monitored_unitless",
|
2021-11-24 12:42:44 +00:00
|
|
|
"state_characteristic": "change_second",
|
2021-11-17 11:31:32 +00:00
|
|
|
},
|
2021-12-02 08:03:24 +00:00
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_unitless_4",
|
|
|
|
"entity_id": "binary_sensor.test_monitored_unitless",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_unitless_5",
|
|
|
|
"entity_id": "binary_sensor.test_monitored_unitless",
|
|
|
|
"state_characteristic": "mean",
|
|
|
|
},
|
2021-11-17 11:31:32 +00:00
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
for value in self.values:
|
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored_unitless",
|
|
|
|
value,
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
2021-12-02 08:03:24 +00:00
|
|
|
for value in self.values_binary:
|
|
|
|
self.hass.states.set(
|
|
|
|
"binary_sensor.test_monitored_unitless",
|
|
|
|
value,
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
2021-11-17 11:31:32 +00:00
|
|
|
|
|
|
|
state = self.hass.states.get("sensor.test_unitless_1")
|
|
|
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
|
|
|
|
state = self.hass.states.get("sensor.test_unitless_2")
|
|
|
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
|
|
|
|
state = self.hass.states.get("sensor.test_unitless_3")
|
|
|
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
|
2021-12-02 08:03:24 +00:00
|
|
|
state = self.hass.states.get("sensor.test_unitless_4")
|
|
|
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
|
|
|
|
state = self.hass.states.get("sensor.test_unitless_5")
|
|
|
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "%"
|
2021-11-17 11:31:32 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
def test_state_characteristics(self):
|
|
|
|
"""Test configured state characteristic for value and unit."""
|
|
|
|
now = dt_util.utcnow()
|
|
|
|
mock_data = {
|
|
|
|
"return_time": datetime(now.year + 1, 8, 2, 12, 23, 42, tzinfo=dt_util.UTC)
|
|
|
|
}
|
|
|
|
|
|
|
|
def mock_now():
|
|
|
|
return mock_data["return_time"]
|
|
|
|
|
|
|
|
value_spacing_minutes = 1
|
|
|
|
|
|
|
|
characteristics = (
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "average_linear",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": 10.68,
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "average_step",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": 11.36,
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "average_timeless",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": float(self.values[0]),
|
|
|
|
"value_9": float(self.mean),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "change",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": float(0),
|
|
|
|
"value_9": float(round(self.values[-1] - self.values[0], 2)),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "change_sample",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": float(
|
|
|
|
round(
|
|
|
|
(self.values[-1] - self.values[0]) / (len(self.values) - 1), 2
|
|
|
|
)
|
|
|
|
),
|
|
|
|
"unit": "°C/sample",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "change_second",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": float(
|
|
|
|
round(
|
|
|
|
(self.values[-1] - self.values[0])
|
|
|
|
/ (60 * (len(self.values) - 1)),
|
|
|
|
2,
|
|
|
|
)
|
|
|
|
),
|
|
|
|
"unit": "°C/s",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "count",
|
|
|
|
"value_0": 0,
|
|
|
|
"value_1": 1,
|
|
|
|
"value_9": len(self.values),
|
|
|
|
"unit": None,
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "datetime_newest",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": datetime(
|
|
|
|
now.year + 1,
|
|
|
|
8,
|
|
|
|
2,
|
|
|
|
12,
|
|
|
|
23 + len(self.values) + 10,
|
|
|
|
42,
|
|
|
|
tzinfo=dt_util.UTC,
|
|
|
|
),
|
|
|
|
"value_9": datetime(
|
|
|
|
now.year + 1,
|
|
|
|
8,
|
|
|
|
2,
|
|
|
|
12,
|
|
|
|
23 + len(self.values) - 1,
|
|
|
|
42,
|
|
|
|
tzinfo=dt_util.UTC,
|
|
|
|
),
|
|
|
|
"unit": None,
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "datetime_oldest",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": datetime(
|
|
|
|
now.year + 1,
|
|
|
|
8,
|
|
|
|
2,
|
|
|
|
12,
|
|
|
|
23 + len(self.values) + 10,
|
|
|
|
42,
|
|
|
|
tzinfo=dt_util.UTC,
|
|
|
|
),
|
|
|
|
"value_9": datetime(now.year + 1, 8, 2, 12, 23, 42, tzinfo=dt_util.UTC),
|
|
|
|
"unit": None,
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "distance_95_percent_of_values",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": float(round(2 * 1.96 * statistics.stdev(self.values), 2)),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "distance_99_percent_of_values",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": float(round(2 * 2.58 * statistics.stdev(self.values), 2)),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "distance_absolute",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": float(0),
|
|
|
|
"value_9": float(max(self.values) - min(self.values)),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "mean",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": float(self.values[0]),
|
|
|
|
"value_9": float(self.mean),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "median",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": float(self.values[0]),
|
|
|
|
"value_9": float(round(statistics.median(self.values), 2)),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "noisiness",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": float(
|
|
|
|
round(sum([3, 4.8, 10.2, 1.2, 5.4, 2.5, 7.3, 8]) / 8, 2)
|
|
|
|
),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "quantiles",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": [
|
|
|
|
round(quantile, 2) for quantile in statistics.quantiles(self.values)
|
|
|
|
],
|
|
|
|
"unit": None,
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "standard_deviation",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": float(round(statistics.stdev(self.values), 2)),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "total",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": float(self.values[0]),
|
|
|
|
"value_9": float(sum(self.values)),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "value_max",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": float(self.values[0]),
|
|
|
|
"value_9": float(max(self.values)),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "value_min",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": float(self.values[0]),
|
|
|
|
"value_9": float(min(self.values)),
|
|
|
|
"unit": "°C",
|
|
|
|
},
|
|
|
|
{
|
2021-12-02 08:03:24 +00:00
|
|
|
"source_sensor_domain": "sensor",
|
2021-11-24 12:42:44 +00:00
|
|
|
"name": "variance",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": float(round(statistics.variance(self.values), 2)),
|
|
|
|
"unit": "°C²",
|
|
|
|
},
|
2021-12-02 08:03:24 +00:00
|
|
|
{
|
|
|
|
"source_sensor_domain": "binary_sensor",
|
|
|
|
"name": "average_step",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": STATE_UNKNOWN,
|
|
|
|
"value_9": 50.0,
|
|
|
|
"unit": "%",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"source_sensor_domain": "binary_sensor",
|
|
|
|
"name": "average_timeless",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": 100.0,
|
|
|
|
"value_9": float(self.mean_binary),
|
|
|
|
"unit": "%",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"source_sensor_domain": "binary_sensor",
|
|
|
|
"name": "count",
|
|
|
|
"value_0": 0,
|
|
|
|
"value_1": 1,
|
|
|
|
"value_9": len(self.values_binary),
|
|
|
|
"unit": None,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"source_sensor_domain": "binary_sensor",
|
|
|
|
"name": "mean",
|
|
|
|
"value_0": STATE_UNKNOWN,
|
|
|
|
"value_1": 100.0,
|
|
|
|
"value_9": float(self.mean_binary),
|
|
|
|
"unit": "%",
|
|
|
|
},
|
2021-11-24 12:42:44 +00:00
|
|
|
)
|
|
|
|
sensors_config = []
|
|
|
|
for characteristic in characteristics:
|
|
|
|
sensors_config.append(
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
2021-12-02 08:03:24 +00:00
|
|
|
"name": f"test_{characteristic['source_sensor_domain']}_{characteristic['name']}",
|
|
|
|
"entity_id": f"{characteristic['source_sensor_domain']}.test_monitored",
|
2021-11-24 12:42:44 +00:00
|
|
|
"state_characteristic": characteristic["name"],
|
|
|
|
"max_age": {"minutes": 10},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"homeassistant.components.statistics.sensor.dt_util.utcnow", new=mock_now
|
|
|
|
):
|
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{"sensor": sensors_config},
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
# With all values in buffer
|
|
|
|
|
2021-12-02 08:03:24 +00:00
|
|
|
for i in range(len(self.values)):
|
2021-11-24 12:42:44 +00:00
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
2021-12-02 08:03:24 +00:00
|
|
|
self.values[i],
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
|
|
|
self.hass.states.set(
|
|
|
|
"binary_sensor.test_monitored",
|
|
|
|
self.values_binary[i],
|
2021-11-24 12:42:44 +00:00
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
|
|
|
mock_data["return_time"] += timedelta(minutes=value_spacing_minutes)
|
|
|
|
|
|
|
|
for characteristic in characteristics:
|
2021-12-02 08:03:24 +00:00
|
|
|
state = self.hass.states.get(
|
|
|
|
f"sensor.test_{characteristic['source_sensor_domain']}_{characteristic['name']}"
|
|
|
|
)
|
2021-11-24 12:42:44 +00:00
|
|
|
assert state.state == str(characteristic["value_9"]), (
|
2021-12-02 08:03:24 +00:00
|
|
|
f"value mismatch for characteristic "
|
|
|
|
f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' "
|
|
|
|
f"(buffer filled) - "
|
|
|
|
f"assert {state.state} == {str(characteristic['value_9'])}"
|
2021-11-24 12:42:44 +00:00
|
|
|
)
|
|
|
|
assert (
|
|
|
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
|
|
|
== characteristic["unit"]
|
|
|
|
), f"unit mismatch for characteristic '{characteristic['name']}'"
|
|
|
|
|
|
|
|
# With empty buffer
|
|
|
|
|
|
|
|
mock_data["return_time"] += timedelta(minutes=10)
|
|
|
|
fire_time_changed(self.hass, mock_data["return_time"])
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
for characteristic in characteristics:
|
2021-12-02 08:03:24 +00:00
|
|
|
state = self.hass.states.get(
|
|
|
|
f"sensor.test_{characteristic['source_sensor_domain']}_{characteristic['name']}"
|
|
|
|
)
|
2021-11-24 12:42:44 +00:00
|
|
|
assert state.state == str(characteristic["value_0"]), (
|
2021-12-02 08:03:24 +00:00
|
|
|
f"value mismatch for characteristic "
|
|
|
|
f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' "
|
|
|
|
f"(buffer empty) - "
|
|
|
|
f"assert {state.state} == {str(characteristic['value_0'])}"
|
2021-11-24 12:42:44 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# With single value in buffer
|
|
|
|
|
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
|
|
|
self.values[0],
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
2021-12-02 08:03:24 +00:00
|
|
|
self.hass.states.set(
|
|
|
|
"binary_sensor.test_monitored",
|
|
|
|
self.values_binary[0],
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
force_update=True,
|
|
|
|
)
|
2021-11-24 12:42:44 +00:00
|
|
|
mock_data["return_time"] += timedelta(minutes=1)
|
2021-12-02 08:03:24 +00:00
|
|
|
fire_time_changed(self.hass, mock_data["return_time"])
|
|
|
|
self.hass.block_till_done()
|
2021-11-24 12:42:44 +00:00
|
|
|
|
|
|
|
for characteristic in characteristics:
|
2021-12-02 08:03:24 +00:00
|
|
|
state = self.hass.states.get(
|
|
|
|
f"sensor.test_{characteristic['source_sensor_domain']}_{characteristic['name']}"
|
|
|
|
)
|
2021-11-24 12:42:44 +00:00
|
|
|
assert state.state == str(characteristic["value_1"]), (
|
2021-12-02 08:03:24 +00:00
|
|
|
f"value mismatch for characteristic "
|
|
|
|
f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' "
|
|
|
|
f"(one stored value) - "
|
|
|
|
f"assert {state.state} == {str(characteristic['value_1'])}"
|
2021-11-24 12:42:44 +00:00
|
|
|
)
|
2021-11-17 11:31:32 +00:00
|
|
|
|
2021-12-02 08:03:24 +00:00
|
|
|
def test_invalid_state_characteristic(self):
|
|
|
|
"""Test the detection of wrong state_characteristics selected."""
|
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_numeric",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
|
|
|
"state_characteristic": "invalid",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test_binary",
|
|
|
|
"entity_id": "binary_sensor.test_monitored",
|
|
|
|
"state_characteristic": "variance",
|
|
|
|
},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
|
|
|
self.values[0],
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
|
|
|
state = self.hass.states.get("sensor.test_numeric")
|
|
|
|
assert state is None
|
|
|
|
state = self.hass.states.get("sensor.test_binary")
|
|
|
|
assert state is None
|
|
|
|
|
2017-10-08 21:45:12 +00:00
|
|
|
def test_initialize_from_database(self):
|
|
|
|
"""Test initializing the statistics from the database."""
|
|
|
|
# enable the recorder
|
|
|
|
init_recorder_component(self.hass)
|
2020-06-22 04:58:57 +00:00
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.data[recorder.DATA_INSTANCE].block_till_done()
|
2017-10-08 21:45:12 +00:00
|
|
|
# store some values
|
|
|
|
for value in self.values:
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.states.set(
|
2021-11-08 22:26:00 +00:00
|
|
|
"sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2017-10-08 21:45:12 +00:00
|
|
|
self.hass.block_till_done()
|
|
|
|
# wait for the recorder to really store the data
|
2020-06-22 04:58:57 +00:00
|
|
|
wait_recording_done(self.hass)
|
2017-10-08 21:45:12 +00:00
|
|
|
# only now create the statistics component, so that it must read the
|
|
|
|
# data from the database
|
2019-07-31 19:25:30 +00:00
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
2021-11-24 12:42:44 +00:00
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
2021-12-02 08:03:24 +00:00
|
|
|
"state_characteristic": "mean",
|
2021-11-24 12:42:44 +00:00
|
|
|
"sampling_size": 100,
|
|
|
|
},
|
|
|
|
]
|
2019-07-31 19:25:30 +00:00
|
|
|
},
|
|
|
|
)
|
2018-11-14 14:13:32 +00:00
|
|
|
|
2020-06-01 05:18:30 +00:00
|
|
|
self.hass.block_till_done()
|
2018-11-14 14:13:32 +00:00
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2017-10-08 21:45:12 +00:00
|
|
|
# check if the result is as in test_sensor_source()
|
2019-08-21 22:54:04 +00:00
|
|
|
state = self.hass.states.get("sensor.test")
|
2018-10-24 10:10:05 +00:00
|
|
|
assert str(self.mean) == state.state
|
2021-10-20 18:28:48 +00:00
|
|
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS
|
2018-11-08 23:08:36 +00:00
|
|
|
|
|
|
|
def test_initialize_from_database_with_maxage(self):
|
|
|
|
"""Test initializing the statistics from the database."""
|
2020-07-15 05:25:48 +00:00
|
|
|
now = dt_util.utcnow()
|
2018-11-08 23:08:36 +00:00
|
|
|
mock_data = {
|
2020-07-15 05:25:48 +00:00
|
|
|
"return_time": datetime(now.year + 1, 8, 2, 12, 23, 42, tzinfo=dt_util.UTC)
|
2018-11-08 23:08:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def mock_now():
|
2019-07-31 19:25:30 +00:00
|
|
|
return mock_data["return_time"]
|
2018-11-08 23:08:36 +00:00
|
|
|
|
|
|
|
# Testing correct retrieval from recorder, thus we do not
|
|
|
|
# want purging to occur within the class itself.
|
|
|
|
def mock_purge(self):
|
|
|
|
return
|
|
|
|
|
|
|
|
# enable the recorder
|
|
|
|
init_recorder_component(self.hass)
|
2020-06-22 04:58:57 +00:00
|
|
|
self.hass.block_till_done()
|
|
|
|
self.hass.data[recorder.DATA_INSTANCE].block_till_done()
|
2018-11-08 23:08:36 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch(
|
|
|
|
"homeassistant.components.statistics.sensor.dt_util.utcnow", new=mock_now
|
|
|
|
), patch.object(StatisticsSensor, "_purge_old", mock_purge):
|
2018-11-08 23:08:36 +00:00
|
|
|
# store some values
|
|
|
|
for value in self.values:
|
2019-07-31 19:25:30 +00:00
|
|
|
self.hass.states.set(
|
|
|
|
"sensor.test_monitored",
|
|
|
|
value,
|
|
|
|
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
|
|
|
)
|
2018-11-08 23:08:36 +00:00
|
|
|
self.hass.block_till_done()
|
|
|
|
# insert the next value 1 hour later
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_data["return_time"] += timedelta(hours=1)
|
2018-11-08 23:08:36 +00:00
|
|
|
|
|
|
|
# wait for the recorder to really store the data
|
2020-06-22 04:58:57 +00:00
|
|
|
wait_recording_done(self.hass)
|
2018-11-08 23:08:36 +00:00
|
|
|
# only now create the statistics component, so that it must read
|
|
|
|
# the data from the database
|
2019-07-31 19:25:30 +00:00
|
|
|
assert setup_component(
|
|
|
|
self.hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
2021-11-24 12:42:44 +00:00
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
|
|
|
"sampling_size": 100,
|
|
|
|
"state_characteristic": "datetime_newest",
|
|
|
|
"max_age": {"hours": 3},
|
|
|
|
},
|
|
|
|
]
|
2019-07-31 19:25:30 +00:00
|
|
|
},
|
|
|
|
)
|
2018-11-28 15:14:37 +00:00
|
|
|
self.hass.block_till_done()
|
2018-11-08 23:08:36 +00:00
|
|
|
|
2020-06-01 05:18:30 +00:00
|
|
|
self.hass.block_till_done()
|
2018-11-14 14:13:32 +00:00
|
|
|
self.hass.start()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
|
2018-11-08 23:08:36 +00:00
|
|
|
# check if the result is as in test_sensor_source()
|
2019-08-21 22:54:04 +00:00
|
|
|
state = self.hass.states.get("sensor.test")
|
2018-11-08 23:08:36 +00:00
|
|
|
|
2021-11-24 12:42:44 +00:00
|
|
|
assert state.attributes.get("age_coverage_ratio") == round(2 / 3, 2)
|
2018-11-08 23:08:36 +00:00
|
|
|
# The max_age timestamp should be 1 hour before what we have right
|
|
|
|
# now in mock_data['return_time'].
|
2021-11-24 12:42:44 +00:00
|
|
|
assert mock_data["return_time"] == datetime.strptime(
|
|
|
|
state.state, "%Y-%m-%d %H:%M:%S%z"
|
|
|
|
) + timedelta(hours=1)
|
2020-08-26 12:52:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_reload(hass):
|
|
|
|
"""Verify we can reload filter sensors."""
|
|
|
|
await hass.async_add_executor_job(
|
|
|
|
init_recorder_component, hass
|
|
|
|
) # force in memory db
|
|
|
|
|
|
|
|
hass.states.async_set("sensor.test_monitored", 12345)
|
|
|
|
await async_setup_component(
|
|
|
|
hass,
|
|
|
|
"sensor",
|
|
|
|
{
|
2021-11-24 12:42:44 +00:00
|
|
|
"sensor": [
|
|
|
|
{
|
|
|
|
"platform": "statistics",
|
|
|
|
"name": "test",
|
|
|
|
"entity_id": "sensor.test_monitored",
|
2021-12-02 08:03:24 +00:00
|
|
|
"state_characteristic": "mean",
|
2021-11-24 12:42:44 +00:00
|
|
|
"sampling_size": 100,
|
|
|
|
},
|
|
|
|
]
|
2020-08-26 12:52:19 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.async_start()
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(hass.states.async_all()) == 2
|
|
|
|
|
|
|
|
assert hass.states.get("sensor.test")
|
|
|
|
|
2021-11-02 03:47:05 +00:00
|
|
|
yaml_path = get_fixture_path("configuration.yaml", "statistics")
|
2020-08-26 12:52:19 +00:00
|
|
|
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
|
|
|
|
await hass.services.async_call(
|
2020-08-27 11:56:20 +00:00
|
|
|
DOMAIN,
|
|
|
|
SERVICE_RELOAD,
|
|
|
|
{},
|
|
|
|
blocking=True,
|
2020-08-26 12:52:19 +00:00
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(hass.states.async_all()) == 2
|
|
|
|
|
|
|
|
assert hass.states.get("sensor.test") is None
|
|
|
|
assert hass.states.get("sensor.cputest")
|