Add range to min_max (#78282)
This commit is contained in:
parent
bd01f90d42
commit
f26fadbdfc
3 changed files with 73 additions and 1 deletions
|
@ -22,6 +22,7 @@ _STATISTIC_MEASURES = [
|
||||||
selector.SelectOptionDict(value="mean", label="Arithmetic mean"),
|
selector.SelectOptionDict(value="mean", label="Arithmetic mean"),
|
||||||
selector.SelectOptionDict(value="median", label="Median"),
|
selector.SelectOptionDict(value="median", label="Median"),
|
||||||
selector.SelectOptionDict(value="last", label="Most recently updated"),
|
selector.SelectOptionDict(value="last", label="Most recently updated"),
|
||||||
|
selector.SelectOptionDict(value="range", label="Statistical range"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ ATTR_MEAN = "mean"
|
||||||
ATTR_MEDIAN = "median"
|
ATTR_MEDIAN = "median"
|
||||||
ATTR_LAST = "last"
|
ATTR_LAST = "last"
|
||||||
ATTR_LAST_ENTITY_ID = "last_entity_id"
|
ATTR_LAST_ENTITY_ID = "last_entity_id"
|
||||||
|
ATTR_RANGE = "range"
|
||||||
|
|
||||||
ICON = "mdi:calculator"
|
ICON = "mdi:calculator"
|
||||||
|
|
||||||
|
@ -48,6 +49,7 @@ SENSOR_TYPES = {
|
||||||
ATTR_MEAN: "mean",
|
ATTR_MEAN: "mean",
|
||||||
ATTR_MEDIAN: "median",
|
ATTR_MEDIAN: "median",
|
||||||
ATTR_LAST: "last",
|
ATTR_LAST: "last",
|
||||||
|
ATTR_RANGE: "range",
|
||||||
}
|
}
|
||||||
SENSOR_TYPE_TO_ATTR = {v: k for k, v in SENSOR_TYPES.items()}
|
SENSOR_TYPE_TO_ATTR = {v: k for k, v in SENSOR_TYPES.items()}
|
||||||
|
|
||||||
|
@ -158,6 +160,19 @@ def calc_median(sensor_values, round_digits):
|
||||||
return round(statistics.median(result), round_digits)
|
return round(statistics.median(result), round_digits)
|
||||||
|
|
||||||
|
|
||||||
|
def calc_range(sensor_values, round_digits):
|
||||||
|
"""Calculate range value, honoring unknown states."""
|
||||||
|
result = [
|
||||||
|
sensor_value
|
||||||
|
for _, sensor_value in sensor_values
|
||||||
|
if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]
|
||||||
|
]
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
return None
|
||||||
|
return round(max(result) - min(result), round_digits)
|
||||||
|
|
||||||
|
|
||||||
class MinMaxSensor(SensorEntity):
|
class MinMaxSensor(SensorEntity):
|
||||||
"""Representation of a min/max sensor."""
|
"""Representation of a min/max sensor."""
|
||||||
|
|
||||||
|
@ -180,6 +195,7 @@ class MinMaxSensor(SensorEntity):
|
||||||
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 = self.median = None
|
self.min_value = self.max_value = self.mean = self.last = self.median = None
|
||||||
|
self.range = 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 = {}
|
||||||
|
@ -288,3 +304,4 @@ class MinMaxSensor(SensorEntity):
|
||||||
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)
|
self.median = calc_median(sensor_values, self._round_digits)
|
||||||
|
self.range = calc_range(sensor_values, self._round_digits)
|
||||||
|
|
|
@ -26,6 +26,8 @@ MEAN = round(sum(VALUES) / COUNT, 2)
|
||||||
MEAN_1_DIGIT = round(sum(VALUES) / COUNT, 1)
|
MEAN_1_DIGIT = round(sum(VALUES) / COUNT, 1)
|
||||||
MEAN_4_DIGITS = round(sum(VALUES) / COUNT, 4)
|
MEAN_4_DIGITS = round(sum(VALUES) / COUNT, 4)
|
||||||
MEDIAN = round(statistics.median(VALUES), 2)
|
MEDIAN = round(statistics.median(VALUES), 2)
|
||||||
|
RANGE_1_DIGIT = round(max(VALUES) - min(VALUES), 1)
|
||||||
|
RANGE_4_DIGITS = round(max(VALUES) - min(VALUES), 4)
|
||||||
|
|
||||||
|
|
||||||
async def test_default_name_sensor(hass):
|
async def test_default_name_sensor(hass):
|
||||||
|
@ -160,7 +162,7 @@ async def test_mean_1_digit_sensor(hass):
|
||||||
|
|
||||||
|
|
||||||
async def test_mean_4_digit_sensor(hass):
|
async def test_mean_4_digit_sensor(hass):
|
||||||
"""Test the mean with 1-digit precision sensor."""
|
"""Test the mean with 4-digit precision sensor."""
|
||||||
config = {
|
config = {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"platform": "min_max",
|
"platform": "min_max",
|
||||||
|
@ -211,6 +213,58 @@ async def test_median_sensor(hass):
|
||||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
||||||
|
|
||||||
|
|
||||||
|
async def test_range_4_digit_sensor(hass):
|
||||||
|
"""Test the range with 4-digit precision sensor."""
|
||||||
|
config = {
|
||||||
|
"sensor": {
|
||||||
|
"platform": "min_max",
|
||||||
|
"name": "test_range",
|
||||||
|
"type": "range",
|
||||||
|
"round_digits": 4,
|
||||||
|
"entity_ids": ["sensor.test_1", "sensor.test_2", "sensor.test_3"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "sensor", config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_ids = config["sensor"]["entity_ids"]
|
||||||
|
|
||||||
|
for entity_id, value in dict(zip(entity_ids, VALUES)).items():
|
||||||
|
hass.states.async_set(entity_id, value)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.test_range")
|
||||||
|
|
||||||
|
assert str(float(RANGE_4_DIGITS)) == state.state
|
||||||
|
|
||||||
|
|
||||||
|
async def test_range_1_digit_sensor(hass):
|
||||||
|
"""Test the range with 1-digit precision sensor."""
|
||||||
|
config = {
|
||||||
|
"sensor": {
|
||||||
|
"platform": "min_max",
|
||||||
|
"name": "test_range",
|
||||||
|
"type": "range",
|
||||||
|
"round_digits": 1,
|
||||||
|
"entity_ids": ["sensor.test_1", "sensor.test_2", "sensor.test_3"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "sensor", config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_ids = config["sensor"]["entity_ids"]
|
||||||
|
|
||||||
|
for entity_id, value in dict(zip(entity_ids, VALUES)).items():
|
||||||
|
hass.states.async_set(entity_id, value)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.test_range")
|
||||||
|
|
||||||
|
assert str(float(RANGE_1_DIGIT)) == state.state
|
||||||
|
|
||||||
|
|
||||||
async def test_not_enough_sensor_value(hass):
|
async def test_not_enough_sensor_value(hass):
|
||||||
"""Test that there is nothing done if not enough values available."""
|
"""Test that there is nothing done if not enough values available."""
|
||||||
config = {
|
config = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue