From 713fb874a8a5b946d7ad02eb1dc805708b8c65eb Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 22 Sep 2022 08:50:08 +0200 Subject: [PATCH] Add NORMALISED_UNIT to UnitConverter (#78920) * Add NORMALISED_UNIT to UnitConverter * Adjust statistics * Rename --- .../components/recorder/statistics.py | 55 ++++++++----------- homeassistant/components/sensor/recorder.py | 26 +++++---- homeassistant/helpers/typing.py | 1 + homeassistant/util/energy.py | 2 + homeassistant/util/power.py | 2 + homeassistant/util/pressure.py | 2 + homeassistant/util/temperature.py | 2 + homeassistant/util/volume.py | 2 + 8 files changed, 49 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 15309d5ab466..d1d9dd2a658c 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -24,13 +24,6 @@ from sqlalchemy.sql.lambdas import StatementLambdaElement from sqlalchemy.sql.selectable import Subquery import voluptuous as vol -from homeassistant.const import ( - ENERGY_KILO_WATT_HOUR, - POWER_WATT, - PRESSURE_PA, - TEMP_CELSIUS, - VOLUME_CUBIC_METERS, -) from homeassistant.core import Event, HomeAssistant, callback, valid_entity_id from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry @@ -137,61 +130,61 @@ def _convert_energy_from_kwh(to_unit: str, value: float | None) -> float | None: """Convert energy in kWh to to_unit.""" if value is None: return None - return energy_util.convert(value, ENERGY_KILO_WATT_HOUR, to_unit) + return energy_util.convert(value, energy_util.NORMALIZED_UNIT, to_unit) def _convert_energy_to_kwh(from_unit: str, value: float) -> float: """Convert energy in from_unit to kWh.""" - return energy_util.convert(value, from_unit, ENERGY_KILO_WATT_HOUR) + return energy_util.convert(value, from_unit, energy_util.NORMALIZED_UNIT) def _convert_power_from_w(to_unit: str, value: float | None) -> float | None: """Convert power in W to to_unit.""" if value is None: return None - return power_util.convert(value, POWER_WATT, to_unit) + return power_util.convert(value, power_util.NORMALIZED_UNIT, to_unit) def _convert_pressure_from_pa(to_unit: str, value: float | None) -> float | None: """Convert pressure in Pa to to_unit.""" if value is None: return None - return pressure_util.convert(value, PRESSURE_PA, to_unit) + return pressure_util.convert(value, pressure_util.NORMALIZED_UNIT, to_unit) def _convert_temperature_from_c(to_unit: str, value: float | None) -> float | None: """Convert temperature in °C to to_unit.""" if value is None: return None - return temperature_util.convert(value, TEMP_CELSIUS, to_unit) + return temperature_util.convert(value, temperature_util.NORMALIZED_UNIT, to_unit) def _convert_volume_from_m3(to_unit: str, value: float | None) -> float | None: """Convert volume in m³ to to_unit.""" if value is None: return None - return volume_util.convert(value, VOLUME_CUBIC_METERS, to_unit) + return volume_util.convert(value, volume_util.NORMALIZED_UNIT, to_unit) def _convert_volume_to_m3(from_unit: str, value: float) -> float: """Convert volume in from_unit to m³.""" - return volume_util.convert(value, from_unit, VOLUME_CUBIC_METERS) + return volume_util.convert(value, from_unit, volume_util.NORMALIZED_UNIT) STATISTIC_UNIT_TO_UNIT_CLASS: dict[str | None, str] = { - ENERGY_KILO_WATT_HOUR: "energy", - POWER_WATT: "power", - PRESSURE_PA: "pressure", - TEMP_CELSIUS: "temperature", - VOLUME_CUBIC_METERS: "volume", + energy_util.NORMALIZED_UNIT: "energy", + power_util.NORMALIZED_UNIT: "power", + pressure_util.NORMALIZED_UNIT: "pressure", + temperature_util.NORMALIZED_UNIT: "temperature", + volume_util.NORMALIZED_UNIT: "volume", } STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, UnitConverter] = { - ENERGY_KILO_WATT_HOUR: energy_util, - POWER_WATT: power_util, - PRESSURE_PA: pressure_util, - TEMP_CELSIUS: temperature_util, - VOLUME_CUBIC_METERS: volume_util, + energy_util.NORMALIZED_UNIT: energy_util, + power_util.NORMALIZED_UNIT: power_util, + pressure_util.NORMALIZED_UNIT: pressure_util, + temperature_util.NORMALIZED_UNIT: temperature_util, + volume_util.NORMALIZED_UNIT: volume_util, } # Convert energy power, pressure, temperature and volume statistics from the @@ -199,19 +192,19 @@ STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, UnitConverter] = { STATISTIC_UNIT_TO_DISPLAY_UNIT_FUNCTIONS: dict[ str, Callable[[str, float | None], float | None] ] = { - ENERGY_KILO_WATT_HOUR: _convert_energy_from_kwh, - POWER_WATT: _convert_power_from_w, - PRESSURE_PA: _convert_pressure_from_pa, - TEMP_CELSIUS: _convert_temperature_from_c, - VOLUME_CUBIC_METERS: _convert_volume_from_m3, + energy_util.NORMALIZED_UNIT: _convert_energy_from_kwh, + power_util.NORMALIZED_UNIT: _convert_power_from_w, + pressure_util.NORMALIZED_UNIT: _convert_pressure_from_pa, + temperature_util.NORMALIZED_UNIT: _convert_temperature_from_c, + volume_util.NORMALIZED_UNIT: _convert_volume_from_m3, } # Convert energy and volume statistics from the display unit configured by the user # to the normalized unit used for statistics. # This is used to support adjusting statistics in the display unit DISPLAY_UNIT_TO_STATISTIC_UNIT_FUNCTIONS: dict[str, Callable[[str, float], float]] = { - ENERGY_KILO_WATT_HOUR: _convert_energy_to_kwh, - VOLUME_CUBIC_METERS: _convert_volume_to_m3, + energy_util.NORMALIZED_UNIT: _convert_energy_to_kwh, + volume_util.NORMALIZED_UNIT: _convert_volume_to_m3, } _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index 40577e6962f7..9bd068aa4691 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -47,6 +47,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, State from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import entity_sources +from homeassistant.helpers.typing import UnitConverter from homeassistant.util import ( dt as dt_util, energy as energy_util, @@ -75,13 +76,12 @@ DEFAULT_STATISTICS = { STATE_CLASS_TOTAL_INCREASING: {"sum"}, } -# Normalized units which will be stored in the statistics table -DEVICE_CLASS_UNITS: dict[str, str] = { - SensorDeviceClass.ENERGY: ENERGY_KILO_WATT_HOUR, - SensorDeviceClass.POWER: POWER_WATT, - SensorDeviceClass.PRESSURE: PRESSURE_PA, - SensorDeviceClass.TEMPERATURE: TEMP_CELSIUS, - SensorDeviceClass.GAS: VOLUME_CUBIC_METERS, +UNIT_CONVERTERS: dict[str, UnitConverter] = { + SensorDeviceClass.ENERGY: energy_util, + SensorDeviceClass.POWER: power_util, + SensorDeviceClass.PRESSURE: pressure_util, + SensorDeviceClass.TEMPERATURE: temperature_util, + SensorDeviceClass.GAS: volume_util, } UNIT_CONVERSIONS: dict[str, dict[str, Callable]] = { @@ -272,7 +272,7 @@ def _normalize_states( fstates.append((UNIT_CONVERSIONS[device_class][state_unit](fstate), state)) - return DEVICE_CLASS_UNITS[device_class], state_unit, fstates + return UNIT_CONVERTERS[device_class].NORMALIZED_UNIT, state_unit, fstates def _suggest_report_issue(hass: HomeAssistant, entity_id: str) -> str: @@ -503,7 +503,7 @@ def _compile_statistics( # noqa: C901 "compiled statistics (%s). Generation of long term statistics " "will be suppressed unless the unit changes back to %s. " "Go to %s to fix this", - "normalized " if device_class in DEVICE_CLASS_UNITS else "", + "normalized " if device_class in UNIT_CONVERTERS else "", entity_id, normalized_unit, old_metadata[1]["unit_of_measurement"], @@ -668,7 +668,7 @@ def list_statistic_ids( if state_unit not in UNIT_CONVERSIONS[device_class]: continue - statistics_unit = DEVICE_CLASS_UNITS[device_class] + statistics_unit = UNIT_CONVERTERS[device_class].NORMALIZED_UNIT result[state.entity_id] = { "has_mean": "mean" in provided_statistics, "has_sum": "sum" in provided_statistics, @@ -732,7 +732,7 @@ def validate_statistics( }, ) ) - elif metadata_unit != DEVICE_CLASS_UNITS[device_class]: + elif metadata_unit != UNIT_CONVERTERS[device_class].NORMALIZED_UNIT: # The unit in metadata is not supported for this device class validation_result[entity_id].append( statistics.ValidationIssue( @@ -741,7 +741,9 @@ def validate_statistics( "statistic_id": entity_id, "device_class": device_class, "metadata_unit": metadata_unit, - "supported_unit": DEVICE_CLASS_UNITS[device_class], + "supported_unit": UNIT_CONVERTERS[ + device_class + ].NORMALIZED_UNIT, }, ) ) diff --git a/homeassistant/helpers/typing.py b/homeassistant/helpers/typing.py index c679de288b14..a6b4862f03b5 100644 --- a/homeassistant/helpers/typing.py +++ b/homeassistant/helpers/typing.py @@ -31,6 +31,7 @@ class UnitConverter(Protocol): """Define the format of a conversion utility.""" VALID_UNITS: tuple[str, ...] + NORMALIZED_UNIT: str def convert(self, value: float, from_unit: str, to_unit: str) -> float: """Convert one unit of measurement to another.""" diff --git a/homeassistant/util/energy.py b/homeassistant/util/energy.py index 00695704751b..551b34be3979 100644 --- a/homeassistant/util/energy.py +++ b/homeassistant/util/energy.py @@ -22,6 +22,8 @@ UNIT_CONVERSION: dict[str, float] = { ENERGY_MEGA_WATT_HOUR: 1 / 1000, } +NORMALIZED_UNIT = ENERGY_KILO_WATT_HOUR + def convert(value: float, from_unit: str, to_unit: str) -> float: """Convert one unit of measurement to another.""" diff --git a/homeassistant/util/power.py b/homeassistant/util/power.py index ae4f4c249b62..bafa56b38c2d 100644 --- a/homeassistant/util/power.py +++ b/homeassistant/util/power.py @@ -19,6 +19,8 @@ UNIT_CONVERSION: dict[str, float] = { POWER_KILO_WATT: 1 / 1000, } +NORMALIZED_UNIT = POWER_WATT + def convert(value: float, from_unit: str, to_unit: str) -> float: """Convert one unit of measurement to another.""" diff --git a/homeassistant/util/pressure.py b/homeassistant/util/pressure.py index a07f9d777c36..b17899eee610 100644 --- a/homeassistant/util/pressure.py +++ b/homeassistant/util/pressure.py @@ -41,6 +41,8 @@ UNIT_CONVERSION: dict[str, float] = { PRESSURE_MMHG: 1 / 133.322, } +NORMALIZED_UNIT = PRESSURE_PA + def convert(value: float, from_unit: str, to_unit: str) -> float: """Convert one unit of measurement to another.""" diff --git a/homeassistant/util/temperature.py b/homeassistant/util/temperature.py index 06febd600e7b..c89ce90ecf98 100644 --- a/homeassistant/util/temperature.py +++ b/homeassistant/util/temperature.py @@ -13,6 +13,8 @@ VALID_UNITS: tuple[str, ...] = ( TEMP_KELVIN, ) +NORMALIZED_UNIT = TEMP_CELSIUS + def fahrenheit_to_celsius(fahrenheit: float, interval: bool = False) -> float: """Convert a temperature in Fahrenheit to Celsius.""" diff --git a/homeassistant/util/volume.py b/homeassistant/util/volume.py index 498368e7e2b6..5cc6dbc58ddf 100644 --- a/homeassistant/util/volume.py +++ b/homeassistant/util/volume.py @@ -41,6 +41,8 @@ UNIT_CONVERSION: dict[str, float] = { VOLUME_CUBIC_FEET: 1 / CUBIC_FOOT_TO_CUBIC_METER, } +NORMALIZED_UNIT = VOLUME_CUBIC_METERS + def liter_to_gallon(liter: float) -> float: """Convert a volume measurement in Liter to Gallon."""