Align temperature conversion with other converters (#79521)
* Align temperature conversion with other converters * Add comments and docstring * Align tests
This commit is contained in:
parent
47b40e1e61
commit
825f9502ad
4 changed files with 48 additions and 63 deletions
|
@ -819,7 +819,9 @@ def temperature_from_object(hass, temp_obj, interval=False):
|
||||||
# convert to Celsius if absolute temperature
|
# convert to Celsius if absolute temperature
|
||||||
temp -= 273.15
|
temp -= 273.15
|
||||||
|
|
||||||
return TemperatureConverter.convert(temp, from_unit, to_unit, interval=interval)
|
if interval:
|
||||||
|
return TemperatureConverter.convert_interval(temp, from_unit, to_unit)
|
||||||
|
return TemperatureConverter.convert(temp, from_unit, to_unit)
|
||||||
|
|
||||||
|
|
||||||
@HANDLERS.register(("Alexa.ThermostatController", "SetTargetTemperature"))
|
@HANDLERS.register(("Alexa.ThermostatController", "SetTargetTemperature"))
|
||||||
|
|
|
@ -43,6 +43,6 @@ def convert(
|
||||||
"unit_conversion.TemperatureConverter instead",
|
"unit_conversion.TemperatureConverter instead",
|
||||||
error_if_core=False,
|
error_if_core=False,
|
||||||
)
|
)
|
||||||
return TemperatureConverter.convert(
|
if interval:
|
||||||
temperature, from_unit, to_unit, interval=interval
|
return TemperatureConverter.convert_interval(temperature, from_unit, to_unit)
|
||||||
)
|
return TemperatureConverter.convert(temperature, from_unit, to_unit)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
"""Typing Helpers for Home Assistant."""
|
"""Typing Helpers for Home Assistant."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import abstractmethod
|
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ENERGY_KILO_WATT_HOUR,
|
ENERGY_KILO_WATT_HOUR,
|
||||||
ENERGY_MEGA_WATT_HOUR,
|
ENERGY_MEGA_WATT_HOUR,
|
||||||
|
@ -88,20 +86,6 @@ class BaseUnitConverter:
|
||||||
NORMALIZED_UNIT: str
|
NORMALIZED_UNIT: str
|
||||||
VALID_UNITS: set[str]
|
VALID_UNITS: set[str]
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def convert(cls, value: float, from_unit: str, to_unit: str) -> float:
|
|
||||||
"""Convert one unit of measurement to another."""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def get_unit_ratio(cls, from_unit: str, to_unit: str) -> float:
|
|
||||||
"""Get unit ratio between units of measurement."""
|
|
||||||
|
|
||||||
|
|
||||||
class BaseUnitConverterWithUnitConversion(BaseUnitConverter):
|
|
||||||
"""Define the format of a conversion utility."""
|
|
||||||
|
|
||||||
_UNIT_CONVERSION: dict[str, float]
|
_UNIT_CONVERSION: dict[str, float]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -133,7 +117,7 @@ class BaseUnitConverterWithUnitConversion(BaseUnitConverter):
|
||||||
return cls._UNIT_CONVERSION[from_unit] / cls._UNIT_CONVERSION[to_unit]
|
return cls._UNIT_CONVERSION[from_unit] / cls._UNIT_CONVERSION[to_unit]
|
||||||
|
|
||||||
|
|
||||||
class DistanceConverter(BaseUnitConverterWithUnitConversion):
|
class DistanceConverter(BaseUnitConverter):
|
||||||
"""Utility to convert distance values."""
|
"""Utility to convert distance values."""
|
||||||
|
|
||||||
UNIT_CLASS = "distance"
|
UNIT_CLASS = "distance"
|
||||||
|
@ -160,7 +144,7 @@ class DistanceConverter(BaseUnitConverterWithUnitConversion):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class EnergyConverter(BaseUnitConverterWithUnitConversion):
|
class EnergyConverter(BaseUnitConverter):
|
||||||
"""Utility to convert energy values."""
|
"""Utility to convert energy values."""
|
||||||
|
|
||||||
UNIT_CLASS = "energy"
|
UNIT_CLASS = "energy"
|
||||||
|
@ -177,7 +161,7 @@ class EnergyConverter(BaseUnitConverterWithUnitConversion):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MassConverter(BaseUnitConverterWithUnitConversion):
|
class MassConverter(BaseUnitConverter):
|
||||||
"""Utility to convert mass values."""
|
"""Utility to convert mass values."""
|
||||||
|
|
||||||
UNIT_CLASS = "mass"
|
UNIT_CLASS = "mass"
|
||||||
|
@ -200,7 +184,7 @@ class MassConverter(BaseUnitConverterWithUnitConversion):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerConverter(BaseUnitConverterWithUnitConversion):
|
class PowerConverter(BaseUnitConverter):
|
||||||
"""Utility to convert power values."""
|
"""Utility to convert power values."""
|
||||||
|
|
||||||
UNIT_CLASS = "power"
|
UNIT_CLASS = "power"
|
||||||
|
@ -215,7 +199,7 @@ class PowerConverter(BaseUnitConverterWithUnitConversion):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PressureConverter(BaseUnitConverterWithUnitConversion):
|
class PressureConverter(BaseUnitConverter):
|
||||||
"""Utility to convert pressure values."""
|
"""Utility to convert pressure values."""
|
||||||
|
|
||||||
UNIT_CLASS = "pressure"
|
UNIT_CLASS = "pressure"
|
||||||
|
@ -244,7 +228,7 @@ class PressureConverter(BaseUnitConverterWithUnitConversion):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SpeedConverter(BaseUnitConverterWithUnitConversion):
|
class SpeedConverter(BaseUnitConverter):
|
||||||
"""Utility to convert speed values."""
|
"""Utility to convert speed values."""
|
||||||
|
|
||||||
UNIT_CLASS = "speed"
|
UNIT_CLASS = "speed"
|
||||||
|
@ -281,47 +265,49 @@ class TemperatureConverter(BaseUnitConverter):
|
||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
TEMP_KELVIN,
|
TEMP_KELVIN,
|
||||||
}
|
}
|
||||||
_UNIT_RATIO = {
|
_UNIT_CONVERSION = {
|
||||||
TEMP_CELSIUS: 1.0,
|
TEMP_CELSIUS: 1.0,
|
||||||
TEMP_FAHRENHEIT: 1.8,
|
TEMP_FAHRENHEIT: 1.8,
|
||||||
TEMP_KELVIN: 1.0,
|
TEMP_KELVIN: 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert(
|
def convert(cls, value: float, from_unit: str, to_unit: str) -> float:
|
||||||
cls, value: float, from_unit: str, to_unit: str, *, interval: bool = False
|
"""Convert a temperature from one unit to another.
|
||||||
) -> float:
|
|
||||||
"""Convert a temperature from one unit to another."""
|
eg. 10°C will return 50°F
|
||||||
|
|
||||||
|
For converting an interval between two temperatures, please use
|
||||||
|
`convert_interval` instead.
|
||||||
|
"""
|
||||||
|
# We cannot use the implementation from BaseUnitConverter here because the temperature
|
||||||
|
# units do not use the same floor: 0°C, 0°F and 0K do not align
|
||||||
if from_unit == to_unit:
|
if from_unit == to_unit:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
if from_unit == TEMP_CELSIUS:
|
if from_unit == TEMP_CELSIUS:
|
||||||
if to_unit == TEMP_FAHRENHEIT:
|
if to_unit == TEMP_FAHRENHEIT:
|
||||||
return cls._celsius_to_fahrenheit(value, interval)
|
return cls._celsius_to_fahrenheit(value)
|
||||||
if to_unit == TEMP_KELVIN:
|
if to_unit == TEMP_KELVIN:
|
||||||
return cls._celsius_to_kelvin(value, interval)
|
return cls._celsius_to_kelvin(value)
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
|
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
|
||||||
)
|
)
|
||||||
|
|
||||||
if from_unit == TEMP_FAHRENHEIT:
|
if from_unit == TEMP_FAHRENHEIT:
|
||||||
if to_unit == TEMP_CELSIUS:
|
if to_unit == TEMP_CELSIUS:
|
||||||
return cls._fahrenheit_to_celsius(value, interval)
|
return cls._fahrenheit_to_celsius(value)
|
||||||
if to_unit == TEMP_KELVIN:
|
if to_unit == TEMP_KELVIN:
|
||||||
return cls._celsius_to_kelvin(
|
return cls._celsius_to_kelvin(cls._fahrenheit_to_celsius(value))
|
||||||
cls._fahrenheit_to_celsius(value, interval), interval
|
|
||||||
)
|
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
|
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
|
||||||
)
|
)
|
||||||
|
|
||||||
if from_unit == TEMP_KELVIN:
|
if from_unit == TEMP_KELVIN:
|
||||||
if to_unit == TEMP_CELSIUS:
|
if to_unit == TEMP_CELSIUS:
|
||||||
return cls._kelvin_to_celsius(value, interval)
|
return cls._kelvin_to_celsius(value)
|
||||||
if to_unit == TEMP_FAHRENHEIT:
|
if to_unit == TEMP_FAHRENHEIT:
|
||||||
return cls._celsius_to_fahrenheit(
|
return cls._celsius_to_fahrenheit(cls._kelvin_to_celsius(value))
|
||||||
cls._kelvin_to_celsius(value, interval), interval
|
|
||||||
)
|
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
|
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
|
||||||
)
|
)
|
||||||
|
@ -330,40 +316,40 @@ class TemperatureConverter(BaseUnitConverter):
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _fahrenheit_to_celsius(cls, fahrenheit: float, interval: bool = False) -> float:
|
def convert_interval(cls, interval: float, from_unit: str, to_unit: str) -> float:
|
||||||
|
"""Convert a temperature interval from one unit to another.
|
||||||
|
|
||||||
|
eg. a 10°C interval (10°C to 20°C) will return a 18°F (50°F to 68°F) interval
|
||||||
|
|
||||||
|
For converting a temperature value, please use `convert` as this method
|
||||||
|
skips floor adjustment.
|
||||||
|
"""
|
||||||
|
# We use BaseUnitConverter implementation here because we are only interested
|
||||||
|
# in the ratio between the units.
|
||||||
|
return super().convert(interval, from_unit, to_unit)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _fahrenheit_to_celsius(cls, fahrenheit: float) -> float:
|
||||||
"""Convert a temperature in Fahrenheit to Celsius."""
|
"""Convert a temperature in Fahrenheit to Celsius."""
|
||||||
if interval:
|
|
||||||
return fahrenheit / 1.8
|
|
||||||
return (fahrenheit - 32.0) / 1.8
|
return (fahrenheit - 32.0) / 1.8
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _kelvin_to_celsius(cls, kelvin: float, interval: bool = False) -> float:
|
def _kelvin_to_celsius(cls, kelvin: float) -> float:
|
||||||
"""Convert a temperature in Kelvin to Celsius."""
|
"""Convert a temperature in Kelvin to Celsius."""
|
||||||
if interval:
|
|
||||||
return kelvin
|
|
||||||
return kelvin - 273.15
|
return kelvin - 273.15
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _celsius_to_fahrenheit(cls, celsius: float, interval: bool = False) -> float:
|
def _celsius_to_fahrenheit(cls, celsius: float) -> float:
|
||||||
"""Convert a temperature in Celsius to Fahrenheit."""
|
"""Convert a temperature in Celsius to Fahrenheit."""
|
||||||
if interval:
|
|
||||||
return celsius * 1.8
|
|
||||||
return celsius * 1.8 + 32.0
|
return celsius * 1.8 + 32.0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _celsius_to_kelvin(cls, celsius: float, interval: bool = False) -> float:
|
def _celsius_to_kelvin(cls, celsius: float) -> float:
|
||||||
"""Convert a temperature in Celsius to Kelvin."""
|
"""Convert a temperature in Celsius to Kelvin."""
|
||||||
if interval:
|
|
||||||
return celsius
|
|
||||||
return celsius + 273.15
|
return celsius + 273.15
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_unit_ratio(cls, from_unit: str, to_unit: str) -> float:
|
|
||||||
"""Get unit ratio between units of measurement."""
|
|
||||||
return cls._UNIT_RATIO[from_unit] / cls._UNIT_RATIO[to_unit]
|
|
||||||
|
|
||||||
|
class VolumeConverter(BaseUnitConverter):
|
||||||
class VolumeConverter(BaseUnitConverterWithUnitConversion):
|
|
||||||
"""Utility to convert volume values."""
|
"""Utility to convert volume values."""
|
||||||
|
|
||||||
UNIT_CLASS = "volume"
|
UNIT_CLASS = "volume"
|
||||||
|
|
|
@ -452,10 +452,7 @@ def test_temperature_convert_with_interval(
|
||||||
value: float, from_unit: str, expected: float, to_unit: str
|
value: float, from_unit: str, expected: float, to_unit: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test conversion to other units."""
|
"""Test conversion to other units."""
|
||||||
assert (
|
assert TemperatureConverter.convert_interval(value, from_unit, to_unit) == expected
|
||||||
TemperatureConverter.convert(value, from_unit, to_unit, interval=True)
|
|
||||||
== expected
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue