diff --git a/homeassistant/const.py b/homeassistant/const.py index 43e07036cb8..da48646beed 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -686,6 +686,7 @@ PRESSURE: Final = "pressure" VOLUME: Final = "volume" TEMPERATURE: Final = "temperature" SPEED: Final = "speed" +WIND_SPEED: Final = "wind_speed" ILLUMINANCE: Final = "illuminance" WEEKDAYS: Final[list[str]] = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index bdd47112dde..1956faea84d 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -17,6 +17,8 @@ from homeassistant.const import ( PRESSURE, PRESSURE_PA, PRESSURE_PSI, + SPEED_METERS_PER_SECOND, + SPEED_MILES_PER_HOUR, TEMP_CELSIUS, TEMP_FAHRENHEIT, TEMPERATURE, @@ -24,10 +26,12 @@ from homeassistant.const import ( VOLUME, VOLUME_GALLONS, VOLUME_LITERS, + WIND_SPEED, ) from homeassistant.util import ( distance as distance_util, pressure as pressure_util, + speed as speed_util, temperature as temperature_util, volume as volume_util, ) @@ -42,6 +46,8 @@ PRESSURE_UNITS = pressure_util.VALID_UNITS VOLUME_UNITS = volume_util.VALID_UNITS +WIND_SPEED_UNITS = speed_util.VALID_UNITS + TEMPERATURE_UNITS: tuple[str, ...] = (TEMP_FAHRENHEIT, TEMP_CELSIUS) @@ -49,6 +55,8 @@ def is_valid_unit(unit: str, unit_type: str) -> bool: """Check if the unit is valid for it's type.""" if unit_type == LENGTH: units = LENGTH_UNITS + elif unit_type == WIND_SPEED: + units = WIND_SPEED_UNITS elif unit_type == TEMPERATURE: units = TEMPERATURE_UNITS elif unit_type == MASS: @@ -71,6 +79,7 @@ class UnitSystem: name: str, temperature: str, length: str, + wind_speed: str, volume: str, mass: str, pressure: str, @@ -81,6 +90,7 @@ class UnitSystem: for unit, unit_type in ( (temperature, TEMPERATURE), (length, LENGTH), + (wind_speed, WIND_SPEED), (volume, VOLUME), (mass, MASS), (pressure, PRESSURE), @@ -97,6 +107,7 @@ class UnitSystem: self.mass_unit = mass self.pressure_unit = pressure self.volume_unit = volume + self.wind_speed_unit = wind_speed @property def is_metric(self) -> bool: @@ -130,6 +141,14 @@ class UnitSystem: pressure, from_unit, self.pressure_unit ) + def wind_speed(self, wind_speed: float | None, from_unit: str) -> float: + """Convert the given wind_speed to this unit system.""" + if not isinstance(wind_speed, Number): + raise TypeError(f"{wind_speed!s} is not a numeric value.") + + # type ignore: https://github.com/python/mypy/issues/7207 + return speed_util.convert(wind_speed, from_unit, self.wind_speed_unit) # type: ignore + def volume(self, volume: float | None, from_unit: str) -> float: """Convert the given volume to this unit system.""" if not isinstance(volume, Number): @@ -146,6 +165,7 @@ class UnitSystem: PRESSURE: self.pressure_unit, TEMPERATURE: self.temperature_unit, VOLUME: self.volume_unit, + WIND_SPEED: self.wind_speed_unit, } @@ -153,6 +173,7 @@ METRIC_SYSTEM = UnitSystem( CONF_UNIT_SYSTEM_METRIC, TEMP_CELSIUS, LENGTH_KILOMETERS, + SPEED_METERS_PER_SECOND, VOLUME_LITERS, MASS_GRAMS, PRESSURE_PA, @@ -162,6 +183,7 @@ IMPERIAL_SYSTEM = UnitSystem( CONF_UNIT_SYSTEM_IMPERIAL, TEMP_FAHRENHEIT, LENGTH_MILES, + SPEED_MILES_PER_HOUR, VOLUME_GALLONS, MASS_POUNDS, PRESSURE_PSI, diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index dd0c4c96a11..f871fc88d3a 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -14,6 +14,7 @@ from homeassistant.const import ( LENGTH_METERS, MASS_GRAMS, PRESSURE_PA, + SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, VOLUME_LITERS, ) @@ -34,7 +35,13 @@ from tests.common import ( def _set_up_units(hass): """Set up the tests.""" hass.config.units = UnitSystem( - "custom", TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, MASS_GRAMS, PRESSURE_PA + "custom", + TEMP_CELSIUS, + LENGTH_METERS, + SPEED_KILOMETERS_PER_HOUR, + VOLUME_LITERS, + MASS_GRAMS, + PRESSURE_PA, ) diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index f32e731f9b3..5c7ac660f9b 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -9,10 +9,12 @@ from homeassistant.const import ( MASS_GRAMS, PRESSURE, PRESSURE_PA, + SPEED_METERS_PER_SECOND, TEMP_CELSIUS, TEMPERATURE, VOLUME, VOLUME_LITERS, + WIND_SPEED, ) from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem @@ -27,6 +29,7 @@ def test_invalid_units(): SYSTEM_NAME, INVALID_UNIT, LENGTH_METERS, + SPEED_METERS_PER_SECOND, VOLUME_LITERS, MASS_GRAMS, PRESSURE_PA, @@ -37,6 +40,7 @@ def test_invalid_units(): SYSTEM_NAME, TEMP_CELSIUS, INVALID_UNIT, + SPEED_METERS_PER_SECOND, VOLUME_LITERS, MASS_GRAMS, PRESSURE_PA, @@ -48,6 +52,7 @@ def test_invalid_units(): TEMP_CELSIUS, LENGTH_METERS, INVALID_UNIT, + VOLUME_LITERS, MASS_GRAMS, PRESSURE_PA, ) @@ -57,6 +62,18 @@ def test_invalid_units(): SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, + SPEED_METERS_PER_SECOND, + INVALID_UNIT, + MASS_GRAMS, + PRESSURE_PA, + ) + + with pytest.raises(ValueError): + UnitSystem( + SYSTEM_NAME, + TEMP_CELSIUS, + LENGTH_METERS, + SPEED_METERS_PER_SECOND, VOLUME_LITERS, INVALID_UNIT, PRESSURE_PA, @@ -67,6 +84,7 @@ def test_invalid_units(): SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, + SPEED_METERS_PER_SECOND, VOLUME_LITERS, MASS_GRAMS, INVALID_UNIT, @@ -79,6 +97,8 @@ def test_invalid_value(): METRIC_SYSTEM.length("25a", LENGTH_KILOMETERS) with pytest.raises(TypeError): METRIC_SYSTEM.temperature("50K", TEMP_CELSIUS) + with pytest.raises(TypeError): + METRIC_SYSTEM.wind_speed("50km/h", SPEED_METERS_PER_SECOND) with pytest.raises(TypeError): METRIC_SYSTEM.volume("50L", VOLUME_LITERS) with pytest.raises(TypeError): @@ -89,6 +109,7 @@ def test_as_dict(): """Test that the as_dict() method returns the expected dictionary.""" expected = { LENGTH: LENGTH_KILOMETERS, + WIND_SPEED: SPEED_METERS_PER_SECOND, TEMPERATURE: TEMP_CELSIUS, VOLUME: VOLUME_LITERS, MASS: MASS_GRAMS, @@ -142,6 +163,29 @@ def test_length_to_imperial(): assert IMPERIAL_SYSTEM.length(5, METRIC_SYSTEM.length_unit) == 3.106855 +def test_wind_speed_unknown_unit(): + """Test wind_speed conversion with unknown from unit.""" + with pytest.raises(ValueError): + METRIC_SYSTEM.length(5, "turtles") + + +def test_wind_speed_to_metric(): + """Test length conversion to metric system.""" + assert METRIC_SYSTEM.wind_speed(100, METRIC_SYSTEM.wind_speed_unit) == 100 + # 1 m/s is about 2.237 mph + assert METRIC_SYSTEM.wind_speed( + 2237, IMPERIAL_SYSTEM.wind_speed_unit + ) == pytest.approx(1000, abs=0.1) + + +def test_wind_speed_to_imperial(): + """Test wind_speed conversion to imperial system.""" + assert IMPERIAL_SYSTEM.wind_speed(100, IMPERIAL_SYSTEM.wind_speed_unit) == 100 + assert IMPERIAL_SYSTEM.wind_speed( + 1000, METRIC_SYSTEM.wind_speed_unit + ) == pytest.approx(2237, abs=0.1) + + def test_pressure_same_unit(): """Test no conversion happens if to unit is same as from unit.""" assert METRIC_SYSTEM.pressure(5, METRIC_SYSTEM.pressure_unit) == 5 @@ -172,6 +216,7 @@ def test_pressure_to_imperial(): def test_properties(): """Test the unit properties are returned as expected.""" assert METRIC_SYSTEM.length_unit == LENGTH_KILOMETERS + assert METRIC_SYSTEM.wind_speed_unit == SPEED_METERS_PER_SECOND assert METRIC_SYSTEM.temperature_unit == TEMP_CELSIUS assert METRIC_SYSTEM.mass_unit == MASS_GRAMS assert METRIC_SYSTEM.volume_unit == VOLUME_LITERS