def _async_validate_usage_stat( hass: HomeAssistant, metadata: dict[str, tuple[int, recorder.models.StatisticMetaData]], stat_id: str, allowed_device_classes: Sequence[str], allowed_units: Mapping[str, Sequence[str]], unit_error: str, result: list[ValidationIssue], ) -> None: """Validate a statistic.""" if stat_id not in metadata: result.append(ValidationIssue("statistics_not_defined", stat_id)) has_entity_source = valid_entity_id(stat_id) if not has_entity_source: return entity_id = stat_id if not recorder.is_entity_recorded(hass, entity_id): result.append(ValidationIssue( "recorder_untracked", entity_id, )) return if (state := hass.states.get(entity_id)) is None: result.append(ValidationIssue( "entity_not_defined", entity_id, )) return
def _async_validate_cost_entity(hass: HomeAssistant, entity_id: str, result: list[ValidationIssue]) -> None: """Validate that the cost entity is correct.""" if not recorder.is_entity_recorded(hass, entity_id): result.append(ValidationIssue( "recorder_untracked", entity_id, )) state = hass.states.get(entity_id) if state is None: result.append(ValidationIssue( "entity_not_defined", entity_id, )) return state_class = state.attributes.get("state_class") supported_state_classes = [ sensor.STATE_CLASS_MEASUREMENT, sensor.STATE_CLASS_TOTAL_INCREASING, ] if state_class not in supported_state_classes: result.append( ValidationIssue("entity_unexpected_state_class_total_increasing", entity_id, state_class))
def _async_validate_auto_generated_cost_entity( hass: HomeAssistant, entity_id: str, result: list[ValidationIssue]) -> None: """Validate that the auto generated cost entity is correct.""" if not recorder.is_entity_recorded(hass, entity_id): result.append(ValidationIssue( "recorder_untracked", entity_id, ))
def _async_validate_auto_generated_cost_entity( hass: HomeAssistant, energy_entity_id: str, result: list[ValidationIssue]) -> None: """Validate that the auto generated cost entity is correct.""" if energy_entity_id not in hass.data[DOMAIN]["cost_sensors"]: # The cost entity has not been setup return cost_entity_id = hass.data[DOMAIN]["cost_sensors"][energy_entity_id] if not recorder.is_entity_recorded(hass, cost_entity_id): result.append(ValidationIssue("recorder_untracked", cost_entity_id))
def _get_sensor_states(hass: HomeAssistant) -> list[State]: """Get the current state of all sensors for which to compile statistics.""" all_sensors = hass.states.all(DOMAIN) statistics_sensors = [] for state in all_sensors: if not is_entity_recorded(hass, state.entity_id): continue if (state.attributes.get(ATTR_STATE_CLASS)) not in STATE_CLASSES: continue statistics_sensors.append(state) return statistics_sensors
def _async_validate_cost_stat(hass: HomeAssistant, stat_id: str, result: list[ValidationIssue]) -> None: """Validate that the cost stat is correct.""" has_entity = valid_entity_id(stat_id) if not has_entity: return if not recorder.is_entity_recorded(hass, stat_id): result.append(ValidationIssue( "recorder_untracked", stat_id, ))
def _async_validate_cost_stat( hass: HomeAssistant, metadata: dict[str, tuple[int, recorder.models.StatisticMetaData]], stat_id: str, result: list[ValidationIssue], ) -> None: """Validate that the cost stat is correct.""" if stat_id not in metadata: result.append(ValidationIssue("statistics_not_defined", stat_id)) has_entity = valid_entity_id(stat_id) if not has_entity: return if not recorder.is_entity_recorded(hass, stat_id): result.append(ValidationIssue("recorder_untracked", stat_id)) if (state := hass.states.get(stat_id)) is None: result.append(ValidationIssue("entity_not_defined", stat_id)) return
def _async_validate_cost_stat(hass: HomeAssistant, stat_id: str, result: list[ValidationIssue]) -> None: """Validate that the cost stat is correct.""" has_entity = valid_entity_id(stat_id) if not has_entity: return if not recorder.is_entity_recorded(hass, stat_id): result.append(ValidationIssue( "recorder_untracked", stat_id, )) state = hass.states.get(stat_id) if state is None: result.append(ValidationIssue( "entity_not_defined", stat_id, )) return state_class = state.attributes.get("state_class") supported_state_classes = [ sensor.STATE_CLASS_MEASUREMENT, sensor.STATE_CLASS_TOTAL, sensor.STATE_CLASS_TOTAL_INCREASING, ] if state_class not in supported_state_classes: result.append( ValidationIssue("entity_unexpected_state_class", stat_id, state_class)) if (state_class == sensor.STATE_CLASS_MEASUREMENT and sensor.ATTR_LAST_RESET not in state.attributes): result.append( ValidationIssue("entity_state_class_measurement_no_last_reset", stat_id))
async def _async_validate_usage_stat( hass: HomeAssistant, stat_id: str, allowed_device_classes: Sequence[str], allowed_units: Mapping[str, Sequence[str]], unit_error: str, result: list[ValidationIssue], ) -> None: """Validate a statistic.""" metadata = await hass.async_add_executor_job( functools.partial( recorder.statistics.get_metadata, hass, statistic_ids=(stat_id, ), )) if stat_id not in metadata: result.append(ValidationIssue("statistics_not_defined", stat_id)) has_entity_source = valid_entity_id(stat_id) if not has_entity_source: return entity_id = stat_id if not recorder.is_entity_recorded(hass, entity_id): result.append(ValidationIssue( "recorder_untracked", entity_id, )) return if (state := hass.states.get(entity_id)) is None: result.append(ValidationIssue( "entity_not_defined", entity_id, )) return
async def _async_validate_cost_stat(hass: HomeAssistant, stat_id: str, result: list[ValidationIssue]) -> None: """Validate that the cost stat is correct.""" metadata = await hass.async_add_executor_job( functools.partial( recorder.statistics.get_metadata, hass, statistic_ids=(stat_id, ), )) if stat_id not in metadata: result.append(ValidationIssue("statistics_not_defined", stat_id)) has_entity = valid_entity_id(stat_id) if not has_entity: return if not recorder.is_entity_recorded(hass, stat_id): result.append(ValidationIssue("recorder_untracked", stat_id)) if (state := hass.states.get(stat_id)) is None: result.append(ValidationIssue("entity_not_defined", stat_id)) return
def validate_statistics( hass: HomeAssistant, ) -> dict[str, list[statistics.ValidationIssue]]: """Validate statistics.""" validation_result = defaultdict(list) sensor_states = hass.states.all(DOMAIN) metadatas = statistics.get_metadata(hass, statistic_source=RECORDER_DOMAIN) sensor_entity_ids = {i.entity_id for i in sensor_states} sensor_statistic_ids = set(metadatas) for state in sensor_states: entity_id = state.entity_id device_class = state.attributes.get(ATTR_DEVICE_CLASS) state_class = state.attributes.get(ATTR_STATE_CLASS) state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) if metadata := metadatas.get(entity_id): if not is_entity_recorded(hass, state.entity_id): # Sensor was previously recorded, but no longer is validation_result[entity_id].append( statistics.ValidationIssue( "entity_no_longer_recorded", {"statistic_id": entity_id}, )) if state_class not in STATE_CLASSES: # Sensor no longer has a valid state class validation_result[entity_id].append( statistics.ValidationIssue( "unsupported_state_class", { "statistic_id": entity_id, "state_class": state_class }, )) metadata_unit = metadata[1]["unit_of_measurement"] if device_class not in UNIT_CONVERSIONS: if state_unit != metadata_unit: # The unit has changed validation_result[entity_id].append( statistics.ValidationIssue( "units_changed", { "statistic_id": entity_id, "state_unit": state_unit, "metadata_unit": metadata_unit, }, )) elif metadata_unit != DEVICE_CLASS_UNITS[device_class]: # The unit in metadata is not supported for this device class validation_result[entity_id].append( statistics.ValidationIssue( "unsupported_unit_metadata", { "statistic_id": entity_id, "device_class": device_class, "metadata_unit": metadata_unit, "supported_unit": DEVICE_CLASS_UNITS[device_class], }, )) elif state_class in STATE_CLASSES: if not is_entity_recorded(hass, state.entity_id): # Sensor is not recorded validation_result[entity_id].append( statistics.ValidationIssue( "entity_not_recorded", {"statistic_id": entity_id}, ))
def _async_validate_usage_stat( hass: HomeAssistant, stat_value: str, allowed_device_classes: Sequence[str], allowed_units: Mapping[str, Sequence[str]], unit_error: str, result: list[ValidationIssue], ) -> None: """Validate a statistic.""" has_entity_source = valid_entity_id(stat_value) if not has_entity_source: return if not recorder.is_entity_recorded(hass, stat_value): result.append(ValidationIssue( "recorder_untracked", stat_value, )) return state = hass.states.get(stat_value) if state is None: result.append(ValidationIssue( "entity_not_defined", stat_value, )) return if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): result.append( ValidationIssue("entity_unavailable", stat_value, state.state)) return try: current_value: float | None = float(state.state) except ValueError: result.append( ValidationIssue("entity_state_non_numeric", stat_value, state.state)) return if current_value is not None and current_value < 0: result.append( ValidationIssue("entity_negative_state", stat_value, current_value)) device_class = state.attributes.get(ATTR_DEVICE_CLASS) if device_class not in allowed_device_classes: result.append( ValidationIssue( "entity_unexpected_device_class", stat_value, device_class, )) else: unit = state.attributes.get("unit_of_measurement") if device_class and unit not in allowed_units.get(device_class, []): result.append(ValidationIssue(unit_error, stat_value, unit)) state_class = state.attributes.get(sensor.ATTR_STATE_CLASS) allowed_state_classes = [ sensor.STATE_CLASS_MEASUREMENT, sensor.STATE_CLASS_TOTAL, sensor.STATE_CLASS_TOTAL_INCREASING, ] if state_class not in allowed_state_classes: result.append( ValidationIssue( "entity_unexpected_state_class", stat_value, state_class, )) if (state_class == sensor.STATE_CLASS_MEASUREMENT and sensor.ATTR_LAST_RESET not in state.attributes): result.append( ValidationIssue("entity_state_class_measurement_no_last_reset", stat_value))
def _async_validate_usage_stat( hass: HomeAssistant, stat_value: str, allowed_units: Sequence[str], unit_error: str, result: list[ValidationIssue], ) -> None: """Validate a statistic.""" has_entity_source = valid_entity_id(stat_value) if not has_entity_source: return if not recorder.is_entity_recorded(hass, stat_value): result.append(ValidationIssue( "recorder_untracked", stat_value, )) return state = hass.states.get(stat_value) if state is None: result.append(ValidationIssue( "entity_not_defined", stat_value, )) return if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): result.append( ValidationIssue("entity_unavailable", stat_value, state.state)) return try: current_value: float | None = float(state.state) except ValueError: result.append( ValidationIssue("entity_state_non_numeric", stat_value, state.state)) return if current_value is not None and current_value < 0: result.append( ValidationIssue("entity_negative_state", stat_value, current_value)) unit = state.attributes.get("unit_of_measurement") if unit not in allowed_units: result.append(ValidationIssue(unit_error, stat_value, unit)) state_class = state.attributes.get("state_class") supported_state_classes = [ sensor.STATE_CLASS_MEASUREMENT, sensor.STATE_CLASS_TOTAL_INCREASING, ] if state_class not in supported_state_classes: result.append( ValidationIssue( "entity_unexpected_state_class_total_increasing", stat_value, state_class, ))
def _async_validate_energy_stat( hass: HomeAssistant, stat_value: str, result: list[ValidationIssue] ) -> None: """Validate a statistic.""" has_entity_source = valid_entity_id(stat_value) if not has_entity_source: return if not recorder.is_entity_recorded(hass, stat_value): result.append( ValidationIssue( "recorder_untracked", stat_value, ) ) return state = hass.states.get(stat_value) if state is None: result.append( ValidationIssue( "entity_not_defined", stat_value, ) ) return if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): result.append(ValidationIssue("entity_unavailable", stat_value, state.state)) return try: current_value: float | None = float(state.state) except ValueError: result.append( ValidationIssue("entity_state_non_numeric", stat_value, state.state) ) return if current_value is not None and current_value < 0: result.append( ValidationIssue("entity_negative_state", stat_value, current_value) ) unit = state.attributes.get("unit_of_measurement") if unit not in (ENERGY_KILO_WATT_HOUR, ENERGY_WATT_HOUR): result.append( ValidationIssue("entity_unexpected_unit_energy", stat_value, unit) ) state_class = state.attributes.get("state_class") if state_class != sensor.STATE_CLASS_TOTAL_INCREASING: result.append( ValidationIssue( "entity_unexpected_state_class_total_increasing", stat_value, state_class, ) )