From c21975ec13270e131488abdc415f32a5df1bf023 Mon Sep 17 00:00:00 2001 From: Thomas Dietrich Date: Wed, 22 Dec 2021 20:30:28 +0100 Subject: [PATCH 1/4] Improve config checking, add device_class timestamp --- homeassistant/components/statistics/sensor.py | 175 +++++++++++++----- tests/components/statistics/test_sensor.py | 81 +++++++- 2 files changed, 198 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 3ec1c6fc5124ad..6585cb368c5a3b 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -16,6 +16,7 @@ from homeassistant.components.recorder.util import execute, session_scope from homeassistant.components.sensor import ( PLATFORM_SCHEMA, + SensorDeviceClass, SensorEntity, SensorStateClass, ) @@ -61,8 +62,12 @@ 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" @@ -76,28 +81,73 @@ STAT_VALUE_MIN = "value_min" STAT_VARIANCE = "variance" -STAT_DEFAULT = "default" -DEPRECATION_WARNING = ( +DEPRECATION_WARNING_CHARACTERISTIC = ( "The configuration parameter 'state_characteristic' will become " "mandatory in a future release of the statistics integration. " "Please add 'state_characteristic: %s' to the configuration of " - 'sensor "%s" to keep the current behavior. Read the documentation ' + "sensor '%s' to keep the current behavior. Read the documentation " "for further details: " "https://www.home-assistant.io/integrations/statistics/" ) +DEPRECATION_WARNING_SIZE = ( + "The configuration of either 'sampling_size' or 'max_age' will become " + "mandatory in a future release of the statistics integration. Please " + "add 'sampling_size: %s' to the configuration of sensor '%s' to keep " + "the current behavior. Read the documentation for further details: " + "https://www.home-assistant.io/integrations/statistics/" +) -STATS_NOT_A_NUMBER = ( - STAT_DATETIME_OLDEST, + +STATS_NUMERIC_SUPPORT = ( + STAT_AVERAGE_LINEAR, + STAT_AVERAGE_STEP, + STAT_AVERAGE_TIMELESS, + STAT_CHANGE_SAMPLE, + STAT_CHANGE_SECOND, + STAT_CHANGE, + 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, + STAT_MEAN, + STAT_MEDIAN, + STAT_NOISINESS, STAT_QUANTILES, + STAT_STANDARD_DEVIATION, + STAT_TOTAL, + STAT_VALUE_MAX, + STAT_VALUE_MIN, + STAT_VARIANCE, ) 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, - STAT_DEFAULT, +) + +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, ) CONF_STATE_CHARACTERISTIC = "state_characteristic" @@ -115,14 +165,40 @@ ICON = "mdi:calculator" -def valid_binary_characteristic_configuration(config: dict[str, Any]) -> dict[str, Any]: +def valid_state_characteristic_configuration(config: dict[str, Any]) -> dict[str, Any]: """Validate that the characteristic selected is valid for the source sensor type, throw if it isn't.""" - if split_entity_id(str(config.get(CONF_ENTITY_ID)))[0] == BINARY_SENSOR_DOMAIN: - if config.get(CONF_STATE_CHARACTERISTIC) not in STATS_BINARY_SUPPORT: - raise ValueError( - "The configured characteristic '" - + str(config.get(CONF_STATE_CHARACTERISTIC)) - + "' is not supported for a binary source sensor." + is_binary = split_entity_id(config[CONF_ENTITY_ID])[0] == BINARY_SENSOR_DOMAIN + + if config.get(CONF_STATE_CHARACTERISTIC) is None: + config[CONF_STATE_CHARACTERISTIC] = STAT_COUNT if is_binary else STAT_MEAN + _LOGGER.warning( + DEPRECATION_WARNING_CHARACTERISTIC, + config[CONF_STATE_CHARACTERISTIC], + config[CONF_NAME], + ) + + characteristic = cast(str, config[CONF_STATE_CHARACTERISTIC]) + if (is_binary and characteristic not in STATS_BINARY_SUPPORT) or ( + not is_binary and characteristic not in STATS_NUMERIC_SUPPORT + ): + raise vol.ValueInvalid( + "The configured characteristic '{}' is not supported for the configured source sensor".format( + characteristic + ) + ) + return config + + +def valid_boundary_configuration(config: dict[str, Any]) -> dict[str, Any]: + """Validate that either sampling_size or max_age are provided.""" + + if config.get(CONF_SAMPLES_MAX_BUFFER_SIZE) is None: + config[CONF_SAMPLES_MAX_BUFFER_SIZE] = DEFAULT_BUFFER_SIZE + if config.get(CONF_MAX_AGE) is None: + _LOGGER.warning( + DEPRECATION_WARNING_SIZE, + str(DEFAULT_BUFFER_SIZE), + config[CONF_NAME], ) return config @@ -132,35 +208,8 @@ def valid_binary_characteristic_configuration(config: dict[str, Any]) -> dict[st vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string, - vol.Optional(CONF_STATE_CHARACTERISTIC, default=STAT_DEFAULT): vol.In( - [ - STAT_AVERAGE_LINEAR, - STAT_AVERAGE_STEP, - STAT_AVERAGE_TIMELESS, - STAT_CHANGE_SAMPLE, - STAT_CHANGE_SECOND, - STAT_CHANGE, - STAT_COUNT, - STAT_DATETIME_NEWEST, - STAT_DATETIME_OLDEST, - STAT_DISTANCE_95P, - STAT_DISTANCE_99P, - STAT_DISTANCE_ABSOLUTE, - STAT_MEAN, - STAT_MEDIAN, - STAT_NOISINESS, - STAT_QUANTILES, - STAT_STANDARD_DEVIATION, - STAT_TOTAL, - STAT_VALUE_MAX, - STAT_VALUE_MIN, - STAT_VARIANCE, - STAT_DEFAULT, - ] - ), - vol.Optional( - CONF_SAMPLES_MAX_BUFFER_SIZE, default=DEFAULT_BUFFER_SIZE - ): vol.All(vol.Coerce(int), vol.Range(min=1)), + vol.Optional(CONF_STATE_CHARACTERISTIC): cv.string, + vol.Optional(CONF_SAMPLES_MAX_BUFFER_SIZE): vol.Coerce(int), vol.Optional(CONF_MAX_AGE): cv.time_period, vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(int), vol.Optional( @@ -173,7 +222,8 @@ def valid_binary_characteristic_configuration(config: dict[str, Any]) -> dict[st ) PLATFORM_SCHEMA = vol.All( _PLATFORM_SCHEMA_BASE, - valid_binary_characteristic_configuration, + valid_state_characteristic_configuration, + valid_boundary_configuration, ) @@ -230,9 +280,6 @@ def __init__( split_entity_id(self._source_entity_id)[0] == BINARY_SENSOR_DOMAIN ) self._state_characteristic: str = state_characteristic - if self._state_characteristic == STAT_DEFAULT: - self._state_characteristic = STAT_COUNT if self.is_binary else STAT_MEAN - _LOGGER.warning(DEPRECATION_WARNING, self._state_characteristic, name) self._samples_max_buffer_size: int = samples_max_buffer_size self._samples_max_age: timedelta | None = samples_max_age self._precision: int = precision @@ -346,11 +393,12 @@ def _derive_unit_of_measurement(self, new_state: State) -> str | None: STAT_VALUE_MIN, ): unit = base_unit + elif self._state_characteristic in STATS_NOT_A_NUMBER: + unit = None elif self._state_characteristic in ( STAT_COUNT, - STAT_DATETIME_NEWEST, - STAT_DATETIME_OLDEST, - STAT_QUANTILES, + STAT_COUNT_BINARY_ON, + STAT_COUNT_BINARY_OFF, ): unit = None elif self._state_characteristic == STAT_VARIANCE: @@ -361,6 +409,13 @@ def _derive_unit_of_measurement(self, new_state: State) -> str | None: unit = base_unit + "/s" return unit + @property + def device_class(self) -> Literal[SensorDeviceClass.TIMESTAMP] | None: + """Return the class of this device.""" + if self._state_characteristic in STATS_DATETIME: + return SensorDeviceClass.TIMESTAMP + return None + @property def state_class(self) -> Literal[SensorStateClass.MEASUREMENT] | None: """Return the state class of this entity.""" @@ -582,6 +637,16 @@ def _stat_datetime_oldest(self) -> datetime | None: 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()) @@ -672,6 +737,18 @@ def _stat_binary_average_timeless(self) -> StateType: 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 05420a859c65f4..ec8f06920827b1 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -1,6 +1,9 @@ """The test for the statistics sensor platform.""" +from __future__ import annotations + from datetime import datetime, timedelta import statistics +from typing import Any, Sequence from unittest.mock import patch from homeassistant import config as hass_config @@ -542,7 +545,7 @@ async def test_state_characteristics(hass: HomeAssistant): def mock_now(): return mock_data["return_time"] - characteristics = ( + characteristics: Sequence[dict[str, Any]] = ( { "source_sensor_domain": "sensor", "name": "average_linear", @@ -615,16 +618,32 @@ def mock_now(): "source_sensor_domain": "sensor", "name": "datetime_newest", "value_0": STATE_UNKNOWN, - "value_1": start_datetime + timedelta(minutes=9), - "value_9": start_datetime + timedelta(minutes=9), + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "value_9": (start_datetime + timedelta(minutes=9)).isoformat(), "unit": None, }, { "source_sensor_domain": "sensor", "name": "datetime_oldest", "value_0": STATE_UNKNOWN, - "value_1": start_datetime + timedelta(minutes=9), - "value_9": start_datetime + timedelta(minutes=1), + "value_1": (start_datetime + timedelta(minutes=9)).isoformat(), + "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, }, { @@ -751,6 +770,38 @@ def mock_now(): "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", @@ -805,7 +856,11 @@ def mock_now(): state = hass.states.get( f"sensor.test_{characteristic['source_sensor_domain']}_{characteristic['name']}" ) - assert state is not None + assert state is not None, ( + f"no state object for characteristic " + f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' " + f"(buffer filled)" + ) assert state.state == str(characteristic["value_9"]), ( f"value mismatch for characteristic " f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' " @@ -826,7 +881,11 @@ def mock_now(): state = hass.states.get( f"sensor.test_{characteristic['source_sensor_domain']}_{characteristic['name']}" ) - assert state is not None + assert state is not None, ( + f"no state object for characteristic " + f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' " + f"(one stored value)" + ) assert state.state == str(characteristic["value_1"]), ( f"value mismatch for characteristic " f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' " @@ -844,7 +903,11 @@ def mock_now(): state = hass.states.get( f"sensor.test_{characteristic['source_sensor_domain']}_{characteristic['name']}" ) - assert state is not None + assert state is not None, ( + f"no state object for characteristic " + f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' " + f"(buffer empty)" + ) assert state.state == str(characteristic["value_0"]), ( f"value mismatch for characteristic " f"'{characteristic['source_sensor_domain']}/{characteristic['name']}' " @@ -987,7 +1050,7 @@ def mock_purge(self, *args): # The max_age timestamp should be 1 hour before what we have right # now in mock_data['return_time']. assert mock_data["return_time"] == datetime.strptime( - state.state, "%Y-%m-%d %H:%M:%S%z" + state.state, "%Y-%m-%dT%H:%M:%S%z" ) + timedelta(hours=1) From 0b7b4e758bb8e0a31613d00aee99537ffdeb1c85 Mon Sep 17 00:00:00 2001 From: Thomas Dietrich Date: Wed, 22 Dec 2021 21:17:11 +0100 Subject: [PATCH 2/4] Move additional characteristics into separate branch --- homeassistant/components/statistics/sensor.py | 42 +--------------- tests/components/statistics/test_sensor.py | 48 ------------------- 2 files changed, 1 insertion(+), 89 deletions(-) diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 6585cb368c5a3b..4bbf95b1c48783 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -62,12 +62,8 @@ 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" @@ -108,8 +104,6 @@ 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, @@ -128,26 +122,18 @@ 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, ) CONF_STATE_CHARACTERISTIC = "state_characteristic" @@ -395,11 +381,7 @@ def _derive_unit_of_measurement(self, new_state: State) -> str | None: unit = base_unit elif self._state_characteristic in STATS_NOT_A_NUMBER: unit = None - elif self._state_characteristic in ( - STAT_COUNT, - STAT_COUNT_BINARY_ON, - STAT_COUNT_BINARY_OFF, - ): + elif self._state_characteristic == STAT_COUNT: unit = None elif self._state_characteristic == STAT_VARIANCE: unit = base_unit + "²" @@ -637,16 +619,6 @@ def _stat_datetime_oldest(self) -> datetime | None: 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()) @@ -737,18 +709,6 @@ def _stat_binary_average_timeless(self) -> StateType: 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 ec8f06920827b1..27a5bc91bf80a5 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -630,22 +630,6 @@ def mock_now(): "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", @@ -770,38 +754,6 @@ def mock_now(): "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", From d1ca5928c82209057dfe7f5d62e16a91f44313a7 Mon Sep 17 00:00:00 2001 From: Thomas Dietrich Date: Wed, 22 Dec 2021 21:32:35 +0100 Subject: [PATCH 3/4] Move deprecation warning for sampling_size default to other branch --- homeassistant/components/statistics/sensor.py | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 4bbf95b1c48783..5ba1ced47045b7 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -85,14 +85,6 @@ "for further details: " "https://www.home-assistant.io/integrations/statistics/" ) -DEPRECATION_WARNING_SIZE = ( - "The configuration of either 'sampling_size' or 'max_age' will become " - "mandatory in a future release of the statistics integration. Please " - "add 'sampling_size: %s' to the configuration of sensor '%s' to keep " - "the current behavior. Read the documentation for further details: " - "https://www.home-assistant.io/integrations/statistics/" -) - STATS_NUMERIC_SUPPORT = ( STAT_AVERAGE_LINEAR, @@ -175,27 +167,15 @@ def valid_state_characteristic_configuration(config: dict[str, Any]) -> dict[str return config -def valid_boundary_configuration(config: dict[str, Any]) -> dict[str, Any]: - """Validate that either sampling_size or max_age are provided.""" - - if config.get(CONF_SAMPLES_MAX_BUFFER_SIZE) is None: - config[CONF_SAMPLES_MAX_BUFFER_SIZE] = DEFAULT_BUFFER_SIZE - if config.get(CONF_MAX_AGE) is None: - _LOGGER.warning( - DEPRECATION_WARNING_SIZE, - str(DEFAULT_BUFFER_SIZE), - config[CONF_NAME], - ) - return config - - _PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend( { vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_STATE_CHARACTERISTIC): cv.string, - vol.Optional(CONF_SAMPLES_MAX_BUFFER_SIZE): vol.Coerce(int), + vol.Optional( + CONF_SAMPLES_MAX_BUFFER_SIZE, default=DEFAULT_BUFFER_SIZE + ): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Optional(CONF_MAX_AGE): cv.time_period, vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(int), vol.Optional( @@ -209,7 +189,6 @@ def valid_boundary_configuration(config: dict[str, Any]) -> dict[str, Any]: PLATFORM_SCHEMA = vol.All( _PLATFORM_SCHEMA_BASE, valid_state_characteristic_configuration, - valid_boundary_configuration, ) From f88f22314563b0a718a0a59215af7eabc459918f Mon Sep 17 00:00:00 2001 From: Thomas Dietrich Date: Fri, 31 Dec 2021 00:24:58 +0100 Subject: [PATCH 4/4] Add supports list description --- homeassistant/components/statistics/sensor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 5ba1ced47045b7..fb5daa974752ff 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -51,10 +51,12 @@ _LOGGER = logging.getLogger(__name__) +# Stats for attributes only STAT_AGE_COVERAGE_RATIO = "age_coverage_ratio" STAT_BUFFER_USAGE_RATIO = "buffer_usage_ratio" STAT_SOURCE_VALUE_VALID = "source_value_valid" +# All sensor statistics STAT_AVERAGE_LINEAR = "average_linear" STAT_AVERAGE_STEP = "average_step" STAT_AVERAGE_TIMELESS = "average_timeless" @@ -86,6 +88,7 @@ "https://www.home-assistant.io/integrations/statistics/" ) +# Statistics supported by a sensor source (numeric) STATS_NUMERIC_SUPPORT = ( STAT_AVERAGE_LINEAR, STAT_AVERAGE_STEP, @@ -110,6 +113,7 @@ STAT_VARIANCE, ) +# Statistics supported by a binary_sensor source STATS_BINARY_SUPPORT = ( STAT_AVERAGE_STEP, STAT_AVERAGE_TIMELESS,