From 635d8c01fbfbf006d9622b4770079ee5ec511d7e Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 24 Nov 2022 20:21:09 +0100 Subject: [PATCH] Add sum to min_max helper (#82651) sum to min_max --- .../components/min_max/config_flow.py | 1 + homeassistant/components/min_max/sensor.py | 16 +++++ homeassistant/components/min_max/strings.json | 8 +-- .../components/min_max/translations/en.json | 10 ++-- tests/components/min_max/test_sensor.py | 58 +++++++++++++++++++ 5 files changed, 84 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/min_max/config_flow.py b/homeassistant/components/min_max/config_flow.py index 0fed67f15b9..d5c9ce43a99 100644 --- a/homeassistant/components/min_max/config_flow.py +++ b/homeassistant/components/min_max/config_flow.py @@ -23,6 +23,7 @@ _STATISTIC_MEASURES = [ selector.SelectOptionDict(value="median", label="Median"), selector.SelectOptionDict(value="last", label="Most recently updated"), selector.SelectOptionDict(value="range", label="Statistical range"), + selector.SelectOptionDict(value="sum", label="Sum"), ] diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index c052cbc211d..d0064d07511 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -48,6 +48,7 @@ ATTR_MEDIAN = "median" ATTR_LAST = "last" ATTR_LAST_ENTITY_ID = "last_entity_id" ATTR_RANGE = "range" +ATTR_SUM = "sum" ICON = "mdi:calculator" @@ -58,6 +59,7 @@ SENSOR_TYPES = { ATTR_MEDIAN: "median", ATTR_LAST: "last", ATTR_RANGE: "range", + ATTR_SUM: "sum", } 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 +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): """Representation of a min/max sensor.""" @@ -222,6 +236,7 @@ class MinMaxSensor(SensorEntity): self.last: float | None = None self.median: float | None = None self.range: float | None = None + self.sum: float | None = None self.min_entity_id: str | None = None self.max_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.median = calc_median(sensor_values, self._round_digits) self.range = calc_range(sensor_values, self._round_digits) + self.sum = calc_sum(sensor_values, self._round_digits) diff --git a/homeassistant/components/min_max/strings.json b/homeassistant/components/min_max/strings.json index 596dd2250eb..67e8416bc2c 100644 --- a/homeassistant/components/min_max/strings.json +++ b/homeassistant/components/min_max/strings.json @@ -1,10 +1,10 @@ { - "title": "Min / max / mean / median sensor", + "title": "Combine the state of several sensors", "config": { "step": { "user": { - "title": "Add min / max / mean / median sensor", - "description": "Create a sensor that calculates a min, max, mean or median value from a list of input sensors.", + "title": "Combine the state of several sensors", + "description": "Create a sensor that calculates a min, max, mean, median or sum from a list of input sensors.", "data": { "entity_ids": "Input entities", "name": "Name", @@ -12,7 +12,7 @@ "type": "Statistic characteristic" }, "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." } } } diff --git a/homeassistant/components/min_max/translations/en.json b/homeassistant/components/min_max/translations/en.json index 8cc0d41c419..6c661c980c0 100644 --- a/homeassistant/components/min_max/translations/en.json +++ b/homeassistant/components/min_max/translations/en.json @@ -9,10 +9,10 @@ "type": "Statistic characteristic" }, "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.", - "title": "Add min / max / mean / median sensor" + "description": "Create a sensor that calculates a min, max, mean, median or sum from a list of input sensors.", + "title": "Combine the state of several sensors" } } }, @@ -25,10 +25,10 @@ "type": "Statistic characteristic" }, "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" } \ No newline at end of file diff --git a/tests/components/min_max/test_sensor.py b/tests/components/min_max/test_sensor.py index 96cbf7317c7..9ba043427b5 100644 --- a/tests/components/min_max/test_sensor.py +++ b/tests/components/min_max/test_sensor.py @@ -33,6 +33,7 @@ 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) +SUM_VALUE = sum(VALUES) 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 "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