mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add binary characteristics, add deprecation warning for optional state_characteristic parameter (#60402)
* Add binary source sensor statistics * Make state_characteristic a required parameter * Move binary unitless testcase * Add testcases for binary characteristics * Revert charact. to optional with deprecation warning * Correctly check for binary supported characteristic
This commit is contained in:
parent
9128dc214c
commit
bee3c9102c
@ -60,12 +60,30 @@ STAT_VALUE_MAX = "value_max"
|
||||
STAT_VALUE_MIN = "value_min"
|
||||
STAT_VARIANCE = "variance"
|
||||
|
||||
STAT_DEFAULT = "default"
|
||||
DEPRECATION_WARNING = (
|
||||
"The configuration parameter 'state_characteristics' will become "
|
||||
"mandatory in a future release of the statistics integration. "
|
||||
"Please add 'state_characteristics: %s' to the configuration of "
|
||||
'sensor "%s" to keep the current behavior. Read the documentation '
|
||||
"for further details: "
|
||||
"https://www.home-assistant.io/integrations/statistics/"
|
||||
)
|
||||
|
||||
STATS_NOT_A_NUMBER = (
|
||||
STAT_DATETIME_OLDEST,
|
||||
STAT_DATETIME_NEWEST,
|
||||
STAT_QUANTILES,
|
||||
)
|
||||
|
||||
STATS_BINARY_SUPPORT = (
|
||||
STAT_AVERAGE_STEP,
|
||||
STAT_AVERAGE_TIMELESS,
|
||||
STAT_COUNT,
|
||||
STAT_MEAN,
|
||||
STAT_DEFAULT,
|
||||
)
|
||||
|
||||
CONF_STATE_CHARACTERISTIC = "state_characteristic"
|
||||
CONF_SAMPLES_MAX_BUFFER_SIZE = "sampling_size"
|
||||
CONF_MAX_AGE = "max_age"
|
||||
@ -80,11 +98,24 @@ DEFAULT_QUANTILE_INTERVALS = 4
|
||||
DEFAULT_QUANTILE_METHOD = "exclusive"
|
||||
ICON = "mdi:calculator"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
|
||||
def valid_binary_characteristic_configuration(config):
|
||||
"""Validate that the characteristic selected is valid for the source sensor type, throw if it isn't."""
|
||||
if config.get(CONF_ENTITY_ID).split(".")[0] == "binary_sensor":
|
||||
if config.get(CONF_STATE_CHARACTERISTIC) not in STATS_BINARY_SUPPORT:
|
||||
raise ValueError(
|
||||
"The configured characteristic '"
|
||||
+ config.get(CONF_STATE_CHARACTERISTIC)
|
||||
+ "' is not supported for a binary source sensor."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
_PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_STATE_CHARACTERISTIC, default=STAT_MEAN): vol.In(
|
||||
vol.Optional(CONF_STATE_CHARACTERISTIC, default=STAT_DEFAULT): vol.In(
|
||||
[
|
||||
STAT_AVERAGE_LINEAR,
|
||||
STAT_AVERAGE_STEP,
|
||||
@ -107,6 +138,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
STAT_VALUE_MAX,
|
||||
STAT_VALUE_MIN,
|
||||
STAT_VARIANCE,
|
||||
STAT_DEFAULT,
|
||||
]
|
||||
),
|
||||
vol.Optional(
|
||||
@ -122,6 +154,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
),
|
||||
}
|
||||
)
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
_PLATFORM_SCHEMA_BASE,
|
||||
valid_binary_characteristic_configuration,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
@ -166,6 +202,9 @@ class StatisticsSensor(SensorEntity):
|
||||
self.is_binary = self._source_entity_id.split(".")[0] == "binary_sensor"
|
||||
self._name = name
|
||||
self._state_characteristic = state_characteristic
|
||||
if self._state_characteristic == STAT_DEFAULT:
|
||||
self._state_characteristic = STAT_COUNT if self.is_binary else STAT_MEAN
|
||||
_LOGGER.warning(DEPRECATION_WARNING, self._state_characteristic, name)
|
||||
self._samples_max_buffer_size = samples_max_buffer_size
|
||||
self._samples_max_age = samples_max_age
|
||||
self._precision = precision
|
||||
@ -181,9 +220,15 @@ class StatisticsSensor(SensorEntity):
|
||||
STAT_BUFFER_USAGE_RATIO: None,
|
||||
STAT_SOURCE_VALUE_VALID: None,
|
||||
}
|
||||
self._state_characteristic_fn = getattr(
|
||||
self, f"_stat_{self._state_characteristic}"
|
||||
)
|
||||
|
||||
if self.is_binary:
|
||||
self._state_characteristic_fn = getattr(
|
||||
self, f"_stat_binary_{self._state_characteristic}"
|
||||
)
|
||||
else:
|
||||
self._state_characteristic_fn = getattr(
|
||||
self, f"_stat_{self._state_characteristic}"
|
||||
)
|
||||
|
||||
self._update_listener = None
|
||||
|
||||
@ -246,9 +291,13 @@ class StatisticsSensor(SensorEntity):
|
||||
|
||||
def _derive_unit_of_measurement(self, new_state):
|
||||
base_unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
if not base_unit:
|
||||
unit = None
|
||||
elif self.is_binary:
|
||||
if self.is_binary and self._state_characteristic in (
|
||||
STAT_AVERAGE_STEP,
|
||||
STAT_AVERAGE_TIMELESS,
|
||||
STAT_MEAN,
|
||||
):
|
||||
unit = "%"
|
||||
elif not base_unit:
|
||||
unit = None
|
||||
elif self._state_characteristic in (
|
||||
STAT_AVERAGE_LINEAR,
|
||||
@ -290,8 +339,6 @@ class StatisticsSensor(SensorEntity):
|
||||
@property
|
||||
def state_class(self):
|
||||
"""Return the state class of this entity."""
|
||||
if self.is_binary:
|
||||
return STATE_CLASS_MEASUREMENT
|
||||
if self._state_characteristic in STATS_NOT_A_NUMBER:
|
||||
return None
|
||||
return STATE_CLASS_MEASUREMENT
|
||||
@ -450,10 +497,6 @@ class StatisticsSensor(SensorEntity):
|
||||
One of the _stat_*() functions is represented by self._state_characteristic_fn().
|
||||
"""
|
||||
|
||||
if self.is_binary:
|
||||
self._value = len(self.states)
|
||||
return
|
||||
|
||||
value = self._state_characteristic_fn()
|
||||
|
||||
if self._state_characteristic not in STATS_NOT_A_NUMBER:
|
||||
@ -463,6 +506,8 @@ class StatisticsSensor(SensorEntity):
|
||||
value = int(value)
|
||||
self._value = value
|
||||
|
||||
# Statistics for numeric sensor
|
||||
|
||||
def _stat_average_linear(self):
|
||||
if len(self.states) >= 2:
|
||||
area = 0
|
||||
@ -590,3 +635,26 @@ class StatisticsSensor(SensorEntity):
|
||||
if len(self.states) >= 2:
|
||||
return statistics.variance(self.states)
|
||||
return None
|
||||
|
||||
# Statistics for binary sensor
|
||||
|
||||
def _stat_binary_average_step(self):
|
||||
if len(self.states) >= 2:
|
||||
on_seconds = 0
|
||||
for i in range(1, len(self.states)):
|
||||
if self.states[i - 1] == "on":
|
||||
on_seconds += (self.ages[i] - self.ages[i - 1]).total_seconds()
|
||||
age_range_seconds = (self.ages[-1] - self.ages[0]).total_seconds()
|
||||
return 100 / age_range_seconds * on_seconds
|
||||
return None
|
||||
|
||||
def _stat_binary_average_timeless(self):
|
||||
return self._stat_binary_mean()
|
||||
|
||||
def _stat_binary_count(self):
|
||||
return len(self.states)
|
||||
|
||||
def _stat_binary_mean(self):
|
||||
if len(self.states) > 0:
|
||||
return 100.0 / len(self.states) * self.states.count("on")
|
||||
return None
|
||||
|
@ -2,3 +2,4 @@ sensor:
|
||||
- platform: statistics
|
||||
entity_id: sensor.cpu
|
||||
name: cputest
|
||||
state_characteristic: mean
|
||||
|
@ -41,56 +41,14 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
def setup_method(self, method):
|
||||
"""Set up things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.values_binary = ["on", "off", "on", "off", "on", "off", "on"]
|
||||
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
|
||||
)
|
||||
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)
|
||||
self.addCleanup(self.hass.stop)
|
||||
|
||||
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",
|
||||
},
|
||||
{
|
||||
"platform": "statistics",
|
||||
"name": "test_unitless",
|
||||
"entity_id": "binary_sensor.test_monitored_unitless",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
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.states.set("binary_sensor.test_monitored_unitless", value)
|
||||
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(7 / 20, 2)
|
||||
assert state.attributes.get("source_value_valid") is True
|
||||
assert "age_coverage_ratio" not in state.attributes
|
||||
|
||||
state = self.hass.states.get("sensor.test_unitless")
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
|
||||
|
||||
def test_sensor_defaults_numeric(self):
|
||||
"""Test the general behavior of the sensor, with numeric source sensor."""
|
||||
assert setup_component(
|
||||
@ -178,6 +136,90 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
assert new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS
|
||||
assert new_state.attributes.get("source_value_valid") is False
|
||||
|
||||
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)
|
||||
|
||||
def test_sampling_size_non_default(self):
|
||||
"""Test rotation."""
|
||||
assert setup_component(
|
||||
@ -189,6 +231,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"platform": "statistics",
|
||||
"name": "test",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "mean",
|
||||
"sampling_size": 5,
|
||||
},
|
||||
]
|
||||
@ -223,6 +266,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"platform": "statistics",
|
||||
"name": "test",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "mean",
|
||||
"sampling_size": 1,
|
||||
},
|
||||
]
|
||||
@ -268,6 +312,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"platform": "statistics",
|
||||
"name": "test",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "mean",
|
||||
"max_age": {"minutes": 4},
|
||||
},
|
||||
]
|
||||
@ -341,6 +386,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"platform": "statistics",
|
||||
"name": "test",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "mean",
|
||||
"precision": 0,
|
||||
},
|
||||
]
|
||||
@ -373,6 +419,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"platform": "statistics",
|
||||
"name": "test",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "mean",
|
||||
"precision": 1,
|
||||
},
|
||||
]
|
||||
@ -459,6 +506,17 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"entity_id": "sensor.test_monitored_unitless",
|
||||
"state_characteristic": "change_second",
|
||||
},
|
||||
{
|
||||
"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",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
@ -473,6 +531,12 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
value,
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
for value in self.values_binary:
|
||||
self.hass.states.set(
|
||||
"binary_sensor.test_monitored_unitless",
|
||||
value,
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get("sensor.test_unitless_1")
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
|
||||
@ -480,6 +544,10 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
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
|
||||
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) == "%"
|
||||
|
||||
def test_state_characteristics(self):
|
||||
"""Test configured state characteristic for value and unit."""
|
||||
@ -495,6 +563,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
|
||||
characteristics = (
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "average_linear",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
@ -502,6 +571,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "average_step",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
@ -509,6 +579,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "average_timeless",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": float(self.values[0]),
|
||||
@ -516,6 +587,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "change",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": float(0),
|
||||
@ -523,6 +595,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "change_sample",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
@ -534,6 +607,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C/sample",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "change_second",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
@ -547,6 +621,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C/s",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "count",
|
||||
"value_0": 0,
|
||||
"value_1": 1,
|
||||
@ -554,6 +629,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": None,
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "datetime_newest",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": datetime(
|
||||
@ -577,6 +653,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": None,
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "datetime_oldest",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": datetime(
|
||||
@ -592,6 +669,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": None,
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "distance_95_percent_of_values",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
@ -599,6 +677,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "distance_99_percent_of_values",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
@ -606,6 +685,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "distance_absolute",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": float(0),
|
||||
@ -613,6 +693,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "mean",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": float(self.values[0]),
|
||||
@ -620,6 +701,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "median",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": float(self.values[0]),
|
||||
@ -627,6 +709,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "noisiness",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
@ -636,6 +719,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "quantiles",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
@ -645,6 +729,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": None,
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "standard_deviation",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
@ -652,6 +737,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "total",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": float(self.values[0]),
|
||||
@ -659,6 +745,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "value_max",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": float(self.values[0]),
|
||||
@ -666,6 +753,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "value_min",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": float(self.values[0]),
|
||||
@ -673,20 +761,53 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"unit": "°C",
|
||||
},
|
||||
{
|
||||
"source_sensor_domain": "sensor",
|
||||
"name": "variance",
|
||||
"value_0": STATE_UNKNOWN,
|
||||
"value_1": STATE_UNKNOWN,
|
||||
"value_9": float(round(statistics.variance(self.values), 2)),
|
||||
"unit": "°C²",
|
||||
},
|
||||
{
|
||||
"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": "%",
|
||||
},
|
||||
)
|
||||
sensors_config = []
|
||||
for characteristic in characteristics:
|
||||
sensors_config.append(
|
||||
{
|
||||
"platform": "statistics",
|
||||
"name": "test_" + characteristic["name"],
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"name": f"test_{characteristic['source_sensor_domain']}_{characteristic['name']}",
|
||||
"entity_id": f"{characteristic['source_sensor_domain']}.test_monitored",
|
||||
"state_characteristic": characteristic["name"],
|
||||
"max_age": {"minutes": 10},
|
||||
}
|
||||
@ -707,20 +828,29 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
|
||||
# With all values in buffer
|
||||
|
||||
for value in self.values:
|
||||
for i in range(len(self.values)):
|
||||
self.hass.states.set(
|
||||
"sensor.test_monitored",
|
||||
value,
|
||||
self.values[i],
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
||||
)
|
||||
self.hass.states.set(
|
||||
"binary_sensor.test_monitored",
|
||||
self.values_binary[i],
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
mock_data["return_time"] += timedelta(minutes=value_spacing_minutes)
|
||||
|
||||
for characteristic in characteristics:
|
||||
state = self.hass.states.get("sensor.test_" + characteristic["name"])
|
||||
state = self.hass.states.get(
|
||||
f"sensor.test_{characteristic['source_sensor_domain']}_{characteristic['name']}"
|
||||
)
|
||||
assert state.state == str(characteristic["value_9"]), (
|
||||
f"value mismatch for characteristic '{characteristic['name']}' (buffer filled) "
|
||||
f"- assert {state.state} == {str(characteristic['value_9'])}"
|
||||
f"value mismatch for characteristic "
|
||||
f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' "
|
||||
f"(buffer filled) - "
|
||||
f"assert {state.state} == {str(characteristic['value_9'])}"
|
||||
)
|
||||
assert (
|
||||
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
@ -734,10 +864,14 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
self.hass.block_till_done()
|
||||
|
||||
for characteristic in characteristics:
|
||||
state = self.hass.states.get("sensor.test_" + characteristic["name"])
|
||||
state = self.hass.states.get(
|
||||
f"sensor.test_{characteristic['source_sensor_domain']}_{characteristic['name']}"
|
||||
)
|
||||
assert state.state == str(characteristic["value_0"]), (
|
||||
f"value mismatch for characteristic '{characteristic['name']}' (buffer empty) "
|
||||
f"- assert {state.state} == {str(characteristic['value_0'])}"
|
||||
f"value mismatch for characteristic "
|
||||
f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' "
|
||||
f"(buffer empty) - "
|
||||
f"assert {state.state} == {str(characteristic['value_0'])}"
|
||||
)
|
||||
|
||||
# With single value in buffer
|
||||
@ -747,15 +881,65 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
self.values[0],
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
self.hass.states.set(
|
||||
"binary_sensor.test_monitored",
|
||||
self.values_binary[0],
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS},
|
||||
force_update=True,
|
||||
)
|
||||
mock_data["return_time"] += timedelta(minutes=1)
|
||||
fire_time_changed(self.hass, mock_data["return_time"])
|
||||
self.hass.block_till_done()
|
||||
|
||||
for characteristic in characteristics:
|
||||
state = self.hass.states.get("sensor.test_" + characteristic["name"])
|
||||
assert state.state == str(characteristic["value_1"]), (
|
||||
f"value mismatch for characteristic '{characteristic['name']}' (one stored value) "
|
||||
f"- assert {state.state} == {str(characteristic['value_1'])}"
|
||||
state = self.hass.states.get(
|
||||
f"sensor.test_{characteristic['source_sensor_domain']}_{characteristic['name']}"
|
||||
)
|
||||
assert state.state == str(characteristic["value_1"]), (
|
||||
f"value mismatch for characteristic "
|
||||
f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' "
|
||||
f"(one stored value) - "
|
||||
f"assert {state.state} == {str(characteristic['value_1'])}"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
def test_initialize_from_database(self):
|
||||
"""Test initializing the statistics from the database."""
|
||||
@ -784,6 +968,7 @@ class TestStatisticsSensor(unittest.TestCase):
|
||||
"platform": "statistics",
|
||||
"name": "test",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "mean",
|
||||
"sampling_size": 100,
|
||||
},
|
||||
]
|
||||
@ -886,6 +1071,7 @@ async def test_reload(hass):
|
||||
"platform": "statistics",
|
||||
"name": "test",
|
||||
"entity_id": "sensor.test_monitored",
|
||||
"state_characteristic": "mean",
|
||||
"sampling_size": 100,
|
||||
},
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user