diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 523833dfd74..b7362d1a381 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -32,7 +32,6 @@ from homeassistant.helpers.storage import STORAGE_DIR from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.util import ( dt as dt_util, - pressure as pressure_util, temperature as temperature_util, volume as volume_util, ) @@ -155,7 +154,7 @@ 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_util.NORMALIZED_UNIT, to_unit) + return PressureConverter.convert(value, PressureConverter.NORMALIZED_UNIT, to_unit) def _convert_temperature_from_c(to_unit: str, value: float | None) -> float | None: @@ -178,9 +177,9 @@ def _convert_volume_to_m3(from_unit: str, value: float) -> float: STATISTIC_UNIT_TO_UNIT_CLASS: dict[str | None, str] = { - EnergyConverter.NORMALIZED_UNIT: "energy", - PowerConverter.NORMALIZED_UNIT: "power", - pressure_util.NORMALIZED_UNIT: "pressure", + EnergyConverter.NORMALIZED_UNIT: EnergyConverter.UNIT_CLASS, + PowerConverter.NORMALIZED_UNIT: PowerConverter.UNIT_CLASS, + PressureConverter.NORMALIZED_UNIT: PressureConverter.UNIT_CLASS, temperature_util.NORMALIZED_UNIT: "temperature", volume_util.NORMALIZED_UNIT: "volume", } @@ -188,7 +187,7 @@ STATISTIC_UNIT_TO_UNIT_CLASS: dict[str | None, str] = { STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = { EnergyConverter.NORMALIZED_UNIT: EnergyConverter, PowerConverter.NORMALIZED_UNIT: PowerConverter, - pressure_util.NORMALIZED_UNIT: PressureConverter, + PressureConverter.NORMALIZED_UNIT: PressureConverter, temperature_util.NORMALIZED_UNIT: TemperatureConverter, volume_util.NORMALIZED_UNIT: VolumeConverter, } @@ -200,7 +199,7 @@ STATISTIC_UNIT_TO_DISPLAY_UNIT_FUNCTIONS: dict[ ] = { EnergyConverter.NORMALIZED_UNIT: _convert_energy_from_kwh, PowerConverter.NORMALIZED_UNIT: _convert_power_from_w, - pressure_util.NORMALIZED_UNIT: _convert_pressure_from_pa, + PressureConverter.NORMALIZED_UNIT: _convert_pressure_from_pa, temperature_util.NORMALIZED_UNIT: _convert_temperature_from_c, volume_util.NORMALIZED_UNIT: _convert_volume_from_m3, } diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index 42273e11670..0c1ede5eb0a 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -19,12 +19,12 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback, valid_entity_id from homeassistant.helpers import config_validation as cv from homeassistant.helpers.json import JSON_DUMP -from homeassistant.util import ( - dt as dt_util, - pressure as pressure_util, - temperature as temperature_util, +from homeassistant.util import dt as dt_util, temperature as temperature_util +from homeassistant.util.unit_conversion import ( + EnergyConverter, + PowerConverter, + PressureConverter, ) -from homeassistant.util.unit_conversion import EnergyConverter, PowerConverter from .const import MAX_QUEUE_BACKLOG from .statistics import ( @@ -122,7 +122,7 @@ async def ws_handle_get_statistics_during_period( { vol.Optional("energy"): vol.In(EnergyConverter.VALID_UNITS), vol.Optional("power"): vol.In(PowerConverter.VALID_UNITS), - vol.Optional("pressure"): vol.In(pressure_util.VALID_UNITS), + vol.Optional("pressure"): vol.In(PressureConverter.VALID_UNITS), vol.Optional("temperature"): vol.In(temperature_util.VALID_UNITS), vol.Optional("volume"): vol.Any(VOLUME_CUBIC_FEET, VOLUME_CUBIC_METERS), } diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index e15bd851952..ab19f053a22 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -57,7 +57,7 @@ from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity from homeassistant.helpers.typing import ConfigType, StateType -from homeassistant.util import dt as dt_util, pressure as pressure_util +from homeassistant.util import dt as dt_util from homeassistant.util.unit_conversion import ( BaseUnitConverter, PressureConverter, @@ -214,7 +214,7 @@ UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = { } UNIT_RATIOS: dict[str, dict[str, float]] = { - SensorDeviceClass.PRESSURE: pressure_util.UNIT_CONVERSION, + SensorDeviceClass.PRESSURE: PressureConverter.UNIT_CONVERSION, SensorDeviceClass.TEMPERATURE: { TEMP_CELSIUS: 1.0, TEMP_FAHRENHEIT: 1.8, diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index 062ee0103d7..a66ef54304a 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -49,7 +49,6 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import entity_sources from homeassistant.util import ( dt as dt_util, - pressure as pressure_util, temperature as temperature_util, volume as volume_util, ) @@ -105,15 +104,15 @@ UNIT_CONVERSIONS: dict[str, dict[str, Callable]] = { POWER_KILO_WATT: lambda x: x / PowerConverter.UNIT_CONVERSION[POWER_KILO_WATT], }, # Convert pressure to Pa - # Note: pressure_util.convert is bypassed to avoid redundant error checking + # Note: PressureConverter.convert is bypassed to avoid redundant error checking SensorDeviceClass.PRESSURE: { - PRESSURE_BAR: lambda x: x / pressure_util.UNIT_CONVERSION[PRESSURE_BAR], - PRESSURE_HPA: lambda x: x / pressure_util.UNIT_CONVERSION[PRESSURE_HPA], - PRESSURE_INHG: lambda x: x / pressure_util.UNIT_CONVERSION[PRESSURE_INHG], - PRESSURE_KPA: lambda x: x / pressure_util.UNIT_CONVERSION[PRESSURE_KPA], - PRESSURE_MBAR: lambda x: x / pressure_util.UNIT_CONVERSION[PRESSURE_MBAR], - PRESSURE_PA: lambda x: x / pressure_util.UNIT_CONVERSION[PRESSURE_PA], - PRESSURE_PSI: lambda x: x / pressure_util.UNIT_CONVERSION[PRESSURE_PSI], + 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: temperature_util.convert is bypassed to avoid redundant error checking diff --git a/homeassistant/util/pressure.py b/homeassistant/util/pressure.py index b17899eee61..adcadf6dfdb 100644 --- a/homeassistant/util/pressure.py +++ b/homeassistant/util/pressure.py @@ -1,9 +1,7 @@ """Pressure util functions.""" from __future__ import annotations -from numbers import Number - -from homeassistant.const import ( +from homeassistant.const import ( # pylint: disable=unused-import # noqa: F401 PRESSURE, PRESSURE_BAR, PRESSURE_CBAR, @@ -17,45 +15,13 @@ from homeassistant.const import ( UNIT_NOT_RECOGNIZED_TEMPLATE, ) -VALID_UNITS: tuple[str, ...] = ( - PRESSURE_PA, - PRESSURE_HPA, - PRESSURE_KPA, - PRESSURE_BAR, - PRESSURE_CBAR, - PRESSURE_MBAR, - PRESSURE_INHG, - PRESSURE_PSI, - PRESSURE_MMHG, -) +from .unit_conversion import PressureConverter -UNIT_CONVERSION: dict[str, float] = { - PRESSURE_PA: 1, - PRESSURE_HPA: 1 / 100, - PRESSURE_KPA: 1 / 1000, - PRESSURE_BAR: 1 / 100000, - PRESSURE_CBAR: 1 / 1000, - PRESSURE_MBAR: 1 / 100, - PRESSURE_INHG: 1 / 3386.389, - PRESSURE_PSI: 1 / 6894.757, - PRESSURE_MMHG: 1 / 133.322, -} - -NORMALIZED_UNIT = PRESSURE_PA +UNIT_CONVERSION: dict[str, float] = PressureConverter.UNIT_CONVERSION +VALID_UNITS: tuple[str, ...] = PressureConverter.VALID_UNITS def convert(value: float, from_unit: str, to_unit: str) -> float: """Convert one unit of measurement to another.""" - if from_unit not in VALID_UNITS: - raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(from_unit, PRESSURE)) - if to_unit not in VALID_UNITS: - raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, PRESSURE)) - - if not isinstance(value, Number): - raise TypeError(f"{value} is not of numeric type") - - if from_unit == to_unit: - return value - - pascals = value / UNIT_CONVERSION[from_unit] - return pascals * UNIT_CONVERSION[to_unit] + # Need to add warning when core migration finished + return PressureConverter.convert(value, from_unit, to_unit) diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index b01d039056e..3d82a76e2d1 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -10,17 +10,21 @@ from homeassistant.const import ( ENERGY_WATT_HOUR, POWER_KILO_WATT, POWER_WATT, + PRESSURE_BAR, + PRESSURE_CBAR, + PRESSURE_HPA, + PRESSURE_INHG, + PRESSURE_KPA, + PRESSURE_MBAR, + PRESSURE_MMHG, PRESSURE_PA, + PRESSURE_PSI, TEMP_CELSIUS, UNIT_NOT_RECOGNIZED_TEMPLATE, VOLUME_CUBIC_METERS, ) -from . import ( - pressure as pressure_util, - temperature as temperature_util, - volume as volume_util, -) +from . import temperature as temperature_util, volume as volume_util class BaseUnitConverter: @@ -34,7 +38,7 @@ class BaseUnitConverter: class BaseUnitConverterWithUnitConversion(BaseUnitConverter): """Define the format of a conversion utility.""" - DEVICE_CLASS: str + UNIT_CLASS: str UNIT_CONVERSION: dict[str, float] @classmethod @@ -42,11 +46,11 @@ class BaseUnitConverterWithUnitConversion(BaseUnitConverter): """Convert one unit of measurement to another.""" if from_unit not in cls.VALID_UNITS: raise ValueError( - UNIT_NOT_RECOGNIZED_TEMPLATE.format(from_unit, cls.DEVICE_CLASS) + UNIT_NOT_RECOGNIZED_TEMPLATE.format(from_unit, cls.UNIT_CLASS) ) if to_unit not in cls.VALID_UNITS: raise ValueError( - UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.DEVICE_CLASS) + UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS) ) if not isinstance(value, Number): @@ -62,7 +66,7 @@ class BaseUnitConverterWithUnitConversion(BaseUnitConverter): class EnergyConverter(BaseUnitConverterWithUnitConversion): """Utility to convert energy values.""" - DEVICE_CLASS = "energy" + UNIT_CLASS = "energy" NORMALIZED_UNIT = ENERGY_KILO_WATT_HOUR UNIT_CONVERSION: dict[str, float] = { ENERGY_WATT_HOUR: 1 * 1000, @@ -79,7 +83,7 @@ class EnergyConverter(BaseUnitConverterWithUnitConversion): class PowerConverter(BaseUnitConverterWithUnitConversion): """Utility to convert power values.""" - DEVICE_CLASS = "power" + UNIT_CLASS = "power" NORMALIZED_UNIT = POWER_WATT UNIT_CONVERSION: dict[str, float] = { POWER_WATT: 1, @@ -91,12 +95,33 @@ class PowerConverter(BaseUnitConverterWithUnitConversion): ) -class PressureConverter(BaseUnitConverter): +class PressureConverter(BaseUnitConverterWithUnitConversion): """Utility to convert pressure values.""" + UNIT_CLASS = "pressure" NORMALIZED_UNIT = PRESSURE_PA - VALID_UNITS = pressure_util.VALID_UNITS - convert = pressure_util.convert + UNIT_CONVERSION: dict[str, float] = { + PRESSURE_PA: 1, + PRESSURE_HPA: 1 / 100, + PRESSURE_KPA: 1 / 1000, + PRESSURE_BAR: 1 / 100000, + PRESSURE_CBAR: 1 / 1000, + PRESSURE_MBAR: 1 / 100, + PRESSURE_INHG: 1 / 3386.389, + PRESSURE_PSI: 1 / 6894.757, + PRESSURE_MMHG: 1 / 133.322, + } + VALID_UNITS: tuple[str, ...] = ( + PRESSURE_PA, + PRESSURE_HPA, + PRESSURE_KPA, + PRESSURE_BAR, + PRESSURE_CBAR, + PRESSURE_MBAR, + PRESSURE_INHG, + PRESSURE_PSI, + PRESSURE_MMHG, + ) class TemperatureConverter(BaseUnitConverter): diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index 477152f8729..f3dfd033be7 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -7,11 +7,20 @@ from homeassistant.const import ( ENERGY_WATT_HOUR, POWER_KILO_WATT, POWER_WATT, + PRESSURE_CBAR, + PRESSURE_HPA, + PRESSURE_INHG, + PRESSURE_KPA, + PRESSURE_MBAR, + PRESSURE_MMHG, + PRESSURE_PA, + PRESSURE_PSI, ) from homeassistant.util.unit_conversion import ( BaseUnitConverter, EnergyConverter, PowerConverter, + PressureConverter, ) INVALID_SYMBOL = "bob" @@ -25,6 +34,14 @@ INVALID_SYMBOL = "bob" (EnergyConverter, ENERGY_MEGA_WATT_HOUR), (PowerConverter, POWER_WATT), (PowerConverter, POWER_KILO_WATT), + (PressureConverter, PRESSURE_PA), + (PressureConverter, PRESSURE_HPA), + (PressureConverter, PRESSURE_MBAR), + (PressureConverter, PRESSURE_INHG), + (PressureConverter, PRESSURE_KPA), + (PressureConverter, PRESSURE_CBAR), + (PressureConverter, PRESSURE_MMHG), + (PressureConverter, PRESSURE_PSI), ], ) def test_convert_same_unit(converter: type[BaseUnitConverter], valid_unit: str) -> None: @@ -37,6 +54,7 @@ def test_convert_same_unit(converter: type[BaseUnitConverter], valid_unit: str) [ (EnergyConverter, ENERGY_KILO_WATT_HOUR), (PowerConverter, POWER_WATT), + (PressureConverter, PRESSURE_PA), ], ) def test_convert_invalid_unit( @@ -47,7 +65,7 @@ def test_convert_invalid_unit( converter.convert(5, INVALID_SYMBOL, valid_unit) with pytest.raises(ValueError): - EnergyConverter.convert(5, valid_unit, INVALID_SYMBOL) + converter.convert(5, valid_unit, INVALID_SYMBOL) @pytest.mark.parametrize( @@ -55,6 +73,7 @@ def test_convert_invalid_unit( [ (EnergyConverter, ENERGY_WATT_HOUR, ENERGY_KILO_WATT_HOUR), (PowerConverter, POWER_WATT, POWER_KILO_WATT), + (PressureConverter, PRESSURE_HPA, PRESSURE_INHG), ], ) def test_convert_nonnumeric_value( @@ -76,6 +95,188 @@ def test_convert_nonnumeric_value( (EnergyConverter, 10, ENERGY_MEGA_WATT_HOUR, 10000, ENERGY_KILO_WATT_HOUR), (PowerConverter, 10, POWER_KILO_WATT, 10000, POWER_WATT), (PowerConverter, 10, POWER_WATT, 0.01, POWER_KILO_WATT), + ( + PressureConverter, + 1000, + PRESSURE_HPA, + pytest.approx(14.5037743897), + PRESSURE_PSI, + ), + ( + PressureConverter, + 1000, + PRESSURE_HPA, + pytest.approx(29.5299801647), + PRESSURE_INHG, + ), + ( + PressureConverter, + 1000, + PRESSURE_HPA, + pytest.approx(100000), + PRESSURE_PA, + ), + ( + PressureConverter, + 1000, + PRESSURE_HPA, + pytest.approx(100), + PRESSURE_KPA, + ), + ( + PressureConverter, + 1000, + PRESSURE_HPA, + pytest.approx(1000), + PRESSURE_MBAR, + ), + ( + PressureConverter, + 1000, + PRESSURE_HPA, + pytest.approx(100), + PRESSURE_CBAR, + ), + ( + PressureConverter, + 100, + PRESSURE_KPA, + pytest.approx(14.5037743897), + PRESSURE_PSI, + ), + ( + PressureConverter, + 100, + PRESSURE_KPA, + pytest.approx(29.5299801647), + PRESSURE_INHG, + ), + ( + PressureConverter, + 100, + PRESSURE_KPA, + pytest.approx(100000), + PRESSURE_PA, + ), + ( + PressureConverter, + 100, + PRESSURE_KPA, + pytest.approx(1000), + PRESSURE_HPA, + ), + ( + PressureConverter, + 100, + PRESSURE_KPA, + pytest.approx(1000), + PRESSURE_MBAR, + ), + ( + PressureConverter, + 100, + PRESSURE_KPA, + pytest.approx(100), + PRESSURE_CBAR, + ), + ( + PressureConverter, + 30, + PRESSURE_INHG, + pytest.approx(14.7346266155), + PRESSURE_PSI, + ), + ( + PressureConverter, + 30, + PRESSURE_INHG, + pytest.approx(101.59167), + PRESSURE_KPA, + ), + ( + PressureConverter, + 30, + PRESSURE_INHG, + pytest.approx(1015.9167), + PRESSURE_HPA, + ), + ( + PressureConverter, + 30, + PRESSURE_INHG, + pytest.approx(101591.67), + PRESSURE_PA, + ), + ( + PressureConverter, + 30, + PRESSURE_INHG, + pytest.approx(1015.9167), + PRESSURE_MBAR, + ), + ( + PressureConverter, + 30, + PRESSURE_INHG, + pytest.approx(101.59167), + PRESSURE_CBAR, + ), + ( + PressureConverter, + 30, + PRESSURE_INHG, + pytest.approx(762.002), + PRESSURE_MMHG, + ), + ( + PressureConverter, + 30, + PRESSURE_MMHG, + pytest.approx(0.580102), + PRESSURE_PSI, + ), + ( + PressureConverter, + 30, + PRESSURE_MMHG, + pytest.approx(3.99966), + PRESSURE_KPA, + ), + ( + PressureConverter, + 30, + PRESSURE_MMHG, + pytest.approx(39.9966), + PRESSURE_HPA, + ), + ( + PressureConverter, + 30, + PRESSURE_MMHG, + pytest.approx(3999.66), + PRESSURE_PA, + ), + ( + PressureConverter, + 30, + PRESSURE_MMHG, + pytest.approx(39.9966), + PRESSURE_MBAR, + ), + ( + PressureConverter, + 30, + PRESSURE_MMHG, + pytest.approx(3.99966), + PRESSURE_CBAR, + ), + ( + PressureConverter, + 30, + PRESSURE_MMHG, + pytest.approx(1.181099), + PRESSURE_INHG, + ), ], ) def test_convert(