diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 8640e90c639dc..f24fdc35c5d1a 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -43,6 +43,7 @@ ATTR_WEATHER_VISIBILITY, ATTR_WEATHER_VISIBILITY_UNIT, ATTR_WEATHER_WIND_BEARING, + ATTR_WEATHER_WIND_GUST_SPEED, ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED_UNIT, DOMAIN, @@ -85,6 +86,8 @@ ATTR_FORECAST_TEMP_LOW: Final = "templow" ATTR_FORECAST_TIME: Final = "datetime" ATTR_FORECAST_WIND_BEARING: Final = "wind_bearing" +ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: Final = "native_wind_gust_speed" +ATTR_FORECAST_WIND_GUST_SPEED: Final = "wind_gust_speed" ATTR_FORECAST_NATIVE_WIND_SPEED: Final = "native_wind_speed" ATTR_FORECAST_WIND_SPEED: Final = "wind_speed" ATTR_FORECAST_NATIVE_DEW_POINT: Final = "native_dew_point" @@ -138,6 +141,7 @@ class Forecast(TypedDict, total=False): native_templow: float | None templow: None wind_bearing: float | str | None + native_wind_gust_speed: float | None native_wind_speed: float | None wind_speed: None native_dew_point: float | None @@ -218,6 +222,7 @@ class WeatherEntity(Entity): _attr_native_visibility: float | None = None _attr_native_visibility_unit: str | None = None _attr_native_precipitation_unit: str | None = None + _attr_native_wind_gust_speed: float | None = None _attr_native_wind_speed: float | None = None _attr_native_wind_speed_unit: str | None = None _attr_native_dew_point: float | None = None @@ -418,6 +423,11 @@ def humidity(self) -> float | None: """Return the humidity in native units.""" return self._attr_humidity + @property + def native_wind_gust_speed(self) -> float | None: + """Return the wind gust speed in native units.""" + return self._attr_native_wind_gust_speed + @final @property def wind_speed(self) -> float | None: @@ -686,6 +696,20 @@ def state_attributes(self) -> dict[str, Any]: # noqa: C901 if (wind_bearing := self.wind_bearing) is not None: data[ATTR_WEATHER_WIND_BEARING] = wind_bearing + if (wind_gust_speed := self.native_wind_gust_speed) is not None: + from_unit = self.native_wind_speed_unit or self._default_wind_speed_unit + to_unit = self._wind_speed_unit + try: + wind_gust_speed_f = float(wind_gust_speed) + value_wind_gust_speed = UNIT_CONVERSIONS[ATTR_WEATHER_WIND_SPEED_UNIT]( + wind_gust_speed_f, from_unit, to_unit + ) + data[ATTR_WEATHER_WIND_GUST_SPEED] = round( + value_wind_gust_speed, ROUNDING_PRECISION + ) + except (TypeError, ValueError): + data[ATTR_WEATHER_WIND_GUST_SPEED] = wind_gust_speed + if (wind_speed := self.native_wind_speed) is not None: from_unit = self.native_wind_speed_unit or self._default_wind_speed_unit to_unit = self._wind_speed_unit @@ -828,6 +852,27 @@ def state_attributes(self) -> dict[str, Any]: # noqa: C901 ROUNDING_PRECISION, ) + if ( + forecast_wind_gust_speed := forecast_entry.pop( + ATTR_FORECAST_NATIVE_WIND_GUST_SPEED, + None, + ) + ) is not None: + from_wind_speed_unit = ( + self.native_wind_speed_unit or self._default_wind_speed_unit + ) + to_wind_speed_unit = self._wind_speed_unit + with suppress(TypeError, ValueError): + forecast_wind_gust_speed_f = float(forecast_wind_gust_speed) + forecast_entry[ATTR_FORECAST_WIND_GUST_SPEED] = round( + UNIT_CONVERSIONS[ATTR_WEATHER_WIND_SPEED_UNIT]( + forecast_wind_gust_speed_f, + from_wind_speed_unit, + to_wind_speed_unit, + ), + ROUNDING_PRECISION, + ) + if ( forecast_wind_speed := forecast_entry.pop( ATTR_FORECAST_NATIVE_WIND_SPEED, diff --git a/homeassistant/components/weather/const.py b/homeassistant/components/weather/const.py index a0b3ad587508d..b995ce2b7299e 100644 --- a/homeassistant/components/weather/const.py +++ b/homeassistant/components/weather/const.py @@ -29,6 +29,7 @@ ATTR_WEATHER_VISIBILITY = "visibility" ATTR_WEATHER_VISIBILITY_UNIT = "visibility_unit" ATTR_WEATHER_WIND_BEARING = "wind_bearing" +ATTR_WEATHER_WIND_GUST_SPEED = "wind_gust_speed" ATTR_WEATHER_WIND_SPEED = "wind_speed" ATTR_WEATHER_WIND_SPEED_UNIT = "wind_speed_unit" ATTR_WEATHER_PRECIPITATION_UNIT = "precipitation_unit" diff --git a/homeassistant/components/weather/strings.json b/homeassistant/components/weather/strings.json index 33507191e015f..53eca9c7f9193 100644 --- a/homeassistant/components/weather/strings.json +++ b/homeassistant/components/weather/strings.json @@ -63,6 +63,9 @@ "wind_bearing": { "name": "Wind bearing" }, + "wind_gust_speed": { + "name": "Wind gust speed" + }, "wind_speed": { "name": "Wind speed" }, diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index 37f0fd6232810..5ed6a02f24b9d 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -14,6 +14,7 @@ ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_GUST_SPEED, ATTR_FORECAST_WIND_SPEED, ATTR_WEATHER_APPARENT_TEMPERATURE, ATTR_WEATHER_OZONE, @@ -25,6 +26,7 @@ ATTR_WEATHER_VISIBILITY, ATTR_WEATHER_VISIBILITY_UNIT, ATTR_WEATHER_WIND_BEARING, + ATTR_WEATHER_WIND_GUST_SPEED, ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED_UNIT, ROUNDING_PRECISION, @@ -77,6 +79,7 @@ def __init__(self) -> None: self._attr_native_temperature_unit = UnitOfTemperature.CELSIUS self._attr_native_visibility = 30 self._attr_native_visibility_unit = UnitOfLength.KILOMETERS + self._attr_native_wind_gust_speed = 10 self._attr_native_wind_speed = 3 self._attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND self._attr_forecast = [ @@ -373,6 +376,49 @@ async def test_wind_speed( ) +@pytest.mark.parametrize( + "native_unit", + ( + UnitOfSpeed.MILES_PER_HOUR, + UnitOfSpeed.KILOMETERS_PER_HOUR, + UnitOfSpeed.METERS_PER_SECOND, + ), +) +@pytest.mark.parametrize( + ("state_unit", "unit_system"), + ( + (UnitOfSpeed.KILOMETERS_PER_HOUR, METRIC_SYSTEM), + (UnitOfSpeed.MILES_PER_HOUR, US_CUSTOMARY_SYSTEM), + ), +) +async def test_wind_gust_speed( + hass: HomeAssistant, + enable_custom_integrations: None, + native_unit: str, + state_unit: str, + unit_system, +) -> None: + """Test wind speed.""" + hass.config.units = unit_system + native_value = 10 + state_value = SpeedConverter.convert(native_value, native_unit, state_unit) + + entity0 = await create_entity( + hass, native_wind_gust_speed=native_value, native_wind_speed_unit=native_unit + ) + + state = hass.states.get(entity0.entity_id) + forecast = state.attributes[ATTR_FORECAST][0] + + expected = state_value + assert float(state.attributes[ATTR_WEATHER_WIND_GUST_SPEED]) == pytest.approx( + expected, rel=1e-2 + ) + assert float(forecast[ATTR_FORECAST_WIND_GUST_SPEED]) == pytest.approx( + expected, rel=1e-2 + ) + + @pytest.mark.parametrize("native_unit", (None,)) @pytest.mark.parametrize( ("state_unit", "unit_system"), diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index e4c7e38066399..a5c49fb92c247 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -13,6 +13,7 @@ ATTR_FORECAST_NATIVE_PRESSURE, ATTR_FORECAST_NATIVE_TEMP, ATTR_FORECAST_NATIVE_TEMP_LOW, + ATTR_FORECAST_NATIVE_WIND_GUST_SPEED, ATTR_FORECAST_NATIVE_WIND_SPEED, ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_PRESSURE, @@ -80,6 +81,11 @@ def humidity(self) -> float | None: """Return the humidity.""" return self._handle("humidity") + @property + def native_wind_gust_speed(self) -> float | None: + """Return the wind speed.""" + return self._handle("native_wind_gust_speed") + @property def native_wind_speed(self) -> float | None: """Return the wind speed.""" @@ -219,6 +225,7 @@ def forecast(self) -> list[Forecast] | None: ATTR_FORECAST_NATIVE_DEW_POINT: self.native_dew_point, ATTR_FORECAST_CLOUD_COVERAGE: self.cloud_coverage, ATTR_FORECAST_NATIVE_PRESSURE: self.native_pressure, + ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: self.native_wind_gust_speed, ATTR_FORECAST_NATIVE_WIND_SPEED: self.native_wind_speed, ATTR_FORECAST_WIND_BEARING: self.wind_bearing, ATTR_FORECAST_NATIVE_PRECIPITATION: self._values.get(