Adds median to min_max component (#36686)

This commit is contained in:
Thorjan Knudsvik 2020-07-19 01:18:31 +02:00 committed by GitHub
parent 6fa04aa3e3
commit e6ff8d6839
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 11 deletions

View file

@ -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)

View file

@ -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")