Finish migration of recorder to unit conversion (#78985)

This commit is contained in:
epenet 2022-09-26 03:14:18 +02:00 committed by GitHub
parent 3ad5d799c6
commit c1bc26b413
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -2,7 +2,7 @@
from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable, Iterable, MutableMapping
from collections.abc import Iterable, MutableMapping
import datetime
import itertools
import logging
@ -23,27 +23,7 @@ from homeassistant.components.recorder.models import (
StatisticMetaData,
StatisticResult,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_UNIT_OF_MEASUREMENT,
ENERGY_KILO_WATT_HOUR,
ENERGY_MEGA_WATT_HOUR,
ENERGY_WATT_HOUR,
POWER_KILO_WATT,
POWER_WATT,
PRESSURE_BAR,
PRESSURE_HPA,
PRESSURE_INHG,
PRESSURE_KPA,
PRESSURE_MBAR,
PRESSURE_PA,
PRESSURE_PSI,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
TEMP_KELVIN,
VOLUME_CUBIC_FEET,
VOLUME_CUBIC_METERS,
)
from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import entity_sources
@ -84,47 +64,6 @@ UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = {
SensorDeviceClass.GAS: VolumeConverter,
}
UNIT_CONVERSIONS: dict[str, dict[str, Callable]] = {
# Convert energy to kWh
SensorDeviceClass.ENERGY: {
ENERGY_KILO_WATT_HOUR: lambda x: x
/ EnergyConverter.UNIT_CONVERSION[ENERGY_KILO_WATT_HOUR],
ENERGY_MEGA_WATT_HOUR: lambda x: x
/ EnergyConverter.UNIT_CONVERSION[ENERGY_MEGA_WATT_HOUR],
ENERGY_WATT_HOUR: lambda x: x
/ EnergyConverter.UNIT_CONVERSION[ENERGY_WATT_HOUR],
},
# Convert power to W
SensorDeviceClass.POWER: {
POWER_WATT: lambda x: x / PowerConverter.UNIT_CONVERSION[POWER_WATT],
POWER_KILO_WATT: lambda x: x / PowerConverter.UNIT_CONVERSION[POWER_KILO_WATT],
},
# Convert pressure to Pa
# Note: PressureConverter.convert is bypassed to avoid redundant error checking
SensorDeviceClass.PRESSURE: {
PRESSURE_BAR: lambda x: x / PressureConverter.UNIT_CONVERSION[PRESSURE_BAR],
PRESSURE_HPA: lambda x: x / PressureConverter.UNIT_CONVERSION[PRESSURE_HPA],
PRESSURE_INHG: lambda x: x / PressureConverter.UNIT_CONVERSION[PRESSURE_INHG],
PRESSURE_KPA: lambda x: x / PressureConverter.UNIT_CONVERSION[PRESSURE_KPA],
PRESSURE_MBAR: lambda x: x / PressureConverter.UNIT_CONVERSION[PRESSURE_MBAR],
PRESSURE_PA: lambda x: x / PressureConverter.UNIT_CONVERSION[PRESSURE_PA],
PRESSURE_PSI: lambda x: x / PressureConverter.UNIT_CONVERSION[PRESSURE_PSI],
},
# Convert temperature to °C
# Note: TemperatureConverter.convert is bypassed to avoid redundant error checking
SensorDeviceClass.TEMPERATURE: {
TEMP_CELSIUS: lambda x: x,
TEMP_FAHRENHEIT: TemperatureConverter.fahrenheit_to_celsius,
TEMP_KELVIN: TemperatureConverter.kelvin_to_celsius,
},
# Convert volume to cubic meter
SensorDeviceClass.GAS: {
VOLUME_CUBIC_METERS: lambda x: x,
VOLUME_CUBIC_FEET: lambda x: x
/ VolumeConverter.UNIT_CONVERSION[VOLUME_CUBIC_FEET],
},
}
# Keep track of entities for which a warning about decreasing value has been logged
SEEN_DIP = "sensor_seen_total_increasing_dip"
WARN_DIP = "sensor_warn_total_increasing_dip"
@ -212,9 +151,9 @@ def _normalize_states(
entity_id: str,
) -> tuple[str | None, str | None, list[tuple[float, State]]]:
"""Normalize units."""
state_unit = None
state_unit: str | None = None
if device_class not in UNIT_CONVERSIONS:
if device_class not in UNIT_CONVERTERS:
# We're not normalizing this device class, return the state as they are
fstates = []
for state in entity_history:
@ -250,6 +189,7 @@ def _normalize_states(
state_unit = fstates[0][1].attributes.get(ATTR_UNIT_OF_MEASUREMENT)
return state_unit, state_unit, fstates
converter = UNIT_CONVERTERS[device_class]
fstates = []
for state in entity_history:
@ -259,7 +199,7 @@ def _normalize_states(
continue
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
# Exclude unsupported units from statistics
if state_unit not in UNIT_CONVERSIONS[device_class]:
if state_unit not in converter.VALID_UNITS:
if WARN_UNSUPPORTED_UNIT not in hass.data:
hass.data[WARN_UNSUPPORTED_UNIT] = set()
if entity_id not in hass.data[WARN_UNSUPPORTED_UNIT]:
@ -272,7 +212,14 @@ def _normalize_states(
)
continue
fstates.append((UNIT_CONVERSIONS[device_class][state_unit](fstate), state))
fstates.append(
(
converter.convert(
fstate, from_unit=state_unit, to_unit=converter.NORMALIZED_UNIT
),
state,
)
)
return UNIT_CONVERTERS[device_class].NORMALIZED_UNIT, state_unit, fstates
@ -655,7 +602,7 @@ def list_statistic_ids(
):
continue
if device_class not in UNIT_CONVERSIONS:
if device_class not in UNIT_CONVERTERS:
result[state.entity_id] = {
"has_mean": "mean" in provided_statistics,
"has_sum": "sum" in provided_statistics,
@ -667,10 +614,11 @@ def list_statistic_ids(
}
continue
if state_unit not in UNIT_CONVERSIONS[device_class]:
converter = UNIT_CONVERTERS[device_class]
if state_unit not in converter.VALID_UNITS:
continue
statistics_unit = UNIT_CONVERTERS[device_class].NORMALIZED_UNIT
statistics_unit = converter.NORMALIZED_UNIT
result[state.entity_id] = {
"has_mean": "mean" in provided_statistics,
"has_sum": "sum" in provided_statistics,
@ -721,7 +669,7 @@ def validate_statistics(
)
metadata_unit = metadata[1]["unit_of_measurement"]
if device_class not in UNIT_CONVERSIONS:
if device_class not in UNIT_CONVERTERS:
if state_unit != metadata_unit:
# The unit has changed
validation_result[entity_id].append(
@ -761,8 +709,8 @@ def validate_statistics(
if (
state_class in STATE_CLASSES
and device_class in UNIT_CONVERSIONS
and state_unit not in UNIT_CONVERSIONS[device_class]
and device_class in UNIT_CONVERTERS
and state_unit not in UNIT_CONVERTERS[device_class].VALID_UNITS
):
# The unit in the state is not supported for this device class
validation_result[entity_id].append(