Add range to min_max (#78282)

This commit is contained in:
Justin Sherman 2022-09-25 20:08:31 -07:00 committed by GitHub
parent bd01f90d42
commit f26fadbdfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 1 deletions

View File

@ -22,6 +22,7 @@ _STATISTIC_MEASURES = [
selector.SelectOptionDict(value="mean", label="Arithmetic mean"),
selector.SelectOptionDict(value="median", label="Median"),
selector.SelectOptionDict(value="last", label="Most recently updated"),
selector.SelectOptionDict(value="range", label="Statistical range"),
]

View File

@ -39,6 +39,7 @@ ATTR_MEAN = "mean"
ATTR_MEDIAN = "median"
ATTR_LAST = "last"
ATTR_LAST_ENTITY_ID = "last_entity_id"
ATTR_RANGE = "range"
ICON = "mdi:calculator"
@ -48,6 +49,7 @@ SENSOR_TYPES = {
ATTR_MEAN: "mean",
ATTR_MEDIAN: "median",
ATTR_LAST: "last",
ATTR_RANGE: "range",
}
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)
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):
"""Representation of a min/max sensor."""
@ -180,6 +195,7 @@ class MinMaxSensor(SensorEntity):
self._unit_of_measurement = None
self._unit_of_measurement_mismatch = False
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.count_sensors = len(self._entity_ids)
self.states = {}
@ -288,3 +304,4 @@ class MinMaxSensor(SensorEntity):
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)
self.range = calc_range(sensor_values, self._round_digits)

View File

@ -26,6 +26,8 @@ MEAN = round(sum(VALUES) / COUNT, 2)
MEAN_1_DIGIT = round(sum(VALUES) / COUNT, 1)
MEAN_4_DIGITS = round(sum(VALUES) / COUNT, 4)
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):
@ -160,7 +162,7 @@ async def test_mean_1_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 = {
"sensor": {
"platform": "min_max",
@ -211,6 +213,58 @@ async def test_median_sensor(hass):
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):
"""Test that there is nothing done if not enough values available."""
config = {