mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 13:47:35 +00:00
Adds median to min_max component (#36686)
This commit is contained in:
parent
6fa04aa3e3
commit
e6ff8d6839
@ -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 logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -24,6 +24,7 @@ ATTR_MAX_VALUE = "max_value"
|
|||||||
ATTR_MAX_ENTITY_ID = "max_entity_id"
|
ATTR_MAX_ENTITY_ID = "max_entity_id"
|
||||||
ATTR_COUNT_SENSORS = "count_sensors"
|
ATTR_COUNT_SENSORS = "count_sensors"
|
||||||
ATTR_MEAN = "mean"
|
ATTR_MEAN = "mean"
|
||||||
|
ATTR_MEDIAN = "median"
|
||||||
ATTR_LAST = "last"
|
ATTR_LAST = "last"
|
||||||
ATTR_LAST_ENTITY_ID = "last_entity_id"
|
ATTR_LAST_ENTITY_ID = "last_entity_id"
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ ATTR_TO_PROPERTY = [
|
|||||||
ATTR_MAX_VALUE,
|
ATTR_MAX_VALUE,
|
||||||
ATTR_MAX_ENTITY_ID,
|
ATTR_MAX_ENTITY_ID,
|
||||||
ATTR_MEAN,
|
ATTR_MEAN,
|
||||||
|
ATTR_MEDIAN,
|
||||||
ATTR_MIN_VALUE,
|
ATTR_MIN_VALUE,
|
||||||
ATTR_MIN_ENTITY_ID,
|
ATTR_MIN_ENTITY_ID,
|
||||||
ATTR_LAST,
|
ATTR_LAST,
|
||||||
@ -47,6 +49,7 @@ SENSOR_TYPES = {
|
|||||||
ATTR_MIN_VALUE: "min",
|
ATTR_MIN_VALUE: "min",
|
||||||
ATTR_MAX_VALUE: "max",
|
ATTR_MAX_VALUE: "max",
|
||||||
ATTR_MEAN: "mean",
|
ATTR_MEAN: "mean",
|
||||||
|
ATTR_MEDIAN: "median",
|
||||||
ATTR_LAST: "last",
|
ATTR_LAST: "last",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +83,7 @@ def calc_min(sensor_values):
|
|||||||
val = None
|
val = None
|
||||||
entity_id = None
|
entity_id = None
|
||||||
for sensor_id, sensor_value in sensor_values:
|
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:
|
if val is None or val > sensor_value:
|
||||||
entity_id, val = sensor_id, sensor_value
|
entity_id, val = sensor_id, sensor_value
|
||||||
return entity_id, val
|
return entity_id, val
|
||||||
@ -91,7 +94,7 @@ def calc_max(sensor_values):
|
|||||||
val = None
|
val = None
|
||||||
entity_id = None
|
entity_id = None
|
||||||
for sensor_id, sensor_value in sensor_values:
|
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:
|
if val is None or val < sensor_value:
|
||||||
entity_id, val = sensor_id, sensor_value
|
entity_id, val = sensor_id, sensor_value
|
||||||
return entity_id, val
|
return entity_id, val
|
||||||
@ -99,15 +102,31 @@ def calc_max(sensor_values):
|
|||||||
|
|
||||||
def calc_mean(sensor_values, round_digits):
|
def calc_mean(sensor_values, round_digits):
|
||||||
"""Calculate mean value, honoring unknown states."""
|
"""Calculate mean value, honoring unknown states."""
|
||||||
sensor_value_sum = 0
|
result = []
|
||||||
count = 0
|
|
||||||
for _, sensor_value in sensor_values:
|
for _, sensor_value in sensor_values:
|
||||||
if sensor_value != STATE_UNKNOWN:
|
if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
|
||||||
sensor_value_sum += sensor_value
|
result.append(sensor_value)
|
||||||
count += 1
|
if len(result) == 0:
|
||||||
if count == 0:
|
|
||||||
return None
|
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):
|
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._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 = None
|
||||||
self._unit_of_measurement_mismatch = False
|
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.min_entity_id = self.max_entity_id = self.last_entity_id = None
|
||||||
self.count_sensors = len(self._entity_ids)
|
self.count_sensors = len(self._entity_ids)
|
||||||
self.states = {}
|
self.states = {}
|
||||||
@ -224,3 +243,4 @@ class MinMaxSensor(Entity):
|
|||||||
self.min_entity_id, self.min_value = calc_min(sensor_values)
|
self.min_entity_id, self.min_value = calc_min(sensor_values)
|
||||||
self.max_entity_id, self.max_value = calc_max(sensor_values)
|
self.max_entity_id, self.max_value = calc_max(sensor_values)
|
||||||
self.mean = calc_mean(sensor_values, self._round_digits)
|
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."""
|
"""The test for the min/max sensor platform."""
|
||||||
|
import statistics
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -27,6 +28,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||||||
self.mean = round(sum(self.values) / self.count, 2)
|
self.mean = round(sum(self.values) / self.count, 2)
|
||||||
self.mean_1_digit = round(sum(self.values) / self.count, 1)
|
self.mean_1_digit = round(sum(self.values) / self.count, 1)
|
||||||
self.mean_4_digits = round(sum(self.values) / self.count, 4)
|
self.mean_4_digits = round(sum(self.values) / self.count, 4)
|
||||||
|
self.median = round(statistics.median(self.values), 2)
|
||||||
|
|
||||||
def teardown_method(self, method):
|
def teardown_method(self, method):
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
@ -58,6 +60,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||||||
assert self.max == state.attributes.get("max_value")
|
assert self.max == state.attributes.get("max_value")
|
||||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||||
assert self.mean == state.attributes.get("mean")
|
assert self.mean == state.attributes.get("mean")
|
||||||
|
assert self.median == state.attributes.get("median")
|
||||||
|
|
||||||
def test_max_sensor(self):
|
def test_max_sensor(self):
|
||||||
"""Test the max sensor."""
|
"""Test the max sensor."""
|
||||||
@ -85,6 +88,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||||||
assert self.min == state.attributes.get("min_value")
|
assert self.min == state.attributes.get("min_value")
|
||||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||||
assert self.mean == state.attributes.get("mean")
|
assert self.mean == state.attributes.get("mean")
|
||||||
|
assert self.median == state.attributes.get("median")
|
||||||
|
|
||||||
def test_mean_sensor(self):
|
def test_mean_sensor(self):
|
||||||
"""Test the mean sensor."""
|
"""Test the mean sensor."""
|
||||||
@ -112,6 +116,7 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||||||
assert entity_ids[2] == state.attributes.get("min_entity_id")
|
assert entity_ids[2] == state.attributes.get("min_entity_id")
|
||||||
assert self.max == state.attributes.get("max_value")
|
assert self.max == state.attributes.get("max_value")
|
||||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||||
|
assert self.median == state.attributes.get("median")
|
||||||
|
|
||||||
def test_mean_1_digit_sensor(self):
|
def test_mean_1_digit_sensor(self):
|
||||||
"""Test the mean with 1-digit precision sensor."""
|
"""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 entity_ids[2] == state.attributes.get("min_entity_id")
|
||||||
assert self.max == state.attributes.get("max_value")
|
assert self.max == state.attributes.get("max_value")
|
||||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
||||||
|
assert self.median == state.attributes.get("median")
|
||||||
|
|
||||||
def test_mean_4_digit_sensor(self):
|
def test_mean_4_digit_sensor(self):
|
||||||
"""Test the mean with 1-digit precision sensor."""
|
"""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 entity_ids[2] == state.attributes.get("min_entity_id")
|
||||||
assert self.max == state.attributes.get("max_value")
|
assert self.max == state.attributes.get("max_value")
|
||||||
assert entity_ids[1] == state.attributes.get("max_entity_id")
|
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):
|
def test_not_enough_sensor_value(self):
|
||||||
"""Test that there is nothing done if not enough values available."""
|
"""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("min_value") is None
|
||||||
assert state.attributes.get("max_entity_id") is None
|
assert state.attributes.get("max_entity_id") is None
|
||||||
assert state.attributes.get("max_value") 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.states.set(entity_ids[1], self.values[1])
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
@ -295,3 +331,4 @@ class TestMinMaxSensor(unittest.TestCase):
|
|||||||
assert self.min == state.attributes.get("min_value")
|
assert self.min == state.attributes.get("min_value")
|
||||||
assert self.max == state.attributes.get("max_value")
|
assert self.max == state.attributes.get("max_value")
|
||||||
assert self.mean == state.attributes.get("mean")
|
assert self.mean == state.attributes.get("mean")
|
||||||
|
assert self.median == state.attributes.get("median")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user