diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 0efaea949e1..5002cf47bb9 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -31,6 +31,7 @@ from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM from .const import ( ATTR_WEATHER_APPARENT_TEMPERATURE, + ATTR_WEATHER_DEW_POINT, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_OZONE, ATTR_WEATHER_PRECIPITATION_UNIT, @@ -84,6 +85,8 @@ ATTR_FORECAST_TIME: Final = "datetime" ATTR_FORECAST_WIND_BEARING: Final = "wind_bearing" 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" +ATTR_FORECAST_DEW_POINT: Final = "dew_point" ENTITY_ID_FORMAT = DOMAIN + ".{}" @@ -132,6 +135,7 @@ class Forecast(TypedDict, total=False): wind_bearing: float | str | None native_wind_speed: float | None wind_speed: None + native_dew_point: float | None async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: @@ -210,6 +214,7 @@ class WeatherEntity(Entity): _attr_native_precipitation_unit: str | None = None _attr_native_wind_speed: float | None = None _attr_native_wind_speed_unit: str | None = None + _attr_native_dew_point: float | None = None _weather_option_temperature_unit: str | None = None _weather_option_pressure_unit: str | None = None @@ -306,6 +311,11 @@ class WeatherEntity(Entity): return self._attr_native_temperature_unit + @property + def native_dew_point(self) -> float | None: + """Return the dew point temperature in native units.""" + return self._attr_native_dew_point + @final @property def temperature_unit(self) -> str | None: @@ -623,6 +633,20 @@ class WeatherEntity(Entity): except (TypeError, ValueError): data[ATTR_WEATHER_APPARENT_TEMPERATURE] = apparent_temperature + if (dew_point := self.native_dew_point) is not None: + from_unit = self.native_temperature_unit or self._default_temperature_unit + to_unit = self._temperature_unit + try: + dew_point_f = float(dew_point) + value_dew_point = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT]( + dew_point_f, from_unit, to_unit + ) + data[ATTR_WEATHER_DEW_POINT] = round_temperature( + value_dew_point, precision + ) + except (TypeError, ValueError): + data[ATTR_WEATHER_DEW_POINT] = dew_point + data[ATTR_WEATHER_TEMPERATURE_UNIT] = self._temperature_unit if (humidity := self.humidity) is not None: @@ -749,6 +773,26 @@ class WeatherEntity(Entity): value_temp_low, precision ) + if ( + forecast_dew_point := forecast_entry.pop( + ATTR_FORECAST_NATIVE_DEW_POINT, + None, + ) + ) is not None: + with suppress(TypeError, ValueError): + forecast_dew_point_f = float(forecast_dew_point) + value_dew_point = UNIT_CONVERSIONS[ + ATTR_WEATHER_TEMPERATURE_UNIT + ]( + forecast_dew_point_f, + from_temp_unit, + to_temp_unit, + ) + + forecast_entry[ATTR_FORECAST_DEW_POINT] = round_temperature( + value_dew_point, precision + ) + if ( forecast_pressure := forecast_entry.pop( ATTR_FORECAST_NATIVE_PRESSURE, diff --git a/homeassistant/components/weather/const.py b/homeassistant/components/weather/const.py index 95094850ff2..980a6ced2d8 100644 --- a/homeassistant/components/weather/const.py +++ b/homeassistant/components/weather/const.py @@ -20,6 +20,7 @@ from homeassistant.util.unit_conversion import ( ATTR_WEATHER_HUMIDITY = "humidity" ATTR_WEATHER_OZONE = "ozone" +ATTR_WEATHER_DEW_POINT = "dew_point" ATTR_WEATHER_PRESSURE = "pressure" ATTR_WEATHER_PRESSURE_UNIT = "pressure_unit" ATTR_WEATHER_APPARENT_TEMPERATURE = "apparent_temperature" diff --git a/homeassistant/components/weather/strings.json b/homeassistant/components/weather/strings.json index e319d42c943..7f05594d2dd 100644 --- a/homeassistant/components/weather/strings.json +++ b/homeassistant/components/weather/strings.json @@ -42,6 +42,9 @@ "apparent_temperature": { "name": "Apparent temperature" }, + "dew_point": { + "name": "Dew point temperature" + }, "temperature": { "name": "Temperature" }, diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index 6ac27f1c2c9..d22bf2c64be 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -7,6 +7,7 @@ from homeassistant.components.weather import ( ATTR_CONDITION_SUNNY, ATTR_FORECAST, ATTR_FORECAST_APPARENT_TEMP, + ATTR_FORECAST_DEW_POINT, ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_PRESSURE, ATTR_FORECAST_TEMP, @@ -30,6 +31,7 @@ from homeassistant.components.weather import ( WeatherEntity, round_temperature, ) +from homeassistant.components.weather.const import ATTR_WEATHER_DEW_POINT from homeassistant.const import ( ATTR_FRIENDLY_NAME, PRECISION_HALVES, @@ -66,6 +68,7 @@ class MockWeatherEntity(WeatherEntity): self._attr_native_pressure_unit = UnitOfPressure.HPA self._attr_native_temperature = 20 self._attr_native_apparent_temperature = 25 + self._attr_native_dew_point = 2 self._attr_native_temperature_unit = UnitOfTemperature.CELSIUS self._attr_native_visibility = 30 self._attr_native_visibility_unit = UnitOfLength.KILOMETERS @@ -76,6 +79,7 @@ class MockWeatherEntity(WeatherEntity): datetime=datetime(2022, 6, 20, 20, 00, 00), native_precipitation=1, native_temperature=20, + native_dew_point=2, ) ] @@ -89,6 +93,7 @@ class MockWeatherEntityPrecision(WeatherEntity): self._attr_condition = ATTR_CONDITION_SUNNY self._attr_native_temperature = 20.3 self._attr_native_apparent_temperature = 25.3 + self._attr_native_dew_point = 2.3 self._attr_native_temperature_unit = UnitOfTemperature.CELSIUS self._attr_precision = PRECISION_HALVES @@ -158,16 +163,22 @@ async def test_temperature( hass.config.units = unit_system native_value = 38 apparent_native_value = 45 + dew_point_native_value = 32 state_value = TemperatureConverter.convert(native_value, native_unit, state_unit) apparent_state_value = TemperatureConverter.convert( apparent_native_value, native_unit, state_unit ) + state_value = TemperatureConverter.convert(native_value, native_unit, state_unit) + dew_point_state_value = TemperatureConverter.convert( + dew_point_native_value, native_unit, state_unit + ) entity0 = await create_entity( hass, native_temperature=native_value, native_temperature_unit=native_unit, native_apparent_temperature=apparent_native_value, + native_dew_point=dew_point_native_value, ) state = hass.states.get(entity0.entity_id) @@ -175,17 +186,24 @@ async def test_temperature( expected = state_value apparent_expected = apparent_state_value + dew_point_expected = dew_point_state_value assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == pytest.approx( expected, rel=0.1 ) assert float(state.attributes[ATTR_WEATHER_APPARENT_TEMPERATURE]) == pytest.approx( apparent_expected, rel=0.1 ) + assert float(state.attributes[ATTR_WEATHER_DEW_POINT]) == pytest.approx( + dew_point_expected, rel=0.1 + ) assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit assert float(forecast[ATTR_FORECAST_TEMP]) == pytest.approx(expected, rel=0.1) assert float(forecast[ATTR_FORECAST_APPARENT_TEMP]) == pytest.approx( apparent_expected, rel=0.1 ) + assert float(forecast[ATTR_FORECAST_DEW_POINT]) == pytest.approx( + dew_point_expected, rel=0.1 + ) assert float(forecast[ATTR_FORECAST_TEMP_LOW]) == pytest.approx(expected, rel=0.1) @@ -207,21 +225,33 @@ async def test_temperature_no_unit( """Test temperature when the entity does not declare a native unit.""" hass.config.units = unit_system native_value = 38 + dew_point_native_value = 32 state_value = native_value + dew_point_state_value = dew_point_native_value entity0 = await create_entity( - hass, native_temperature=native_value, native_temperature_unit=native_unit + hass, + native_temperature=native_value, + native_temperature_unit=native_unit, + native_dew_point=dew_point_native_value, ) state = hass.states.get(entity0.entity_id) forecast = state.attributes[ATTR_FORECAST][0] expected = state_value + dew_point_expected = dew_point_state_value assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == pytest.approx( expected, rel=0.1 ) + assert float(state.attributes[ATTR_WEATHER_DEW_POINT]) == pytest.approx( + dew_point_expected, rel=0.1 + ) assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit assert float(forecast[ATTR_FORECAST_TEMP]) == pytest.approx(expected, rel=0.1) + assert float(forecast[ATTR_FORECAST_DEW_POINT]) == pytest.approx( + dew_point_expected, rel=0.1 + ) assert float(forecast[ATTR_FORECAST_TEMP_LOW]) == pytest.approx(expected, rel=0.1) @@ -943,7 +973,9 @@ async def test_precision_for_temperature(hass: HomeAssistant) -> None: assert weather.condition == ATTR_CONDITION_SUNNY assert weather.native_temperature == 20.3 + assert weather.native_dew_point == 2.3 assert weather._temperature_unit == UnitOfTemperature.CELSIUS assert weather.precision == PRECISION_HALVES assert weather.state_attributes[ATTR_WEATHER_TEMPERATURE] == 20.5 + assert weather.state_attributes[ATTR_WEATHER_DEW_POINT] == 2.5 diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index df9a3faea3f..121d43c2996 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -6,6 +6,7 @@ from __future__ import annotations from homeassistant.components.weather import ( ATTR_FORECAST_NATIVE_APPARENT_TEMP, + ATTR_FORECAST_NATIVE_DEW_POINT, ATTR_FORECAST_NATIVE_PRECIPITATION, ATTR_FORECAST_NATIVE_PRESSURE, ATTR_FORECAST_NATIVE_TEMP, @@ -52,6 +53,11 @@ class MockWeather(MockEntity, WeatherEntity): """Return the platform apparent temperature.""" return self._handle("native_apparent_temperature") + @property + def native_dew_point(self) -> float | None: + """Return the platform dewpoint temperature.""" + return self._handle("native_dew_point") + @property def native_temperature_unit(self) -> str | None: """Return the unit of measurement for temperature.""" @@ -203,6 +209,7 @@ class MockWeatherMockForecast(MockWeather): ATTR_FORECAST_NATIVE_TEMP: self.native_temperature, ATTR_FORECAST_NATIVE_APPARENT_TEMP: self.native_apparent_temperature, ATTR_FORECAST_NATIVE_TEMP_LOW: self.native_temperature, + ATTR_FORECAST_NATIVE_DEW_POINT: self.native_dew_point, ATTR_FORECAST_NATIVE_PRESSURE: self.native_pressure, ATTR_FORECAST_NATIVE_WIND_SPEED: self.native_wind_speed, ATTR_FORECAST_WIND_BEARING: self.wind_bearing,