Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions homeassistant/components/climacell/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
LENGTH_MILES,
PRESSURE_HPA,
PRESSURE_INHG,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
TEMP_FAHRENHEIT,
)
from homeassistant.core import HomeAssistant
Expand All @@ -45,6 +47,7 @@
from homeassistant.util import dt as dt_util
from homeassistant.util.distance import convert as distance_convert
from homeassistant.util.pressure import convert as pressure_convert
from homeassistant.util.speed import convert as speed_convert

from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity
from .const import (
Expand Down Expand Up @@ -166,7 +169,10 @@ def _forecast_dict(
)
if wind_speed:
wind_speed = round(
distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS), 4
speed_convert(
wind_speed, SPEED_MILES_PER_HOUR, SPEED_KILOMETERS_PER_HOUR
),
4,
)

data = {
Expand All @@ -188,7 +194,10 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None:
wind_gust = self.wind_gust
if wind_gust and self.hass.config.units.is_metric:
wind_gust = round(
distance_convert(self.wind_gust, LENGTH_MILES, LENGTH_KILOMETERS), 4
speed_convert(
self.wind_gust, SPEED_MILES_PER_HOUR, SPEED_KILOMETERS_PER_HOUR
),
4,
)
cloud_cover = self.cloud_cover
return {
Expand Down Expand Up @@ -236,7 +245,10 @@ def wind_speed(self):
"""Return the wind speed."""
if self.hass.config.units.is_metric and self._wind_speed:
return round(
distance_convert(self._wind_speed, LENGTH_MILES, LENGTH_KILOMETERS), 4
speed_convert(
self._wind_speed, SPEED_MILES_PER_HOUR, SPEED_KILOMETERS_PER_HOUR
),
4,
)
return self._wind_speed

Expand Down
9 changes: 6 additions & 3 deletions homeassistant/components/met/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,18 @@
CONF_LONGITUDE,
CONF_NAME,
LENGTH_INCHES,
LENGTH_KILOMETERS,
LENGTH_MILES,
LENGTH_MILLIMETERS,
PRESSURE_HPA,
PRESSURE_INHG,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.distance import convert as convert_distance
from homeassistant.util.pressure import convert as convert_pressure
from homeassistant.util.speed import convert as convert_speed

from .const import (
ATTR_FORECAST_PRECIPITATION,
Expand Down Expand Up @@ -198,7 +199,9 @@ def wind_speed(self):
if self._is_metric or speed_km_h is None:
return speed_km_h

speed_mi_h = convert_distance(speed_km_h, LENGTH_KILOMETERS, LENGTH_MILES)
speed_mi_h = convert_speed(
speed_km_h, SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR
)
return int(round(speed_mi_h))

@property
Expand Down
10 changes: 6 additions & 4 deletions homeassistant/components/met_eireann/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@
CONF_LONGITUDE,
CONF_NAME,
LENGTH_INCHES,
LENGTH_METERS,
LENGTH_MILES,
LENGTH_MILLIMETERS,
PRESSURE_HPA,
PRESSURE_INHG,
SPEED_METERS_PER_SECOND,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from homeassistant.util.distance import convert as convert_distance
from homeassistant.util.pressure import convert as convert_pressure
from homeassistant.util.speed import convert as convert_speed

from .const import ATTRIBUTION, CONDITION_MAP, DEFAULT_NAME, DOMAIN, FORECAST_MAP

Expand Down Expand Up @@ -129,8 +130,9 @@ def wind_speed(self):
if self._is_metric or speed_m_s is None:
return speed_m_s

speed_mi_s = convert_distance(speed_m_s, LENGTH_METERS, LENGTH_MILES)
speed_mi_h = speed_mi_s / 3600.0
speed_mi_h = convert_speed(
speed_m_s, SPEED_METERS_PER_SECOND, SPEED_MILES_PER_HOUR
)
return int(round(speed_mi_h))

@property
Expand Down
7 changes: 5 additions & 2 deletions homeassistant/components/nws/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
ATTR_ATTRIBUTION,
CONF_LATITUDE,
CONF_LONGITUDE,
LENGTH_KILOMETERS,
LENGTH_METERS,
LENGTH_MILES,
PERCENTAGE,
PRESSURE_INHG,
PRESSURE_PA,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
)
Expand All @@ -19,6 +19,7 @@
from homeassistant.util.distance import convert as convert_distance
from homeassistant.util.dt import utcnow
from homeassistant.util.pressure import convert as convert_pressure
from homeassistant.util.speed import convert as convert_speed

from . import base_unique_id, device_info
from .const import (
Expand Down Expand Up @@ -86,7 +87,9 @@ def native_value(self):
# Set alias to unit property -> prevent unnecessary hasattr calls
unit_of_measurement = self.native_unit_of_measurement
if unit_of_measurement == SPEED_MILES_PER_HOUR:
return round(convert_distance(value, LENGTH_KILOMETERS, LENGTH_MILES))
return round(
convert_speed(value, SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR)
)
if unit_of_measurement == LENGTH_MILES:
return round(convert_distance(value, LENGTH_METERS, LENGTH_MILES))
if unit_of_measurement == PRESSURE_INHG:
Expand Down
11 changes: 9 additions & 2 deletions homeassistant/components/nws/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
PRESSURE_HPA,
PRESSURE_INHG,
PRESSURE_PA,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
Expand All @@ -28,6 +30,7 @@
from homeassistant.util.distance import convert as convert_distance
from homeassistant.util.dt import utcnow
from homeassistant.util.pressure import convert as convert_pressure
from homeassistant.util.speed import convert as convert_speed
from homeassistant.util.temperature import convert as convert_temperature

from . import base_unique_id, device_info
Expand Down Expand Up @@ -196,7 +199,9 @@ def wind_speed(self):
if self.is_metric:
wind = wind_km_hr
else:
wind = convert_distance(wind_km_hr, LENGTH_KILOMETERS, LENGTH_MILES)
wind = convert_speed(
wind_km_hr, SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR
)
return round(wind)

@property
Expand Down Expand Up @@ -271,7 +276,9 @@ def forecast(self):
if wind_speed is not None:
if self.is_metric:
data[ATTR_FORECAST_WIND_SPEED] = round(
convert_distance(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS)
convert_speed(
wind_speed, SPEED_MILES_PER_HOUR, SPEED_KILOMETERS_PER_HOUR
)
)
else:
data[ATTR_FORECAST_WIND_SPEED] = round(wind_speed)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@
PRESSURE: Final = "pressure"
VOLUME: Final = "volume"
TEMPERATURE: Final = "temperature"
SPEED_MS: Final = "speed_ms"
SPEED: Final = "speed"
ILLUMINANCE: Final = "illuminance"

WEEKDAYS: Final[list[str]] = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
Expand Down
56 changes: 56 additions & 0 deletions homeassistant/util/speed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Distance util functions."""
from __future__ import annotations

from numbers import Number

from homeassistant.const import (
SPEED,
SPEED_INCHES_PER_DAY,
SPEED_INCHES_PER_HOUR,
SPEED_KILOMETERS_PER_HOUR,
SPEED_METERS_PER_SECOND,
SPEED_MILES_PER_HOUR,
SPEED_MILLIMETERS_PER_DAY,
UNIT_NOT_RECOGNIZED_TEMPLATE,
)

VALID_UNITS: tuple[str, ...] = (
SPEED_METERS_PER_SECOND,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
SPEED_MILLIMETERS_PER_DAY,
SPEED_INCHES_PER_DAY,
SPEED_INCHES_PER_HOUR,
)

HRS_TO_SECS = 60 * 60 # 1 hr = 3600 seconds
KM_TO_M = 1000 # 1 km = 1000 m
KM_TO_MILE = 0.62137119 # 1 km = 0.62137119 mi
M_TO_IN = 39.3700787 # 1 m = 39.3700787 in

# Units in terms of m/s
UNIT_CONVERSION: dict[str, float] = {
SPEED_METERS_PER_SECOND: 1,
SPEED_KILOMETERS_PER_HOUR: HRS_TO_SECS / KM_TO_M,
SPEED_MILES_PER_HOUR: HRS_TO_SECS * KM_TO_MILE / KM_TO_M,
SPEED_MILLIMETERS_PER_DAY: (24 * HRS_TO_SECS) * 1000,
SPEED_INCHES_PER_DAY: (24 * HRS_TO_SECS) * M_TO_IN,
SPEED_INCHES_PER_HOUR: HRS_TO_SECS * M_TO_IN,
}


def convert(value: float, unit_1: str, unit_2: str) -> float:
"""Convert one unit of measurement to another."""
if unit_1 not in VALID_UNITS:
raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(unit_1, SPEED))
if unit_2 not in VALID_UNITS:
raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(unit_2, SPEED))

if not isinstance(value, Number):
raise TypeError(f"{value} is not of numeric type")

if unit_1 == unit_2:
return value

meters_per_second = value / UNIT_CONVERSION[unit_1]
return meters_per_second * UNIT_CONVERSION[unit_2]
15 changes: 11 additions & 4 deletions tests/components/nws/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
PRESSURE_HPA,
PRESSURE_INHG,
PRESSURE_PA,
SPEED_KILOMETERS_PER_HOUR,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.util.distance import convert as convert_distance
from homeassistant.util.pressure import convert as convert_pressure
from homeassistant.util.speed import convert as convert_speed
from homeassistant.util.temperature import convert as convert_temperature

NWS_CONFIG = {
Expand Down Expand Up @@ -80,8 +83,12 @@
"windChill": str(round(convert_temperature(5, TEMP_CELSIUS, TEMP_FAHRENHEIT))),
"heatIndex": str(round(convert_temperature(15, TEMP_CELSIUS, TEMP_FAHRENHEIT))),
"relativeHumidity": "10",
"windSpeed": str(round(convert_distance(10, LENGTH_KILOMETERS, LENGTH_MILES))),
"windGust": str(round(convert_distance(20, LENGTH_KILOMETERS, LENGTH_MILES))),
"windSpeed": str(
round(convert_speed(10, SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR))
),
"windGust": str(
round(convert_speed(20, SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR))
),
"windDirection": "180",
"barometricPressure": str(
round(convert_pressure(100000, PRESSURE_PA, PRESSURE_INHG), 2)
Expand All @@ -98,7 +105,7 @@
),
ATTR_WEATHER_WIND_BEARING: 180,
ATTR_WEATHER_WIND_SPEED: round(
convert_distance(10, LENGTH_KILOMETERS, LENGTH_MILES)
convert_speed(10, SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR)
),
ATTR_WEATHER_PRESSURE: round(
convert_pressure(100000, PRESSURE_PA, PRESSURE_INHG), 2
Expand Down Expand Up @@ -152,7 +159,7 @@
ATTR_FORECAST_TIME: "2019-08-12T20:00:00-04:00",
ATTR_FORECAST_TEMP: round(convert_temperature(10, TEMP_FAHRENHEIT, TEMP_CELSIUS)),
ATTR_FORECAST_WIND_SPEED: round(
convert_distance(10, LENGTH_MILES, LENGTH_KILOMETERS)
convert_speed(10, SPEED_MILES_PER_HOUR, SPEED_KILOMETERS_PER_HOUR)
),
ATTR_FORECAST_WIND_BEARING: 180,
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 90,
Expand Down
70 changes: 70 additions & 0 deletions tests/util/test_speed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Test Home Assistant speed utility functions."""
import pytest

from homeassistant.const import (
SPEED_INCHES_PER_DAY,
SPEED_INCHES_PER_HOUR,
SPEED_KILOMETERS_PER_HOUR,
SPEED_METERS_PER_SECOND,
SPEED_MILES_PER_HOUR,
SPEED_MILLIMETERS_PER_DAY,
)
import homeassistant.util.speed as speed_util

INVALID_SYMBOL = "bob"
VALID_SYMBOL = SPEED_KILOMETERS_PER_HOUR


def test_convert_same_unit():
"""Test conversion from any unit to same unit."""
assert speed_util.convert(2, SPEED_INCHES_PER_DAY, SPEED_INCHES_PER_DAY) == 2
assert speed_util.convert(3, SPEED_INCHES_PER_HOUR, SPEED_INCHES_PER_HOUR) == 3
assert (
speed_util.convert(4, SPEED_KILOMETERS_PER_HOUR, SPEED_KILOMETERS_PER_HOUR) == 4
)
assert speed_util.convert(5, SPEED_METERS_PER_SECOND, SPEED_METERS_PER_SECOND) == 5
assert speed_util.convert(6, SPEED_MILES_PER_HOUR, SPEED_MILES_PER_HOUR) == 6
assert (
speed_util.convert(7, SPEED_MILLIMETERS_PER_DAY, SPEED_MILLIMETERS_PER_DAY) == 7
)


def test_convert_invalid_unit():
"""Test exception is thrown for invalid units."""
with pytest.raises(ValueError):
speed_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL)

with pytest.raises(ValueError):
speed_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL)


def test_convert_nonnumeric_value():
"""Test exception is thrown for nonnumeric type."""
with pytest.raises(TypeError):
speed_util.convert("a", SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR)


@pytest.mark.parametrize(
"from_value, from_unit, expected, to_unit",
[
# 5 km/h / 1.609 km/mi = 3.10686 mi/h
(5, SPEED_KILOMETERS_PER_HOUR, 3.10686, SPEED_MILES_PER_HOUR),
# 5 mi/h * 1.609 km/mi = 8.04672 km/h
(5, SPEED_MILES_PER_HOUR, 8.04672, SPEED_KILOMETERS_PER_HOUR),
# 5 in/day * 25.4 mm/in = 127 mm/day
(5, SPEED_INCHES_PER_DAY, 127, SPEED_MILLIMETERS_PER_DAY),
# 5 mm/day / 25.4 mm/in = 0.19685 in/day
(5, SPEED_MILLIMETERS_PER_DAY, 0.19685, SPEED_INCHES_PER_DAY),
# 5 in/hr * 24 hr/day = 3048 mm/day
(5, SPEED_INCHES_PER_HOUR, 3048, SPEED_MILLIMETERS_PER_DAY),
# 5 m/s * 39.3701 in/m * 3600 s/hr = 708661
(5, SPEED_METERS_PER_SECOND, 708661, SPEED_INCHES_PER_HOUR),
# 5000 in/hr / 39.3701 in/m / 3600 s/hr = 0.03528 m/s
(5000, SPEED_INCHES_PER_HOUR, 0.03528, SPEED_METERS_PER_SECOND),
],
)
def test_convert_different_units(from_value, from_unit, expected, to_unit):
"""Test conversion between units."""
assert speed_util.convert(from_value, from_unit, to_unit) == pytest.approx(
expected, rel=1e-4
)