mirror of
https://github.com/home-assistant/core
synced 2024-09-06 10:29:55 +02:00
Add NORMALISED_UNIT to UnitConverter (#78920)
* Add NORMALISED_UNIT to UnitConverter * Adjust statistics * Rename
This commit is contained in:
parent
f5120872aa
commit
713fb874a8
@ -24,13 +24,6 @@ from sqlalchemy.sql.lambdas import StatementLambdaElement
|
|||||||
from sqlalchemy.sql.selectable import Subquery
|
from sqlalchemy.sql.selectable import Subquery
|
||||||
import voluptuous as vol
|
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.core import Event, HomeAssistant, callback, valid_entity_id
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry
|
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."""
|
"""Convert energy in kWh to to_unit."""
|
||||||
if value is None:
|
if value is None:
|
||||||
return 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:
|
def _convert_energy_to_kwh(from_unit: str, value: float) -> float:
|
||||||
"""Convert energy in from_unit to kWh."""
|
"""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:
|
def _convert_power_from_w(to_unit: str, value: float | None) -> float | None:
|
||||||
"""Convert power in W to to_unit."""
|
"""Convert power in W to to_unit."""
|
||||||
if value is None:
|
if value is None:
|
||||||
return 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:
|
def _convert_pressure_from_pa(to_unit: str, value: float | None) -> float | None:
|
||||||
"""Convert pressure in Pa to to_unit."""
|
"""Convert pressure in Pa to to_unit."""
|
||||||
if value is None:
|
if value is None:
|
||||||
return 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:
|
def _convert_temperature_from_c(to_unit: str, value: float | None) -> float | None:
|
||||||
"""Convert temperature in °C to to_unit."""
|
"""Convert temperature in °C to to_unit."""
|
||||||
if value is None:
|
if value is None:
|
||||||
return 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:
|
def _convert_volume_from_m3(to_unit: str, value: float | None) -> float | None:
|
||||||
"""Convert volume in m³ to to_unit."""
|
"""Convert volume in m³ to to_unit."""
|
||||||
if value is None:
|
if value is None:
|
||||||
return 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:
|
def _convert_volume_to_m3(from_unit: str, value: float) -> float:
|
||||||
"""Convert volume in from_unit to m³."""
|
"""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] = {
|
STATISTIC_UNIT_TO_UNIT_CLASS: dict[str | None, str] = {
|
||||||
ENERGY_KILO_WATT_HOUR: "energy",
|
energy_util.NORMALIZED_UNIT: "energy",
|
||||||
POWER_WATT: "power",
|
power_util.NORMALIZED_UNIT: "power",
|
||||||
PRESSURE_PA: "pressure",
|
pressure_util.NORMALIZED_UNIT: "pressure",
|
||||||
TEMP_CELSIUS: "temperature",
|
temperature_util.NORMALIZED_UNIT: "temperature",
|
||||||
VOLUME_CUBIC_METERS: "volume",
|
volume_util.NORMALIZED_UNIT: "volume",
|
||||||
}
|
}
|
||||||
|
|
||||||
STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, UnitConverter] = {
|
STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, UnitConverter] = {
|
||||||
ENERGY_KILO_WATT_HOUR: energy_util,
|
energy_util.NORMALIZED_UNIT: energy_util,
|
||||||
POWER_WATT: power_util,
|
power_util.NORMALIZED_UNIT: power_util,
|
||||||
PRESSURE_PA: pressure_util,
|
pressure_util.NORMALIZED_UNIT: pressure_util,
|
||||||
TEMP_CELSIUS: temperature_util,
|
temperature_util.NORMALIZED_UNIT: temperature_util,
|
||||||
VOLUME_CUBIC_METERS: volume_util,
|
volume_util.NORMALIZED_UNIT: volume_util,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Convert energy power, pressure, temperature and volume statistics from the
|
# 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[
|
STATISTIC_UNIT_TO_DISPLAY_UNIT_FUNCTIONS: dict[
|
||||||
str, Callable[[str, float | None], float | None]
|
str, Callable[[str, float | None], float | None]
|
||||||
] = {
|
] = {
|
||||||
ENERGY_KILO_WATT_HOUR: _convert_energy_from_kwh,
|
energy_util.NORMALIZED_UNIT: _convert_energy_from_kwh,
|
||||||
POWER_WATT: _convert_power_from_w,
|
power_util.NORMALIZED_UNIT: _convert_power_from_w,
|
||||||
PRESSURE_PA: _convert_pressure_from_pa,
|
pressure_util.NORMALIZED_UNIT: _convert_pressure_from_pa,
|
||||||
TEMP_CELSIUS: _convert_temperature_from_c,
|
temperature_util.NORMALIZED_UNIT: _convert_temperature_from_c,
|
||||||
VOLUME_CUBIC_METERS: _convert_volume_from_m3,
|
volume_util.NORMALIZED_UNIT: _convert_volume_from_m3,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Convert energy and volume statistics from the display unit configured by the user
|
# Convert energy and volume statistics from the display unit configured by the user
|
||||||
# to the normalized unit used for statistics.
|
# to the normalized unit used for statistics.
|
||||||
# This is used to support adjusting statistics in the display unit
|
# This is used to support adjusting statistics in the display unit
|
||||||
DISPLAY_UNIT_TO_STATISTIC_UNIT_FUNCTIONS: dict[str, Callable[[str, float], float]] = {
|
DISPLAY_UNIT_TO_STATISTIC_UNIT_FUNCTIONS: dict[str, Callable[[str, float], float]] = {
|
||||||
ENERGY_KILO_WATT_HOUR: _convert_energy_to_kwh,
|
energy_util.NORMALIZED_UNIT: _convert_energy_to_kwh,
|
||||||
VOLUME_CUBIC_METERS: _convert_volume_to_m3,
|
volume_util.NORMALIZED_UNIT: _convert_volume_to_m3,
|
||||||
}
|
}
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -47,6 +47,7 @@ from homeassistant.const import (
|
|||||||
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
|
||||||
|
from homeassistant.helpers.typing import UnitConverter
|
||||||
from homeassistant.util import (
|
from homeassistant.util import (
|
||||||
dt as dt_util,
|
dt as dt_util,
|
||||||
energy as energy_util,
|
energy as energy_util,
|
||||||
@ -75,13 +76,12 @@ DEFAULT_STATISTICS = {
|
|||||||
STATE_CLASS_TOTAL_INCREASING: {"sum"},
|
STATE_CLASS_TOTAL_INCREASING: {"sum"},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Normalized units which will be stored in the statistics table
|
UNIT_CONVERTERS: dict[str, UnitConverter] = {
|
||||||
DEVICE_CLASS_UNITS: dict[str, str] = {
|
SensorDeviceClass.ENERGY: energy_util,
|
||||||
SensorDeviceClass.ENERGY: ENERGY_KILO_WATT_HOUR,
|
SensorDeviceClass.POWER: power_util,
|
||||||
SensorDeviceClass.POWER: POWER_WATT,
|
SensorDeviceClass.PRESSURE: pressure_util,
|
||||||
SensorDeviceClass.PRESSURE: PRESSURE_PA,
|
SensorDeviceClass.TEMPERATURE: temperature_util,
|
||||||
SensorDeviceClass.TEMPERATURE: TEMP_CELSIUS,
|
SensorDeviceClass.GAS: volume_util,
|
||||||
SensorDeviceClass.GAS: VOLUME_CUBIC_METERS,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UNIT_CONVERSIONS: dict[str, dict[str, Callable]] = {
|
UNIT_CONVERSIONS: dict[str, dict[str, Callable]] = {
|
||||||
@ -272,7 +272,7 @@ def _normalize_states(
|
|||||||
|
|
||||||
fstates.append((UNIT_CONVERSIONS[device_class][state_unit](fstate), state))
|
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:
|
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 "
|
"compiled statistics (%s). Generation of long term statistics "
|
||||||
"will be suppressed unless the unit changes back to %s. "
|
"will be suppressed unless the unit changes back to %s. "
|
||||||
"Go to %s to fix this",
|
"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,
|
entity_id,
|
||||||
normalized_unit,
|
normalized_unit,
|
||||||
old_metadata[1]["unit_of_measurement"],
|
old_metadata[1]["unit_of_measurement"],
|
||||||
@ -668,7 +668,7 @@ def list_statistic_ids(
|
|||||||
if state_unit not in UNIT_CONVERSIONS[device_class]:
|
if state_unit not in UNIT_CONVERSIONS[device_class]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
statistics_unit = DEVICE_CLASS_UNITS[device_class]
|
statistics_unit = UNIT_CONVERTERS[device_class].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,
|
||||||
@ -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
|
# The unit in metadata is not supported for this device class
|
||||||
validation_result[entity_id].append(
|
validation_result[entity_id].append(
|
||||||
statistics.ValidationIssue(
|
statistics.ValidationIssue(
|
||||||
@ -741,7 +741,9 @@ def validate_statistics(
|
|||||||
"statistic_id": entity_id,
|
"statistic_id": entity_id,
|
||||||
"device_class": device_class,
|
"device_class": device_class,
|
||||||
"metadata_unit": metadata_unit,
|
"metadata_unit": metadata_unit,
|
||||||
"supported_unit": DEVICE_CLASS_UNITS[device_class],
|
"supported_unit": UNIT_CONVERTERS[
|
||||||
|
device_class
|
||||||
|
].NORMALIZED_UNIT,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -31,6 +31,7 @@ class UnitConverter(Protocol):
|
|||||||
"""Define the format of a conversion utility."""
|
"""Define the format of a conversion utility."""
|
||||||
|
|
||||||
VALID_UNITS: tuple[str, ...]
|
VALID_UNITS: tuple[str, ...]
|
||||||
|
NORMALIZED_UNIT: str
|
||||||
|
|
||||||
def convert(self, value: float, from_unit: str, to_unit: str) -> float:
|
def convert(self, value: float, from_unit: str, to_unit: str) -> float:
|
||||||
"""Convert one unit of measurement to another."""
|
"""Convert one unit of measurement to another."""
|
||||||
|
@ -22,6 +22,8 @@ UNIT_CONVERSION: dict[str, float] = {
|
|||||||
ENERGY_MEGA_WATT_HOUR: 1 / 1000,
|
ENERGY_MEGA_WATT_HOUR: 1 / 1000,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NORMALIZED_UNIT = ENERGY_KILO_WATT_HOUR
|
||||||
|
|
||||||
|
|
||||||
def convert(value: float, from_unit: str, to_unit: str) -> float:
|
def convert(value: float, from_unit: str, to_unit: str) -> float:
|
||||||
"""Convert one unit of measurement to another."""
|
"""Convert one unit of measurement to another."""
|
||||||
|
@ -19,6 +19,8 @@ UNIT_CONVERSION: dict[str, float] = {
|
|||||||
POWER_KILO_WATT: 1 / 1000,
|
POWER_KILO_WATT: 1 / 1000,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NORMALIZED_UNIT = POWER_WATT
|
||||||
|
|
||||||
|
|
||||||
def convert(value: float, from_unit: str, to_unit: str) -> float:
|
def convert(value: float, from_unit: str, to_unit: str) -> float:
|
||||||
"""Convert one unit of measurement to another."""
|
"""Convert one unit of measurement to another."""
|
||||||
|
@ -41,6 +41,8 @@ UNIT_CONVERSION: dict[str, float] = {
|
|||||||
PRESSURE_MMHG: 1 / 133.322,
|
PRESSURE_MMHG: 1 / 133.322,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NORMALIZED_UNIT = PRESSURE_PA
|
||||||
|
|
||||||
|
|
||||||
def convert(value: float, from_unit: str, to_unit: str) -> float:
|
def convert(value: float, from_unit: str, to_unit: str) -> float:
|
||||||
"""Convert one unit of measurement to another."""
|
"""Convert one unit of measurement to another."""
|
||||||
|
@ -13,6 +13,8 @@ VALID_UNITS: tuple[str, ...] = (
|
|||||||
TEMP_KELVIN,
|
TEMP_KELVIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
NORMALIZED_UNIT = TEMP_CELSIUS
|
||||||
|
|
||||||
|
|
||||||
def fahrenheit_to_celsius(fahrenheit: float, interval: bool = False) -> float:
|
def fahrenheit_to_celsius(fahrenheit: float, interval: bool = False) -> float:
|
||||||
"""Convert a temperature in Fahrenheit to Celsius."""
|
"""Convert a temperature in Fahrenheit to Celsius."""
|
||||||
|
@ -41,6 +41,8 @@ UNIT_CONVERSION: dict[str, float] = {
|
|||||||
VOLUME_CUBIC_FEET: 1 / CUBIC_FOOT_TO_CUBIC_METER,
|
VOLUME_CUBIC_FEET: 1 / CUBIC_FOOT_TO_CUBIC_METER,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NORMALIZED_UNIT = VOLUME_CUBIC_METERS
|
||||||
|
|
||||||
|
|
||||||
def liter_to_gallon(liter: float) -> float:
|
def liter_to_gallon(liter: float) -> float:
|
||||||
"""Convert a volume measurement in Liter to Gallon."""
|
"""Convert a volume measurement in Liter to Gallon."""
|
||||||
|
Loading…
Reference in New Issue
Block a user