Finish migration of recorder to unit conversion (#78985)
This commit is contained in:
parent
3ad5d799c6
commit
c1bc26b413
1 changed files with 21 additions and 73 deletions
|
@ -2,7 +2,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Callable, Iterable, MutableMapping
|
from collections.abc import Iterable, MutableMapping
|
||||||
import datetime
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
@ -23,27 +23,7 @@ from homeassistant.components.recorder.models import (
|
||||||
StatisticMetaData,
|
StatisticMetaData,
|
||||||
StatisticResult,
|
StatisticResult,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT
|
||||||
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.core import HomeAssistant, State
|
from homeassistant.core import HomeAssistant, State
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity import entity_sources
|
from homeassistant.helpers.entity import entity_sources
|
||||||
|
@ -84,47 +64,6 @@ UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = {
|
||||||
SensorDeviceClass.GAS: VolumeConverter,
|
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
|
# Keep track of entities for which a warning about decreasing value has been logged
|
||||||
SEEN_DIP = "sensor_seen_total_increasing_dip"
|
SEEN_DIP = "sensor_seen_total_increasing_dip"
|
||||||
WARN_DIP = "sensor_warn_total_increasing_dip"
|
WARN_DIP = "sensor_warn_total_increasing_dip"
|
||||||
|
@ -212,9 +151,9 @@ def _normalize_states(
|
||||||
entity_id: str,
|
entity_id: str,
|
||||||
) -> tuple[str | None, str | None, list[tuple[float, State]]]:
|
) -> tuple[str | None, str | None, list[tuple[float, State]]]:
|
||||||
"""Normalize units."""
|
"""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
|
# We're not normalizing this device class, return the state as they are
|
||||||
fstates = []
|
fstates = []
|
||||||
for state in entity_history:
|
for state in entity_history:
|
||||||
|
@ -250,6 +189,7 @@ def _normalize_states(
|
||||||
state_unit = fstates[0][1].attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
state_unit = fstates[0][1].attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
return state_unit, state_unit, fstates
|
return state_unit, state_unit, fstates
|
||||||
|
|
||||||
|
converter = UNIT_CONVERTERS[device_class]
|
||||||
fstates = []
|
fstates = []
|
||||||
|
|
||||||
for state in entity_history:
|
for state in entity_history:
|
||||||
|
@ -259,7 +199,7 @@ def _normalize_states(
|
||||||
continue
|
continue
|
||||||
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
# Exclude unsupported units from statistics
|
# 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:
|
if WARN_UNSUPPORTED_UNIT not in hass.data:
|
||||||
hass.data[WARN_UNSUPPORTED_UNIT] = set()
|
hass.data[WARN_UNSUPPORTED_UNIT] = set()
|
||||||
if entity_id not in hass.data[WARN_UNSUPPORTED_UNIT]:
|
if entity_id not in hass.data[WARN_UNSUPPORTED_UNIT]:
|
||||||
|
@ -272,7 +212,14 @@ def _normalize_states(
|
||||||
)
|
)
|
||||||
continue
|
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
|
return UNIT_CONVERTERS[device_class].NORMALIZED_UNIT, state_unit, fstates
|
||||||
|
|
||||||
|
@ -655,7 +602,7 @@ def list_statistic_ids(
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if device_class not in UNIT_CONVERSIONS:
|
if device_class not in UNIT_CONVERTERS:
|
||||||
result[state.entity_id] = {
|
result[state.entity_id] = {
|
||||||
"has_mean": "mean" in provided_statistics,
|
"has_mean": "mean" in provided_statistics,
|
||||||
"has_sum": "sum" in provided_statistics,
|
"has_sum": "sum" in provided_statistics,
|
||||||
|
@ -667,10 +614,11 @@ def list_statistic_ids(
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if state_unit not in UNIT_CONVERSIONS[device_class]:
|
converter = UNIT_CONVERTERS[device_class]
|
||||||
|
if state_unit not in converter.VALID_UNITS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
statistics_unit = UNIT_CONVERTERS[device_class].NORMALIZED_UNIT
|
statistics_unit = converter.NORMALIZED_UNIT
|
||||||
result[state.entity_id] = {
|
result[state.entity_id] = {
|
||||||
"has_mean": "mean" in provided_statistics,
|
"has_mean": "mean" in provided_statistics,
|
||||||
"has_sum": "sum" in provided_statistics,
|
"has_sum": "sum" in provided_statistics,
|
||||||
|
@ -721,7 +669,7 @@ def validate_statistics(
|
||||||
)
|
)
|
||||||
|
|
||||||
metadata_unit = metadata[1]["unit_of_measurement"]
|
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:
|
if state_unit != metadata_unit:
|
||||||
# The unit has changed
|
# The unit has changed
|
||||||
validation_result[entity_id].append(
|
validation_result[entity_id].append(
|
||||||
|
@ -761,8 +709,8 @@ def validate_statistics(
|
||||||
|
|
||||||
if (
|
if (
|
||||||
state_class in STATE_CLASSES
|
state_class in STATE_CLASSES
|
||||||
and device_class in UNIT_CONVERSIONS
|
and device_class in UNIT_CONVERTERS
|
||||||
and state_unit not in UNIT_CONVERSIONS[device_class]
|
and state_unit not in UNIT_CONVERTERS[device_class].VALID_UNITS
|
||||||
):
|
):
|
||||||
# The unit in the state is not supported for this device class
|
# The unit in the state is not supported for this device class
|
||||||
validation_result[entity_id].append(
|
validation_result[entity_id].append(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue