Switch statistics config to require either/both 'max_age' and 'sampling_size' (#80999)

* Remove default characteristic

* Remove default sampling_size

* Fix typo

* Fix typo
This commit is contained in:
Thomas Dietrich 2022-11-17 10:31:06 +01:00 committed by GitHub
parent 1b80c66195
commit ad8b882cb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 79 deletions

View file

@ -34,11 +34,10 @@ from homeassistant.core import (
Event,
HomeAssistant,
State,
async_get_hass,
callback,
split_entity_id,
)
from homeassistant.helpers import config_validation as cv, issue_registry
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import (
async_track_point_in_utc_time,
@ -179,7 +178,7 @@ CONF_MAX_AGE = "max_age"
CONF_PRECISION = "precision"
CONF_PERCENTILE = "percentile"
DEFAULT_NAME = "Stats"
DEFAULT_NAME = "Statistical characteristic"
DEFAULT_PRECISION = 2
ICON = "mdi:calculator"
@ -187,24 +186,6 @@ ICON = "mdi:calculator"
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."""
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
issue_registry.async_create_issue(
hass=async_get_hass(),
domain=DOMAIN,
issue_id=f"{config[CONF_ENTITY_ID]}_default_characteristic",
breaks_in_ha_version="2022.12.0",
is_fixable=False,
severity=issue_registry.IssueSeverity.WARNING,
translation_key="deprecation_warning_characteristic",
translation_placeholders={
"entity": config[CONF_NAME],
"characteristic": config[CONF_STATE_CHARACTERISTIC],
},
learn_more_url="https://github.com/home-assistant/core/pull/60402",
)
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
@ -218,20 +199,14 @@ def valid_state_characteristic_configuration(config: dict[str, Any]) -> dict[str
def valid_boundary_configuration(config: dict[str, Any]) -> dict[str, Any]:
"""Validate that sampling_size, max_age, or both are provided."""
"""Validate that max_age, sampling_size, or both are provided."""
if config.get(CONF_SAMPLES_MAX_BUFFER_SIZE) is None:
config[CONF_SAMPLES_MAX_BUFFER_SIZE] = 20
issue_registry.async_create_issue(
hass=async_get_hass(),
domain=DOMAIN,
issue_id=f"{config[CONF_ENTITY_ID]}_invalid_boundary_config",
breaks_in_ha_version="2022.12.0",
is_fixable=False,
severity=issue_registry.IssueSeverity.WARNING,
translation_key="deprecation_warning_size",
translation_placeholders={"entity": config[CONF_NAME]},
learn_more_url="https://github.com/home-assistant/core/pull/69700",
if (
config.get(CONF_SAMPLES_MAX_BUFFER_SIZE) is None
and config.get(CONF_MAX_AGE) is None
):
raise vol.RequiredFieldInvalid(
"The sensor configuration must provide 'max_age' and/or 'sampling_size'"
)
return config
@ -241,8 +216,10 @@ _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.Required(CONF_STATE_CHARACTERISTIC): cv.string,
vol.Optional(CONF_SAMPLES_MAX_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(CONF_PERCENTILE, default=50): vol.All(
@ -274,7 +251,7 @@ async def async_setup_platform(
name=config[CONF_NAME],
unique_id=config.get(CONF_UNIQUE_ID),
state_characteristic=config[CONF_STATE_CHARACTERISTIC],
samples_max_buffer_size=config[CONF_SAMPLES_MAX_BUFFER_SIZE],
samples_max_buffer_size=config.get(CONF_SAMPLES_MAX_BUFFER_SIZE),
samples_max_age=config.get(CONF_MAX_AGE),
precision=config[CONF_PRECISION],
percentile=config[CONF_PERCENTILE],
@ -293,7 +270,7 @@ class StatisticsSensor(SensorEntity):
name: str,
unique_id: str | None,
state_characteristic: str,
samples_max_buffer_size: int,
samples_max_buffer_size: int | None,
samples_max_age: timedelta | None,
precision: int,
percentile: int,
@ -308,20 +285,17 @@ class StatisticsSensor(SensorEntity):
split_entity_id(self._source_entity_id)[0] == BINARY_SENSOR_DOMAIN
)
self._state_characteristic: str = state_characteristic
self._samples_max_buffer_size: int = samples_max_buffer_size
self._samples_max_buffer_size: int | None = samples_max_buffer_size
self._samples_max_age: timedelta | None = samples_max_age
self._precision: int = precision
self._percentile: int = percentile
self._value: StateType | datetime = None
self._unit_of_measurement: str | None = None
self._available: bool = False
self.states: deque[float | bool] = deque(maxlen=self._samples_max_buffer_size)
self.ages: deque[datetime] = deque(maxlen=self._samples_max_buffer_size)
self.attributes: dict[str, StateType] = {
STAT_AGE_COVERAGE_RATIO: None,
STAT_BUFFER_USAGE_RATIO: None,
STAT_SOURCE_VALUE_VALID: None,
}
self.attributes: dict[str, StateType] = {}
self._state_characteristic_fn: Callable[[], StateType | datetime]
if self.is_binary:
@ -496,11 +470,8 @@ class StatisticsSensor(SensorEntity):
self._update_value()
# If max_age is set, ensure to update again after the defined interval.
next_to_purge_timestamp = self._next_to_purge_timestamp()
if next_to_purge_timestamp:
_LOGGER.debug(
"%s: scheduling update at %s", self.entity_id, next_to_purge_timestamp
)
if timestamp := self._next_to_purge_timestamp():
_LOGGER.debug("%s: scheduling update at %s", self.entity_id, timestamp)
if self._update_listener:
self._update_listener()
self._update_listener = None
@ -513,7 +484,7 @@ class StatisticsSensor(SensorEntity):
self._update_listener = None
self._update_listener = async_track_point_in_utc_time(
self.hass, _scheduled_update, next_to_purge_timestamp
self.hass, _scheduled_update, timestamp
)
def _fetch_states_from_database(self) -> list[State]:
@ -563,18 +534,20 @@ class StatisticsSensor(SensorEntity):
def _update_attributes(self) -> None:
"""Calculate and update the various attributes."""
self.attributes[STAT_BUFFER_USAGE_RATIO] = round(
len(self.states) / self._samples_max_buffer_size, 2
)
if len(self.states) >= 1 and self._samples_max_age is not None:
self.attributes[STAT_AGE_COVERAGE_RATIO] = round(
(self.ages[-1] - self.ages[0]).total_seconds()
/ self._samples_max_age.total_seconds(),
2,
if self._samples_max_buffer_size is not None:
self.attributes[STAT_BUFFER_USAGE_RATIO] = round(
len(self.states) / self._samples_max_buffer_size, 2
)
else:
self.attributes[STAT_AGE_COVERAGE_RATIO] = None
if self._samples_max_age is not None:
if len(self.states) >= 1:
self.attributes[STAT_AGE_COVERAGE_RATIO] = round(
(self.ages[-1] - self.ages[0]).total_seconds()
/ self._samples_max_age.total_seconds(),
2,
)
else:
self.attributes[STAT_AGE_COVERAGE_RATIO] = None
def _update_value(self) -> None:
"""Front to call the right statistical characteristics functions.