Adds median to min_max component (#36686)
This commit is contained in:
parent
6fa04aa3e3
commit
e6ff8d6839
2 changed files with 68 additions and 11 deletions
|
@ -1,4 +1,4 @@
|
|||
"""Support for displaying the minimal and the maximal value."""
|
||||
"""Support for displaying minimal, maximal, mean or median values."""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
@ -24,6 +24,7 @@ ATTR_MAX_VALUE = "max_value"
|
|||
ATTR_MAX_ENTITY_ID = "max_entity_id"
|
||||
ATTR_COUNT_SENSORS = "count_sensors"
|
||||
ATTR_MEAN = "mean"
|
||||
ATTR_MEDIAN = "median"
|
||||
ATTR_LAST = "last"
|
||||
ATTR_LAST_ENTITY_ID = "last_entity_id"
|
||||
|
||||
|
@ -32,6 +33,7 @@ ATTR_TO_PROPERTY = [
|
|||
ATTR_MAX_VALUE,
|
||||
ATTR_MAX_ENTITY_ID,
|
||||
ATTR_MEAN,
|
||||
ATTR_MEDIAN,
|
||||
ATTR_MIN_VALUE,
|
||||
ATTR_MIN_ENTITY_ID,
|
||||
ATTR_LAST,
|
||||
|
@ -47,6 +49,7 @@ SENSOR_TYPES = {
|
|||
ATTR_MIN_VALUE: "min",
|
||||
ATTR_MAX_VALUE: "max",
|
||||
ATTR_MEAN: "mean",
|
||||
ATTR_MEDIAN: "median",
|
||||
ATTR_LAST: "last",
|
||||
}
|
||||
|
||||
|
@ -80,7 +83,7 @@ def calc_min(sensor_values):
|
|||
val = None
|
||||
entity_id = None
|
||||
for sensor_id, sensor_value in sensor_values:
|
||||
if sensor_value != STATE_UNKNOWN:
|
||||
if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
|
||||
if val is None or val > sensor_value:
|
||||
entity_id, val = sensor_id, sensor_value
|
||||
return entity_id, val
|
||||
|
@ -91,7 +94,7 @@ def calc_max(sensor_values):
|
|||
val = None
|
||||
entity_id = None
|
||||
for sensor_id, sensor_value in sensor_values:
|
||||
if sensor_value != STATE_UNKNOWN:
|
||||
if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
|
||||
if val is None or val < sensor_value:
|
||||
entity_id, val = sensor_id, sensor_value
|
||||
return entity_id, val
|
||||
|
@ -99,15 +102,31 @@ def calc_max(sensor_values):
|
|||
|
||||
def calc_mean(sensor_values, round_digits):
|
||||
"""Calculate mean value, honoring unknown states."""
|
||||
sensor_value_sum = 0
|
||||
count = 0
|
||||
result = []
|
||||
for _, sensor_value in sensor_values:
|
||||
if sensor_value != STATE_UNKNOWN:
|
||||
sensor_value_sum += sensor_value
|
||||
count += 1
|
||||
if count == 0:
|
||||
if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
|
||||
result.append(sensor_value)
|
||||
if len(result) == 0:
|
||||
return None
|
||||
return round(sensor_value_sum / count, round_digits)
|
||||
return round(sum(result) / len(result), round_digits)
|
||||
|
||||
|
||||
def calc_median(sensor_values, round_digits):
|
||||
"""Calculate median value, honoring unknown states."""
|
||||
result = []
|
||||
for _, sensor_value in sensor_values:
|
||||
if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
|
||||
result.append(sensor_value)
|
||||
if len(result) == 0:
|
||||
return None
|
||||
result.sort()
|
||||
if len(result) % 2 == 0:
|
||||
median1 = result[len(result) // 2]
|
||||
median2 = result[len(result) // 2 - 1]
|
||||
median = (median1 + median2) / 2
|
||||
else:
|
||||
median = result[len(result) // 2]
|
||||
return round(median, round_digits)
|
||||
|
||||
|
||||
class MinMaxSensor(Entity):
|
||||
|
@ -126,7 +145,7 @@ class MinMaxSensor(Entity):
|
|||
self._name = f"{next(v for k, v in SENSOR_TYPES.items() if self._sensor_type == v)} sensor".capitalize()
|
||||
self._unit_of_measurement = None
|
||||
self._unit_of_measurement_mismatch = False
|
||||
self.min_value = self.max_value = self.mean = self.last = None
|
||||
self.min_value = self.max_value = self.mean = self.last = self.median = None
|
||||
self.min_entity_id = self.max_entity_id = self.last_entity_id = None
|
||||
self.count_sensors = len(self._entity_ids)
|
||||
self.states = {}
|
||||
|
@ -224,3 +243,4 @@ class MinMaxSensor(Entity):
|
|||
self.min_entity_id, self.min_value = calc_min(sensor_values)
|
||||
self.max_entity_id, self.max_value = calc_max(sensor_values)
|
||||
self.mean = calc_mean(sensor_values, self._round_digits)
|
||||
self.median = calc_median(sensor_values, self._round_digits)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""The test for the min/max sensor platform."""
|
||||
import statistics
|
||||
import unittest
|
||||
|
||||
from homeassistant.const import (
|
||||
|
@ -27,6 +28,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||
self.mean = round(sum(self.values) / self.count, 2)
|
||||
self.mean_1_digit = round(sum(self.values) / self.count, 1)
|
||||
self.mean_4_digits = round(sum(self.values) / self.count, 4)
|
||||
self.median = round(statistics.median(self.values), 2)
|
||||
|
||||
def teardown_method(self, method):
|
||||
"""Stop everything that was started."""
|
||||
|
@ -58,6 +60,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||
assert self.max == state.attributes.get("max_value")
|
||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||
assert self.mean == state.attributes.get("mean")
|
||||
assert self.median == state.attributes.get("median")
|
||||
|
||||
def test_max_sensor(self):
|
||||
"""Test the max sensor."""
|
||||
|
@ -85,6 +88,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||
assert self.min == state.attributes.get("min_value")
|
||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||
assert self.mean == state.attributes.get("mean")
|
||||
assert self.median == state.attributes.get("median")
|
||||
|
||||
def test_mean_sensor(self):
|
||||
"""Test the mean sensor."""
|
||||
|
@ -112,6 +116,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||
assert entity_ids[2] == state.attributes.get("min_entity_id")
|
||||
assert self.max == state.attributes.get("max_value")
|
||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||
assert self.median == state.attributes.get("median")
|
||||
|
||||
def test_mean_1_digit_sensor(self):
|
||||
"""Test the mean with 1-digit precision sensor."""
|
||||
|
@ -140,6 +145,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||
assert entity_ids[2] == state.attributes.get("min_entity_id")
|
||||
assert self.max == state.attributes.get("max_value")
|
||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||
assert self.median == state.attributes.get("median")
|
||||
|
||||
def test_mean_4_digit_sensor(self):
|
||||
"""Test the mean with 1-digit precision sensor."""
|
||||
|
@ -168,6 +174,35 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||
assert entity_ids[2] == state.attributes.get("min_entity_id")
|
||||
assert self.max == state.attributes.get("max_value")
|
||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||
assert self.median == state.attributes.get("median")
|
||||
|
||||
def test_median_sensor(self):
|
||||
"""Test the median sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
"platform": "min_max",
|
||||
"name": "test_median",
|
||||
"type": "median",
|
||||
"entity_ids": ["sensor.test_1", "sensor.test_2", "sensor.test_3"],
|
||||
}
|
||||
}
|
||||
|
||||
assert setup_component(self.hass, "sensor", config)
|
||||
|
||||
entity_ids = config["sensor"]["entity_ids"]
|
||||
|
||||
for entity_id, value in dict(zip(entity_ids, self.values)).items():
|
||||
self.hass.states.set(entity_id, value)
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get("sensor.test_median")
|
||||
|
||||
assert str(float(self.median)) == state.state
|
||||
assert self.min == state.attributes.get("min_value")
|
||||
assert entity_ids[2] == state.attributes.get("min_entity_id")
|
||||
assert self.max == state.attributes.get("max_value")
|
||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||
assert self.mean == state.attributes.get("mean")
|
||||
|
||||
def test_not_enough_sensor_value(self):
|
||||
"""Test that there is nothing done if not enough values available."""
|
||||
|
@ -193,6 +228,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||
assert state.attributes.get("min_value") is None
|
||||
assert state.attributes.get("max_entity_id") is None
|
||||
assert state.attributes.get("max_value") is None
|
||||
assert state.attributes.get("median") is None
|
||||
|
||||
self.hass.states.set(entity_ids[1], self.values[1])
|
||||
self.hass.block_till_done()
|
||||
|
@ -295,3 +331,4 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||
assert self.min == state.attributes.get("min_value")
|
||||
assert self.max == state.attributes.get("max_value")
|
||||
assert self.mean == state.attributes.get("mean")
|
||||
assert self.median == state.attributes.get("median")
|
||||
|
|
Loading…
Add table
Reference in a new issue