Add sum to min_max helper (#82651)

sum to min_max
This commit is contained in:
G Johansson 2022-11-24 20:21:09 +01:00 committed by GitHub
parent cf681cd921
commit 635d8c01fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 9 deletions

View File

@ -23,6 +23,7 @@ _STATISTIC_MEASURES = [
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"), selector.SelectOptionDict(value="range", label="Statistical range"),
selector.SelectOptionDict(value="sum", label="Sum"),
] ]

View File

@ -48,6 +48,7 @@ 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" ATTR_RANGE = "range"
ATTR_SUM = "sum"
ICON = "mdi:calculator" ICON = "mdi:calculator"
@ -58,6 +59,7 @@ SENSOR_TYPES = {
ATTR_MEDIAN: "median", ATTR_MEDIAN: "median",
ATTR_LAST: "last", ATTR_LAST: "last",
ATTR_RANGE: "range", ATTR_RANGE: "range",
ATTR_SUM: "sum",
} }
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()}
@ -188,6 +190,18 @@ def calc_range(sensor_values: list[tuple[str, Any]], round_digits: int) -> float
return value return value
def calc_sum(sensor_values: list[tuple[str, Any]], round_digits: int) -> float | None:
"""Calculate a sum of values, not honoring unknown states."""
result = 0
for _, sensor_value in sensor_values:
if sensor_value in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
return None
result += sensor_value
value: float = round(result, round_digits)
return value
class MinMaxSensor(SensorEntity): class MinMaxSensor(SensorEntity):
"""Representation of a min/max sensor.""" """Representation of a min/max sensor."""
@ -222,6 +236,7 @@ class MinMaxSensor(SensorEntity):
self.last: float | None = None self.last: float | None = None
self.median: float | None = None self.median: float | None = None
self.range: float | None = None self.range: float | None = None
self.sum: float | None = None
self.min_entity_id: str | None = None self.min_entity_id: str | None = None
self.max_entity_id: str | None = None self.max_entity_id: str | None = None
self.last_entity_id: str | None = None self.last_entity_id: str | None = None
@ -336,3 +351,4 @@ class MinMaxSensor(SensorEntity):
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) self.range = calc_range(sensor_values, self._round_digits)
self.sum = calc_sum(sensor_values, self._round_digits)

View File

@ -1,10 +1,10 @@
{ {
"title": "Min / max / mean / median sensor", "title": "Combine the state of several sensors",
"config": { "config": {
"step": { "step": {
"user": { "user": {
"title": "Add min / max / mean / median sensor", "title": "Combine the state of several sensors",
"description": "Create a sensor that calculates a min, max, mean or median value from a list of input sensors.", "description": "Create a sensor that calculates a min, max, mean, median or sum from a list of input sensors.",
"data": { "data": {
"entity_ids": "Input entities", "entity_ids": "Input entities",
"name": "Name", "name": "Name",
@ -12,7 +12,7 @@
"type": "Statistic characteristic" "type": "Statistic characteristic"
}, },
"data_description": { "data_description": {
"round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean or median." "round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean, median or sum."
} }
} }
} }

View File

@ -9,10 +9,10 @@
"type": "Statistic characteristic" "type": "Statistic characteristic"
}, },
"data_description": { "data_description": {
"round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean or median." "round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean, median or sum."
}, },
"description": "Create a sensor that calculates a min, max, mean or median value from a list of input sensors.", "description": "Create a sensor that calculates a min, max, mean, median or sum from a list of input sensors.",
"title": "Add min / max / mean / median sensor" "title": "Combine the state of several sensors"
} }
} }
}, },
@ -25,10 +25,10 @@
"type": "Statistic characteristic" "type": "Statistic characteristic"
}, },
"data_description": { "data_description": {
"round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean or median." "round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean, median or sum."
} }
} }
} }
}, },
"title": "Min / max / mean / median sensor" "title": "Combine the state of several sensors"
} }

View File

@ -33,6 +33,7 @@ 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_1_DIGIT = round(max(VALUES) - min(VALUES), 1)
RANGE_4_DIGITS = round(max(VALUES) - min(VALUES), 4) RANGE_4_DIGITS = round(max(VALUES) - min(VALUES), 4)
SUM_VALUE = sum(VALUES)
async def test_default_name_sensor(hass: HomeAssistant) -> None: async def test_default_name_sensor(hass: HomeAssistant) -> None:
@ -466,3 +467,60 @@ async def test_sensor_incorrect_state(
assert state.state == "15.3" assert state.state == "15.3"
assert "Unable to store state. Only numerical states are supported" in caplog.text assert "Unable to store state. Only numerical states are supported" in caplog.text
async def test_sum_sensor(hass: HomeAssistant) -> None:
"""Test the sum sensor."""
config = {
"sensor": {
"platform": "min_max",
"name": "test_sum",
"type": "sum",
"entity_ids": ["sensor.test_1", "sensor.test_2", "sensor.test_3"],
"unique_id": "very_unique_id_sum_sensor",
}
}
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_sum")
assert str(float(SUM_VALUE)) == state.state
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entity_reg = er.async_get(hass)
entity = entity_reg.async_get("sensor.test_sum")
assert entity.unique_id == "very_unique_id_sum_sensor"
async def test_sum_sensor_no_state(hass: HomeAssistant) -> None:
"""Test the sum sensor with no state ."""
config = {
"sensor": {
"platform": "min_max",
"name": "test_sum",
"type": "sum",
"entity_ids": ["sensor.test_1", "sensor.test_2", "sensor.test_3"],
"unique_id": "very_unique_id_sum_sensor",
}
}
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_ERROR)).items():
hass.states.async_set(entity_id, value)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_sum")
assert state.state == STATE_UNKNOWN