diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index bc6acb2732a..ac62b63e8ca 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -64,8 +64,12 @@ STAT_CHANGE = "change" STAT_CHANGE_SAMPLE = "change_sample" STAT_CHANGE_SECOND = "change_second" STAT_COUNT = "count" +STAT_COUNT_BINARY_ON = "count_on" +STAT_COUNT_BINARY_OFF = "count_off" STAT_DATETIME_NEWEST = "datetime_newest" STAT_DATETIME_OLDEST = "datetime_oldest" +STAT_DATETIME_VALUE_MAX = "datetime_value_max" +STAT_DATETIME_VALUE_MIN = "datetime_value_min" STAT_DISTANCE_95P = "distance_95_percent_of_values" STAT_DISTANCE_99P = "distance_99_percent_of_values" STAT_DISTANCE_ABSOLUTE = "distance_absolute" @@ -99,6 +103,8 @@ STATS_NUMERIC_SUPPORT = { STAT_COUNT, STAT_DATETIME_NEWEST, STAT_DATETIME_OLDEST, + STAT_DATETIME_VALUE_MAX, + STAT_DATETIME_VALUE_MIN, STAT_DISTANCE_95P, STAT_DISTANCE_99P, STAT_DISTANCE_ABSOLUTE, @@ -118,18 +124,26 @@ STATS_BINARY_SUPPORT = { STAT_AVERAGE_STEP, STAT_AVERAGE_TIMELESS, STAT_COUNT, + STAT_COUNT_BINARY_ON, + STAT_COUNT_BINARY_OFF, + STAT_DATETIME_NEWEST, + STAT_DATETIME_OLDEST, STAT_MEAN, } STATS_NOT_A_NUMBER = { STAT_DATETIME_NEWEST, STAT_DATETIME_OLDEST, + STAT_DATETIME_VALUE_MAX, + STAT_DATETIME_VALUE_MIN, STAT_QUANTILES, } STATS_DATETIME = { STAT_DATETIME_NEWEST, STAT_DATETIME_OLDEST, + STAT_DATETIME_VALUE_MAX, + STAT_DATETIME_VALUE_MIN, } # Statistics which retain the unit of the source entity @@ -351,7 +365,7 @@ class StatisticsSensor(SensorEntity): except ValueError: self.attributes[STAT_SOURCE_VALUE_VALID] = False _LOGGER.error( - "%s: parsing error, expected number and received %s", + "%s: parsing error. Expected number or binary state, but received '%s'", self.entity_id, new_state.state, ) @@ -370,7 +384,11 @@ class StatisticsSensor(SensorEntity): unit = base_unit elif self._state_characteristic in STATS_NOT_A_NUMBER: unit = None - elif self._state_characteristic == STAT_COUNT: + elif self._state_characteristic in ( + STAT_COUNT, + STAT_COUNT_BINARY_ON, + STAT_COUNT_BINARY_OFF, + ): unit = None elif self._state_characteristic == STAT_VARIANCE: unit = base_unit + "²" @@ -614,6 +632,16 @@ class StatisticsSensor(SensorEntity): return self.ages[0] return None + def _stat_datetime_value_max(self) -> datetime | None: + if len(self.states) > 0: + return self.ages[self.states.index(max(self.states))] + return None + + def _stat_datetime_value_min(self) -> datetime | None: + if len(self.states) > 0: + return self.ages[self.states.index(min(self.states))] + return None + def _stat_distance_95_percent_of_values(self) -> StateType: if len(self.states) >= 2: return 2 * 1.96 * cast(float, self._stat_standard_deviation()) @@ -704,6 +732,18 @@ class StatisticsSensor(SensorEntity): def _stat_binary_count(self) -> StateType: return len(self.states) + def _stat_binary_count_on(self) -> StateType: + return self.states.count(True) + + def _stat_binary_count_off(self) -> StateType: + return self.states.count(False) + + def _stat_binary_datetime_newest(self) -> datetime | None: + return self._stat_datetime_newest() + + def _stat_binary_datetime_oldest(self) -> datetime | None: + return self._stat_datetime_oldest() + def _stat_binary_mean(self) -> StateType: if len(self.states) > 0: return 100.0 / len(self.states) * self.states.count(True) diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index dbcb3b1b8e7..56255216f52 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -687,6 +687,22 @@ async def test_state_characteristics(hass: HomeAssistant): "value_9": (start_datetime + timedelta(minutes=1)).isoformat(), "unit": None, }, + { + "source_sensor_domain": "sensor", + "name": "datetime_value_max", + "value_0": STATE_UNKNOWN, + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "value_9": (start_datetime + timedelta(minutes=2)).isoformat(), + "unit": None, + }, + { + "source_sensor_domain": "sensor", + "name": "datetime_value_min", + "value_0": STATE_UNKNOWN, + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "value_9": (start_datetime + timedelta(minutes=5)).isoformat(), + "unit": None, + }, { "source_sensor_domain": "sensor", "name": "distance_95_percent_of_values", @@ -811,6 +827,38 @@ async def test_state_characteristics(hass: HomeAssistant): "value_9": len(VALUES_BINARY), "unit": None, }, + { + "source_sensor_domain": "binary_sensor", + "name": "count_on", + "value_0": 0, + "value_1": 1, + "value_9": VALUES_BINARY.count("on"), + "unit": None, + }, + { + "source_sensor_domain": "binary_sensor", + "name": "count_off", + "value_0": 0, + "value_1": 0, + "value_9": VALUES_BINARY.count("off"), + "unit": None, + }, + { + "source_sensor_domain": "binary_sensor", + "name": "datetime_newest", + "value_0": STATE_UNKNOWN, + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "value_9": (start_datetime + timedelta(minutes=9)).isoformat(), + "unit": None, + }, + { + "source_sensor_domain": "binary_sensor", + "name": "datetime_oldest", + "value_0": STATE_UNKNOWN, + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "value_9": (start_datetime + timedelta(minutes=1)).isoformat(), + "unit": None, + }, { "source_sensor_domain": "binary_sensor", "name": "mean",