2022-09-22 12:15:22 +00:00
|
|
|
"""Typing Helpers for Home Assistant."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-09-22 16:31:50 +00:00
|
|
|
from abc import abstractmethod
|
2022-09-22 13:39:49 +00:00
|
|
|
from numbers import Number
|
|
|
|
|
|
|
|
from homeassistant.const import (
|
|
|
|
ENERGY_KILO_WATT_HOUR,
|
|
|
|
ENERGY_MEGA_WATT_HOUR,
|
|
|
|
ENERGY_WATT_HOUR,
|
2022-09-23 12:23:59 +00:00
|
|
|
LENGTH_CENTIMETERS,
|
|
|
|
LENGTH_FEET,
|
|
|
|
LENGTH_INCHES,
|
|
|
|
LENGTH_KILOMETERS,
|
|
|
|
LENGTH_METERS,
|
|
|
|
LENGTH_MILES,
|
|
|
|
LENGTH_MILLIMETERS,
|
|
|
|
LENGTH_YARD,
|
2022-09-22 13:39:49 +00:00
|
|
|
POWER_KILO_WATT,
|
|
|
|
POWER_WATT,
|
2022-09-22 14:44:09 +00:00
|
|
|
PRESSURE_BAR,
|
|
|
|
PRESSURE_CBAR,
|
|
|
|
PRESSURE_HPA,
|
|
|
|
PRESSURE_INHG,
|
|
|
|
PRESSURE_KPA,
|
|
|
|
PRESSURE_MBAR,
|
|
|
|
PRESSURE_MMHG,
|
2022-09-22 13:39:49 +00:00
|
|
|
PRESSURE_PA,
|
2022-09-22 14:44:09 +00:00
|
|
|
PRESSURE_PSI,
|
2022-09-23 12:23:59 +00:00
|
|
|
SPEED_FEET_PER_SECOND,
|
|
|
|
SPEED_INCHES_PER_DAY,
|
|
|
|
SPEED_INCHES_PER_HOUR,
|
|
|
|
SPEED_KILOMETERS_PER_HOUR,
|
|
|
|
SPEED_KNOTS,
|
|
|
|
SPEED_METERS_PER_SECOND,
|
|
|
|
SPEED_MILES_PER_HOUR,
|
|
|
|
SPEED_MILLIMETERS_PER_DAY,
|
2022-09-22 13:39:49 +00:00
|
|
|
TEMP_CELSIUS,
|
2022-09-22 16:31:50 +00:00
|
|
|
TEMP_FAHRENHEIT,
|
|
|
|
TEMP_KELVIN,
|
2022-09-22 13:39:49 +00:00
|
|
|
UNIT_NOT_RECOGNIZED_TEMPLATE,
|
2022-09-22 15:49:45 +00:00
|
|
|
VOLUME_CUBIC_FEET,
|
2022-09-22 13:39:49 +00:00
|
|
|
VOLUME_CUBIC_METERS,
|
2022-09-22 15:49:45 +00:00
|
|
|
VOLUME_FLUID_OUNCE,
|
|
|
|
VOLUME_GALLONS,
|
|
|
|
VOLUME_LITERS,
|
|
|
|
VOLUME_MILLILITERS,
|
2022-09-22 13:39:49 +00:00
|
|
|
)
|
2022-09-22 12:15:22 +00:00
|
|
|
|
2022-09-23 12:23:59 +00:00
|
|
|
# Distance conversion constants
|
|
|
|
_MM_TO_M = 0.001 # 1 mm = 0.001 m
|
|
|
|
_CM_TO_M = 0.01 # 1 cm = 0.01 m
|
|
|
|
_KM_TO_M = 1000 # 1 km = 1000 m
|
|
|
|
|
|
|
|
_IN_TO_M = 0.0254 # 1 inch = 0.0254 m
|
|
|
|
_FOOT_TO_M = _IN_TO_M * 12 # 12 inches = 1 foot (0.3048 m)
|
|
|
|
_YARD_TO_M = _FOOT_TO_M * 3 # 3 feet = 1 yard (0.9144 m)
|
|
|
|
_MILE_TO_M = _YARD_TO_M * 1760 # 1760 yard = 1 mile (1609.344 m)
|
|
|
|
|
|
|
|
_NAUTICAL_MILE_TO_M = 1852 # 1 nautical mile = 1852 m
|
|
|
|
|
|
|
|
# Duration conversion constants
|
|
|
|
_HRS_TO_SECS = 60 * 60 # 1 hr = 3600 seconds
|
|
|
|
_DAYS_TO_SECS = 24 * _HRS_TO_SECS # 1 day = 24 hours = 86400 seconds
|
2022-09-22 15:49:45 +00:00
|
|
|
|
|
|
|
# Volume conversion constants
|
|
|
|
_L_TO_CUBIC_METER = 0.001 # 1 L = 0.001 m³
|
|
|
|
_ML_TO_CUBIC_METER = 0.001 * _L_TO_CUBIC_METER # 1 mL = 0.001 L
|
2022-09-23 12:23:59 +00:00
|
|
|
_GALLON_TO_CUBIC_METER = 231 * pow(_IN_TO_M, 3) # US gallon is 231 cubic inches
|
2022-09-22 15:49:45 +00:00
|
|
|
_FLUID_OUNCE_TO_CUBIC_METER = _GALLON_TO_CUBIC_METER / 128 # 128 fl. oz. in a US gallon
|
2022-09-23 12:23:59 +00:00
|
|
|
_CUBIC_FOOT_TO_CUBIC_METER = pow(_FOOT_TO_M, 3)
|
2022-09-22 12:15:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
class BaseUnitConverter:
|
|
|
|
"""Define the format of a conversion utility."""
|
|
|
|
|
2022-09-22 16:31:50 +00:00
|
|
|
UNIT_CLASS: str
|
2022-09-22 12:15:22 +00:00
|
|
|
NORMALIZED_UNIT: str
|
|
|
|
VALID_UNITS: tuple[str, ...]
|
2022-09-22 13:39:49 +00:00
|
|
|
|
|
|
|
@classmethod
|
2022-09-22 16:31:50 +00:00
|
|
|
def _check_arguments(cls, value: float, from_unit: str, to_unit: str) -> None:
|
|
|
|
"""Check that arguments are all valid."""
|
2022-09-22 13:39:49 +00:00
|
|
|
if from_unit not in cls.VALID_UNITS:
|
|
|
|
raise ValueError(
|
2022-09-22 14:44:09 +00:00
|
|
|
UNIT_NOT_RECOGNIZED_TEMPLATE.format(from_unit, cls.UNIT_CLASS)
|
2022-09-22 13:39:49 +00:00
|
|
|
)
|
|
|
|
if to_unit not in cls.VALID_UNITS:
|
|
|
|
raise ValueError(
|
2022-09-22 14:44:09 +00:00
|
|
|
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
|
2022-09-22 13:39:49 +00:00
|
|
|
)
|
2022-09-22 12:15:22 +00:00
|
|
|
|
2022-09-22 13:39:49 +00:00
|
|
|
if not isinstance(value, Number):
|
|
|
|
raise TypeError(f"{value} is not of numeric type")
|
2022-09-22 12:15:22 +00:00
|
|
|
|
2022-09-22 16:31:50 +00:00
|
|
|
@classmethod
|
|
|
|
@abstractmethod
|
|
|
|
def convert(cls, value: float, from_unit: str, to_unit: str) -> float:
|
|
|
|
"""Convert one unit of measurement to another."""
|
|
|
|
|
|
|
|
|
|
|
|
class BaseUnitConverterWithUnitConversion(BaseUnitConverter):
|
|
|
|
"""Define the format of a conversion utility."""
|
|
|
|
|
|
|
|
UNIT_CONVERSION: dict[str, float]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def convert(cls, value: float, from_unit: str, to_unit: str) -> float:
|
|
|
|
"""Convert one unit of measurement to another."""
|
|
|
|
cls._check_arguments(value, from_unit, to_unit)
|
|
|
|
|
2022-09-22 13:39:49 +00:00
|
|
|
if from_unit == to_unit:
|
|
|
|
return value
|
|
|
|
|
|
|
|
new_value = value / cls.UNIT_CONVERSION[from_unit]
|
|
|
|
return new_value * cls.UNIT_CONVERSION[to_unit]
|
|
|
|
|
|
|
|
|
2022-09-23 12:23:59 +00:00
|
|
|
class DistanceConverter(BaseUnitConverterWithUnitConversion):
|
|
|
|
"""Utility to convert distance values."""
|
|
|
|
|
|
|
|
UNIT_CLASS = "distance"
|
|
|
|
NORMALIZED_UNIT = LENGTH_METERS
|
|
|
|
UNIT_CONVERSION: dict[str, float] = {
|
|
|
|
LENGTH_METERS: 1,
|
|
|
|
LENGTH_MILLIMETERS: 1 / _MM_TO_M,
|
|
|
|
LENGTH_CENTIMETERS: 1 / _CM_TO_M,
|
|
|
|
LENGTH_KILOMETERS: 1 / _KM_TO_M,
|
|
|
|
LENGTH_INCHES: 1 / _IN_TO_M,
|
|
|
|
LENGTH_FEET: 1 / _FOOT_TO_M,
|
|
|
|
LENGTH_YARD: 1 / _YARD_TO_M,
|
|
|
|
LENGTH_MILES: 1 / _MILE_TO_M,
|
|
|
|
}
|
|
|
|
VALID_UNITS: tuple[str, ...] = (
|
|
|
|
LENGTH_KILOMETERS,
|
|
|
|
LENGTH_MILES,
|
|
|
|
LENGTH_FEET,
|
|
|
|
LENGTH_METERS,
|
|
|
|
LENGTH_CENTIMETERS,
|
|
|
|
LENGTH_MILLIMETERS,
|
|
|
|
LENGTH_INCHES,
|
|
|
|
LENGTH_YARD,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-09-22 13:39:49 +00:00
|
|
|
class EnergyConverter(BaseUnitConverterWithUnitConversion):
|
|
|
|
"""Utility to convert energy values."""
|
2022-09-22 12:15:22 +00:00
|
|
|
|
2022-09-22 14:44:09 +00:00
|
|
|
UNIT_CLASS = "energy"
|
2022-09-22 13:39:49 +00:00
|
|
|
NORMALIZED_UNIT = ENERGY_KILO_WATT_HOUR
|
|
|
|
UNIT_CONVERSION: dict[str, float] = {
|
|
|
|
ENERGY_WATT_HOUR: 1 * 1000,
|
|
|
|
ENERGY_KILO_WATT_HOUR: 1,
|
|
|
|
ENERGY_MEGA_WATT_HOUR: 1 / 1000,
|
|
|
|
}
|
|
|
|
VALID_UNITS: tuple[str, ...] = (
|
|
|
|
ENERGY_WATT_HOUR,
|
|
|
|
ENERGY_KILO_WATT_HOUR,
|
|
|
|
ENERGY_MEGA_WATT_HOUR,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class PowerConverter(BaseUnitConverterWithUnitConversion):
|
2022-09-22 12:15:22 +00:00
|
|
|
"""Utility to convert power values."""
|
|
|
|
|
2022-09-22 14:44:09 +00:00
|
|
|
UNIT_CLASS = "power"
|
2022-09-22 13:39:49 +00:00
|
|
|
NORMALIZED_UNIT = POWER_WATT
|
|
|
|
UNIT_CONVERSION: dict[str, float] = {
|
|
|
|
POWER_WATT: 1,
|
|
|
|
POWER_KILO_WATT: 1 / 1000,
|
|
|
|
}
|
|
|
|
VALID_UNITS: tuple[str, ...] = (
|
|
|
|
POWER_WATT,
|
|
|
|
POWER_KILO_WATT,
|
|
|
|
)
|
2022-09-22 12:15:22 +00:00
|
|
|
|
|
|
|
|
2022-09-22 14:44:09 +00:00
|
|
|
class PressureConverter(BaseUnitConverterWithUnitConversion):
|
2022-09-22 12:15:22 +00:00
|
|
|
"""Utility to convert pressure values."""
|
|
|
|
|
2022-09-22 14:44:09 +00:00
|
|
|
UNIT_CLASS = "pressure"
|
2022-09-22 13:39:49 +00:00
|
|
|
NORMALIZED_UNIT = PRESSURE_PA
|
2022-09-22 14:44:09 +00:00
|
|
|
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,
|
|
|
|
)
|
2022-09-22 12:15:22 +00:00
|
|
|
|
|
|
|
|
2022-09-23 12:23:59 +00:00
|
|
|
class SpeedConverter(BaseUnitConverterWithUnitConversion):
|
|
|
|
"""Utility to convert speed values."""
|
|
|
|
|
|
|
|
UNIT_CLASS = "speed"
|
|
|
|
NORMALIZED_UNIT = SPEED_METERS_PER_SECOND
|
|
|
|
UNIT_CONVERSION: dict[str, float] = {
|
|
|
|
SPEED_FEET_PER_SECOND: 1 / _FOOT_TO_M,
|
|
|
|
SPEED_INCHES_PER_DAY: _DAYS_TO_SECS / _IN_TO_M,
|
|
|
|
SPEED_INCHES_PER_HOUR: _HRS_TO_SECS / _IN_TO_M,
|
|
|
|
SPEED_KILOMETERS_PER_HOUR: _HRS_TO_SECS / _KM_TO_M,
|
|
|
|
SPEED_KNOTS: _HRS_TO_SECS / _NAUTICAL_MILE_TO_M,
|
|
|
|
SPEED_METERS_PER_SECOND: 1,
|
|
|
|
SPEED_MILES_PER_HOUR: _HRS_TO_SECS / _MILE_TO_M,
|
|
|
|
SPEED_MILLIMETERS_PER_DAY: _DAYS_TO_SECS / _MM_TO_M,
|
|
|
|
}
|
|
|
|
VALID_UNITS: tuple[str, ...] = (
|
|
|
|
SPEED_FEET_PER_SECOND,
|
|
|
|
SPEED_INCHES_PER_DAY,
|
|
|
|
SPEED_INCHES_PER_HOUR,
|
|
|
|
SPEED_KILOMETERS_PER_HOUR,
|
|
|
|
SPEED_KNOTS,
|
|
|
|
SPEED_METERS_PER_SECOND,
|
|
|
|
SPEED_MILES_PER_HOUR,
|
|
|
|
SPEED_MILLIMETERS_PER_DAY,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-09-22 12:15:22 +00:00
|
|
|
class TemperatureConverter(BaseUnitConverter):
|
|
|
|
"""Utility to convert temperature values."""
|
|
|
|
|
2022-09-22 16:31:50 +00:00
|
|
|
UNIT_CLASS = "temperature"
|
2022-09-22 13:39:49 +00:00
|
|
|
NORMALIZED_UNIT = TEMP_CELSIUS
|
2022-09-22 16:31:50 +00:00
|
|
|
VALID_UNITS: tuple[str, ...] = (
|
|
|
|
TEMP_CELSIUS,
|
|
|
|
TEMP_FAHRENHEIT,
|
|
|
|
TEMP_KELVIN,
|
|
|
|
)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def convert(
|
|
|
|
cls, value: float, from_unit: str, to_unit: str, *, interval: bool = False
|
|
|
|
) -> float:
|
|
|
|
"""Convert a temperature from one unit to another."""
|
|
|
|
cls._check_arguments(value, from_unit, to_unit)
|
|
|
|
|
|
|
|
if from_unit == to_unit:
|
|
|
|
return value
|
|
|
|
|
|
|
|
if from_unit == TEMP_CELSIUS:
|
|
|
|
if to_unit == TEMP_FAHRENHEIT:
|
|
|
|
return cls.celsius_to_fahrenheit(value, interval)
|
|
|
|
# kelvin
|
|
|
|
return cls.celsius_to_kelvin(value, interval)
|
|
|
|
|
|
|
|
if from_unit == TEMP_FAHRENHEIT:
|
|
|
|
if to_unit == TEMP_CELSIUS:
|
|
|
|
return cls.fahrenheit_to_celsius(value, interval)
|
|
|
|
# kelvin
|
|
|
|
return cls.celsius_to_kelvin(
|
|
|
|
cls.fahrenheit_to_celsius(value, interval), interval
|
|
|
|
)
|
|
|
|
|
|
|
|
# from_unit == kelvin
|
|
|
|
if to_unit == TEMP_CELSIUS:
|
|
|
|
return cls.kelvin_to_celsius(value, interval)
|
|
|
|
# fahrenheit
|
|
|
|
return cls.celsius_to_fahrenheit(
|
|
|
|
cls.kelvin_to_celsius(value, interval), interval
|
|
|
|
)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def fahrenheit_to_celsius(cls, fahrenheit: float, interval: bool = False) -> float:
|
|
|
|
"""Convert a temperature in Fahrenheit to Celsius."""
|
|
|
|
if interval:
|
|
|
|
return fahrenheit / 1.8
|
|
|
|
return (fahrenheit - 32.0) / 1.8
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def kelvin_to_celsius(cls, kelvin: float, interval: bool = False) -> float:
|
|
|
|
"""Convert a temperature in Kelvin to Celsius."""
|
|
|
|
if interval:
|
|
|
|
return kelvin
|
|
|
|
return kelvin - 273.15
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def celsius_to_fahrenheit(cls, celsius: float, interval: bool = False) -> float:
|
|
|
|
"""Convert a temperature in Celsius to Fahrenheit."""
|
|
|
|
if interval:
|
|
|
|
return celsius * 1.8
|
|
|
|
return celsius * 1.8 + 32.0
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def celsius_to_kelvin(cls, celsius: float, interval: bool = False) -> float:
|
|
|
|
"""Convert a temperature in Celsius to Kelvin."""
|
|
|
|
if interval:
|
|
|
|
return celsius
|
|
|
|
return celsius + 273.15
|
2022-09-22 12:15:22 +00:00
|
|
|
|
|
|
|
|
2022-09-22 15:49:45 +00:00
|
|
|
class VolumeConverter(BaseUnitConverterWithUnitConversion):
|
2022-09-22 12:15:22 +00:00
|
|
|
"""Utility to convert volume values."""
|
|
|
|
|
2022-09-22 15:49:45 +00:00
|
|
|
UNIT_CLASS = "volume"
|
2022-09-22 13:39:49 +00:00
|
|
|
NORMALIZED_UNIT = VOLUME_CUBIC_METERS
|
2022-09-22 15:49:45 +00:00
|
|
|
# Units in terms of m³
|
|
|
|
UNIT_CONVERSION: dict[str, float] = {
|
|
|
|
VOLUME_LITERS: 1 / _L_TO_CUBIC_METER,
|
|
|
|
VOLUME_MILLILITERS: 1 / _ML_TO_CUBIC_METER,
|
|
|
|
VOLUME_GALLONS: 1 / _GALLON_TO_CUBIC_METER,
|
|
|
|
VOLUME_FLUID_OUNCE: 1 / _FLUID_OUNCE_TO_CUBIC_METER,
|
|
|
|
VOLUME_CUBIC_METERS: 1,
|
|
|
|
VOLUME_CUBIC_FEET: 1 / _CUBIC_FOOT_TO_CUBIC_METER,
|
|
|
|
}
|
|
|
|
VALID_UNITS: tuple[str, ...] = (
|
|
|
|
VOLUME_LITERS,
|
|
|
|
VOLUME_MILLILITERS,
|
|
|
|
VOLUME_GALLONS,
|
|
|
|
VOLUME_FLUID_OUNCE,
|
|
|
|
VOLUME_CUBIC_METERS,
|
|
|
|
VOLUME_CUBIC_FEET,
|
|
|
|
)
|