Update unit system unit conversions (#87632)

* Update unit system unit conversions

* Tweak tests
This commit is contained in:
Erik Montnemery 2023-02-08 10:52:54 +01:00 committed by GitHub
parent c3e733c0aa
commit 45ac8c515e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 282 additions and 0 deletions

View File

@ -22,6 +22,7 @@ from homeassistant.const import (
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolume,
UnitOfVolumetricFlux,
)
from .unit_conversion import (
@ -244,12 +245,29 @@ METRIC_SYSTEM = UnitSystem(
("gas", UnitOfVolume.CUBIC_FEET): UnitOfVolume.CUBIC_METERS,
# Convert non-metric precipitation
("precipitation", UnitOfLength.INCHES): UnitOfLength.MILLIMETERS,
# Convert non-metric precipitation intensity
(
"precipitation_intensity",
UnitOfVolumetricFlux.INCHES_PER_DAY,
): UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
(
"precipitation_intensity",
UnitOfVolumetricFlux.INCHES_PER_HOUR,
): UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
# Convert non-metric pressure
("pressure", UnitOfPressure.PSI): UnitOfPressure.KPA,
("pressure", UnitOfPressure.INHG): UnitOfPressure.HPA,
# Convert non-metric speeds except knots to km/h
("speed", UnitOfSpeed.FEET_PER_SECOND): UnitOfSpeed.KILOMETERS_PER_HOUR,
("speed", UnitOfSpeed.MILES_PER_HOUR): UnitOfSpeed.KILOMETERS_PER_HOUR,
(
"speed",
UnitOfVolumetricFlux.INCHES_PER_DAY,
): UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
(
"speed",
UnitOfVolumetricFlux.INCHES_PER_HOUR,
): UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
# Convert non-metric volumes
("volume", UnitOfVolume.CENTUM_CUBIC_FEET): UnitOfVolume.CUBIC_METERS,
("volume", UnitOfVolume.CUBIC_FEET): UnitOfVolume.CUBIC_METERS,
@ -288,6 +306,15 @@ US_CUSTOMARY_SYSTEM = UnitSystem(
# Convert non-USCS precipitation
("precipitation", UnitOfLength.CENTIMETERS): UnitOfLength.INCHES,
("precipitation", UnitOfLength.MILLIMETERS): UnitOfLength.INCHES,
# Convert non-USCS precipitation intensity
(
"precipitation_intensity",
UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
): UnitOfVolumetricFlux.INCHES_PER_DAY,
(
"precipitation_intensity",
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
): UnitOfVolumetricFlux.INCHES_PER_HOUR,
# Convert non-USCS pressure
("pressure", UnitOfPressure.MBAR): UnitOfPressure.PSI,
("pressure", UnitOfPressure.CBAR): UnitOfPressure.PSI,
@ -299,6 +326,14 @@ US_CUSTOMARY_SYSTEM = UnitSystem(
# Convert non-USCS speeds, except knots, to mph
("speed", UnitOfSpeed.METERS_PER_SECOND): UnitOfSpeed.MILES_PER_HOUR,
("speed", UnitOfSpeed.KILOMETERS_PER_HOUR): UnitOfSpeed.MILES_PER_HOUR,
(
"speed",
UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
): UnitOfVolumetricFlux.INCHES_PER_DAY,
(
"speed",
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
): UnitOfVolumetricFlux.INCHES_PER_HOUR,
# Convert non-USCS volumes
("volume", UnitOfVolume.CUBIC_METERS): UnitOfVolume.CUBIC_FEET,
("volume", UnitOfVolume.LITERS): UnitOfVolume.GALLONS,

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import pytest
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.components.sensor.const import DEVICE_CLASS_UNITS
from homeassistant.const import (
ACCUMULATED_PRECIPITATION,
LENGTH,
@ -18,6 +19,7 @@ from homeassistant.const import (
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolume,
UnitOfVolumetricFlux,
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.unit_system import (
@ -361,9 +363,45 @@ def test_get_unit_system_invalid(key: str) -> None:
(SensorDeviceClass.DISTANCE, UnitOfLength.KILOMETERS, None),
(SensorDeviceClass.DISTANCE, "very_long", None),
# Test gas meter conversion
(
SensorDeviceClass.GAS,
UnitOfVolume.CENTUM_CUBIC_FEET,
UnitOfVolume.CUBIC_METERS,
),
(SensorDeviceClass.GAS, UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS),
(SensorDeviceClass.GAS, UnitOfVolume.CUBIC_METERS, None),
(SensorDeviceClass.GAS, "very_much", None),
# Test precipitation conversion
(
SensorDeviceClass.PRECIPITATION,
UnitOfLength.INCHES,
UnitOfLength.MILLIMETERS,
),
(SensorDeviceClass.PRECIPITATION, UnitOfLength.CENTIMETERS, None),
(SensorDeviceClass.PRECIPITATION, UnitOfLength.MILLIMETERS, None),
(SensorDeviceClass.PRECIPITATION, "very_much", None),
# Test precipitation intensity conversion
(
SensorDeviceClass.PRECIPITATION_INTENSITY,
UnitOfVolumetricFlux.INCHES_PER_DAY,
UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
),
(
SensorDeviceClass.PRECIPITATION_INTENSITY,
UnitOfVolumetricFlux.INCHES_PER_HOUR,
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
),
(
SensorDeviceClass.PRECIPITATION_INTENSITY,
UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
None,
),
(
SensorDeviceClass.PRECIPITATION_INTENSITY,
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
None,
),
(SensorDeviceClass.PRECIPITATION_INTENSITY, "very_heavy", None),
# Test pressure conversion
(SensorDeviceClass.PRESSURE, UnitOfPressure.PSI, UnitOfPressure.KPA),
(SensorDeviceClass.PRESSURE, UnitOfPressure.BAR, None),
@ -382,8 +420,25 @@ def test_get_unit_system_invalid(key: str) -> None:
(SensorDeviceClass.SPEED, UnitOfSpeed.KILOMETERS_PER_HOUR, None),
(SensorDeviceClass.SPEED, UnitOfSpeed.KNOTS, None),
(SensorDeviceClass.SPEED, UnitOfSpeed.METERS_PER_SECOND, None),
(
SensorDeviceClass.SPEED,
UnitOfVolumetricFlux.INCHES_PER_DAY,
UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
),
(
SensorDeviceClass.SPEED,
UnitOfVolumetricFlux.INCHES_PER_HOUR,
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
),
(SensorDeviceClass.SPEED, UnitOfVolumetricFlux.MILLIMETERS_PER_DAY, None),
(SensorDeviceClass.SPEED, UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR, None),
(SensorDeviceClass.SPEED, "very_fast", None),
# Test volume conversion
(
SensorDeviceClass.VOLUME,
UnitOfVolume.CENTUM_CUBIC_FEET,
UnitOfVolume.CUBIC_METERS,
),
(SensorDeviceClass.VOLUME, UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS),
(SensorDeviceClass.VOLUME, UnitOfVolume.FLUID_OUNCES, UnitOfVolume.MILLILITERS),
(SensorDeviceClass.VOLUME, UnitOfVolume.GALLONS, UnitOfVolume.LITERS),
@ -392,6 +447,11 @@ def test_get_unit_system_invalid(key: str) -> None:
(SensorDeviceClass.VOLUME, UnitOfVolume.MILLILITERS, None),
(SensorDeviceClass.VOLUME, "very_much", None),
# Test water meter conversion
(
SensorDeviceClass.WATER,
UnitOfVolume.CENTUM_CUBIC_FEET,
UnitOfVolume.CUBIC_METERS,
),
(SensorDeviceClass.WATER, UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS),
(SensorDeviceClass.WATER, UnitOfVolume.GALLONS, UnitOfVolume.LITERS),
(SensorDeviceClass.WATER, UnitOfVolume.CUBIC_METERS, None),
@ -409,6 +469,79 @@ def test_get_metric_converted_unit_(
assert unit_system.get_converted_unit(device_class, original_unit) == state_unit
UNCONVERTED_UNITS_METRIC_SYSTEM = {
SensorDeviceClass.ATMOSPHERIC_PRESSURE: (UnitOfPressure.HPA,),
SensorDeviceClass.DISTANCE: (
UnitOfLength.CENTIMETERS,
UnitOfLength.KILOMETERS,
UnitOfLength.METERS,
UnitOfLength.MILLIMETERS,
),
SensorDeviceClass.GAS: (UnitOfVolume.CUBIC_METERS,),
SensorDeviceClass.PRECIPITATION: (
UnitOfLength.CENTIMETERS,
UnitOfLength.MILLIMETERS,
),
SensorDeviceClass.PRECIPITATION_INTENSITY: (
UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
),
SensorDeviceClass.PRESSURE: (
UnitOfPressure.BAR,
UnitOfPressure.CBAR,
UnitOfPressure.HPA,
UnitOfPressure.KPA,
UnitOfPressure.MBAR,
UnitOfPressure.MMHG,
UnitOfPressure.PA,
),
SensorDeviceClass.SPEED: (
UnitOfSpeed.KILOMETERS_PER_HOUR,
UnitOfSpeed.KNOTS,
UnitOfSpeed.METERS_PER_SECOND,
UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
),
SensorDeviceClass.VOLUME: (
UnitOfVolume.CUBIC_METERS,
UnitOfVolume.LITERS,
UnitOfVolume.MILLILITERS,
),
SensorDeviceClass.WATER: (
UnitOfVolume.CUBIC_METERS,
UnitOfVolume.LITERS,
),
}
@pytest.mark.parametrize(
"device_class",
(
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
SensorDeviceClass.DISTANCE,
SensorDeviceClass.GAS,
SensorDeviceClass.PRECIPITATION,
SensorDeviceClass.PRECIPITATION_INTENSITY,
SensorDeviceClass.PRESSURE,
SensorDeviceClass.SPEED,
SensorDeviceClass.VOLUME,
SensorDeviceClass.WATER,
),
)
def test_metric_converted_units(device_class: SensorDeviceClass) -> None:
"""Test unit conversion rules are in place for all units."""
unit_system = METRIC_SYSTEM
# Make sure excluded_units is not stale
for unit in UNCONVERTED_UNITS_METRIC_SYSTEM[device_class]:
assert unit in DEVICE_CLASS_UNITS[device_class]
for unit in DEVICE_CLASS_UNITS[device_class]:
if unit in UNCONVERTED_UNITS_METRIC_SYSTEM[device_class]:
assert (device_class, unit) not in unit_system._conversions
continue
assert (device_class, unit) in unit_system._conversions
@pytest.mark.parametrize(
"device_class, original_unit, state_unit",
(
@ -438,9 +571,45 @@ def test_get_metric_converted_unit_(
(SensorDeviceClass.DISTANCE, UnitOfLength.MILES, None),
(SensorDeviceClass.DISTANCE, "very_long", None),
# Test gas meter conversion
(SensorDeviceClass.GAS, UnitOfVolume.CENTUM_CUBIC_FEET, None),
(SensorDeviceClass.GAS, UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_FEET),
(SensorDeviceClass.GAS, UnitOfVolume.CUBIC_FEET, None),
(SensorDeviceClass.GAS, "very_much", None),
# Test precipitation conversion
(
SensorDeviceClass.PRECIPITATION,
UnitOfLength.CENTIMETERS,
UnitOfLength.INCHES,
),
(
SensorDeviceClass.PRECIPITATION,
UnitOfLength.MILLIMETERS,
UnitOfLength.INCHES,
),
(SensorDeviceClass.PRECIPITATION, UnitOfLength.INCHES, None),
(SensorDeviceClass.PRECIPITATION, "very_much", None),
# Test precipitation intensity conversion
(
SensorDeviceClass.PRECIPITATION_INTENSITY,
UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
UnitOfVolumetricFlux.INCHES_PER_DAY,
),
(
SensorDeviceClass.PRECIPITATION_INTENSITY,
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
UnitOfVolumetricFlux.INCHES_PER_HOUR,
),
(
SensorDeviceClass.PRECIPITATION_INTENSITY,
UnitOfVolumetricFlux.INCHES_PER_DAY,
None,
),
(
SensorDeviceClass.PRECIPITATION_INTENSITY,
UnitOfVolumetricFlux.INCHES_PER_HOUR,
None,
),
(SensorDeviceClass.PRECIPITATION_INTENSITY, "very_heavy", None),
# Test pressure conversion
(SensorDeviceClass.PRESSURE, UnitOfPressure.BAR, UnitOfPressure.PSI),
(SensorDeviceClass.PRESSURE, UnitOfPressure.PSI, None),
@ -459,11 +628,24 @@ def test_get_metric_converted_unit_(
(SensorDeviceClass.SPEED, UnitOfSpeed.FEET_PER_SECOND, None),
(SensorDeviceClass.SPEED, UnitOfSpeed.KNOTS, None),
(SensorDeviceClass.SPEED, UnitOfSpeed.MILES_PER_HOUR, None),
(
SensorDeviceClass.SPEED,
UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
UnitOfVolumetricFlux.INCHES_PER_DAY,
),
(
SensorDeviceClass.SPEED,
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
UnitOfVolumetricFlux.INCHES_PER_HOUR,
),
(SensorDeviceClass.SPEED, UnitOfVolumetricFlux.INCHES_PER_DAY, None),
(SensorDeviceClass.SPEED, UnitOfVolumetricFlux.INCHES_PER_HOUR, None),
(SensorDeviceClass.SPEED, "very_fast", None),
# Test volume conversion
(SensorDeviceClass.VOLUME, UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_FEET),
(SensorDeviceClass.VOLUME, UnitOfVolume.LITERS, UnitOfVolume.GALLONS),
(SensorDeviceClass.VOLUME, UnitOfVolume.MILLILITERS, UnitOfVolume.FLUID_OUNCES),
(SensorDeviceClass.VOLUME, UnitOfVolume.CENTUM_CUBIC_FEET, None),
(SensorDeviceClass.VOLUME, UnitOfVolume.CUBIC_FEET, None),
(SensorDeviceClass.VOLUME, UnitOfVolume.FLUID_OUNCES, None),
(SensorDeviceClass.VOLUME, UnitOfVolume.GALLONS, None),
@ -471,6 +653,7 @@ def test_get_metric_converted_unit_(
# Test water meter conversion
(SensorDeviceClass.WATER, UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_FEET),
(SensorDeviceClass.WATER, UnitOfVolume.LITERS, UnitOfVolume.GALLONS),
(SensorDeviceClass.WATER, UnitOfVolume.CENTUM_CUBIC_FEET, None),
(SensorDeviceClass.WATER, UnitOfVolume.CUBIC_FEET, None),
(SensorDeviceClass.WATER, UnitOfVolume.GALLONS, None),
(SensorDeviceClass.WATER, "very_much", None),
@ -484,3 +667,67 @@ def test_get_us_converted_unit(
"""Test unit conversion rules."""
unit_system = US_CUSTOMARY_SYSTEM
assert unit_system.get_converted_unit(device_class, original_unit) == state_unit
UNCONVERTED_UNITS_US_SYSTEM = {
SensorDeviceClass.ATMOSPHERIC_PRESSURE: (UnitOfPressure.INHG,),
SensorDeviceClass.DISTANCE: (
UnitOfLength.FEET,
UnitOfLength.INCHES,
UnitOfLength.MILES,
UnitOfLength.YARDS,
),
SensorDeviceClass.GAS: (UnitOfVolume.CENTUM_CUBIC_FEET, UnitOfVolume.CUBIC_FEET),
SensorDeviceClass.PRECIPITATION: (UnitOfLength.INCHES,),
SensorDeviceClass.PRECIPITATION_INTENSITY: (
UnitOfVolumetricFlux.INCHES_PER_DAY,
UnitOfVolumetricFlux.INCHES_PER_HOUR,
),
SensorDeviceClass.PRESSURE: (UnitOfPressure.INHG, UnitOfPressure.PSI),
SensorDeviceClass.SPEED: (
UnitOfSpeed.FEET_PER_SECOND,
UnitOfSpeed.KNOTS,
UnitOfSpeed.MILES_PER_HOUR,
UnitOfVolumetricFlux.INCHES_PER_DAY,
UnitOfVolumetricFlux.INCHES_PER_HOUR,
),
SensorDeviceClass.VOLUME: (
UnitOfVolume.CENTUM_CUBIC_FEET,
UnitOfVolume.CUBIC_FEET,
UnitOfVolume.FLUID_OUNCES,
UnitOfVolume.GALLONS,
),
SensorDeviceClass.WATER: (
UnitOfVolume.CENTUM_CUBIC_FEET,
UnitOfVolume.CUBIC_FEET,
UnitOfVolume.GALLONS,
),
}
@pytest.mark.parametrize(
"device_class",
(
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
SensorDeviceClass.DISTANCE,
SensorDeviceClass.GAS,
SensorDeviceClass.PRECIPITATION,
SensorDeviceClass.PRECIPITATION_INTENSITY,
SensorDeviceClass.PRESSURE,
SensorDeviceClass.SPEED,
SensorDeviceClass.VOLUME,
SensorDeviceClass.WATER,
),
)
def test_imperial_converted_units(device_class: SensorDeviceClass) -> None:
"""Test unit conversion rules are in place for all units."""
unit_system = US_CUSTOMARY_SYSTEM
# Make sure excluded_units is not stale
for unit in UNCONVERTED_UNITS_US_SYSTEM[device_class]:
assert unit in DEVICE_CLASS_UNITS[device_class]
for unit in DEVICE_CLASS_UNITS[device_class]:
if unit in UNCONVERTED_UNITS_US_SYSTEM[device_class]:
assert (device_class, unit) not in unit_system._conversions
continue
assert (device_class, unit) in unit_system._conversions