Add humidity to NWS forecast (#95575)

* Add humidity to NWS forecast to address https://github.com/home-assistant/core/issues/95572

* Use pynws 1.5.0 enhancements for probabilityOfPrecipitation, dewpoint, and relativeHumidity.

* Update requirements to match pynws version

* test for clear night

* update docstring

---------

Co-authored-by: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com>
This commit is contained in:
lymanepp 2023-07-06 17:05:46 -04:00 committed by GitHub
parent e94726ec84
commit 6c4b5291e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 65 additions and 20 deletions

View File

@ -7,5 +7,5 @@
"iot_class": "cloud_polling",
"loggers": ["metar", "pynws"],
"quality_scale": "platinum",
"requirements": ["pynws==1.4.1"]
"requirements": ["pynws==1.5.0"]
}

View File

@ -8,6 +8,8 @@ from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_SUNNY,
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_HUMIDITY,
ATTR_FORECAST_NATIVE_DEW_POINT,
ATTR_FORECAST_NATIVE_TEMP,
ATTR_FORECAST_NATIVE_WIND_SPEED,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
@ -52,16 +54,13 @@ from .const import (
PARALLEL_UPDATES = 0
def convert_condition(
time: str, weather: tuple[tuple[str, int | None], ...]
) -> tuple[str, int | None]:
def convert_condition(time: str, weather: tuple[tuple[str, int | None], ...]) -> str:
"""Convert NWS codes to HA condition.
Choose first condition in CONDITION_CLASSES that exists in weather code.
If no match is found, return first condition from NWS
"""
conditions: list[str] = [w[0] for w in weather]
prec_probs = [w[1] or 0 for w in weather]
# Choose condition with highest priority.
cond = next(
@ -75,10 +74,10 @@ def convert_condition(
if cond == "clear":
if time == "day":
return ATTR_CONDITION_SUNNY, max(prec_probs)
return ATTR_CONDITION_SUNNY
if time == "night":
return ATTR_CONDITION_CLEAR_NIGHT, max(prec_probs)
return cond, max(prec_probs)
return ATTR_CONDITION_CLEAR_NIGHT
return cond
async def async_setup_entry(
@ -219,8 +218,7 @@ class NWSWeather(WeatherEntity):
time = self.observation.get("iconTime")
if weather:
cond, _ = convert_condition(time, weather)
return cond
return convert_condition(time, weather)
return None
@property
@ -256,16 +254,27 @@ class NWSWeather(WeatherEntity):
else:
data[ATTR_FORECAST_NATIVE_TEMP] = None
data[ATTR_FORECAST_PRECIPITATION_PROBABILITY] = forecast_entry.get(
"probabilityOfPrecipitation"
)
if (dewp := forecast_entry.get("dewpoint")) is not None:
data[ATTR_FORECAST_NATIVE_DEW_POINT] = TemperatureConverter.convert(
dewp, UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS
)
else:
data[ATTR_FORECAST_NATIVE_DEW_POINT] = None
data[ATTR_FORECAST_HUMIDITY] = forecast_entry.get("relativeHumidity")
if self.mode == DAYNIGHT:
data[ATTR_FORECAST_DAYTIME] = forecast_entry.get("isDaytime")
time = forecast_entry.get("iconTime")
weather = forecast_entry.get("iconWeather")
if time and weather:
cond, precip = convert_condition(time, weather)
else:
cond, precip = None, None
data[ATTR_FORECAST_CONDITION] = cond
data[ATTR_FORECAST_PRECIPITATION_PROBABILITY] = precip
data[ATTR_FORECAST_CONDITION] = (
convert_condition(time, weather) if time and weather else None
)
data[ATTR_FORECAST_WIND_BEARING] = forecast_entry.get("windBearing")
wind_speed = forecast_entry.get("windSpeedAvg")

View File

@ -1861,7 +1861,7 @@ pynuki==1.6.2
pynut2==2.1.2
# homeassistant.components.nws
pynws==1.4.1
pynws==1.5.0
# homeassistant.components.nx584
pynx584==0.5

View File

@ -1377,7 +1377,7 @@ pynuki==1.6.2
pynut2==2.1.2
# homeassistant.components.nws
pynws==1.4.1
pynws==1.5.0
# homeassistant.components.nx584
pynx584==0.5

View File

@ -3,6 +3,8 @@ from homeassistant.components.nws.const import CONF_STATION
from homeassistant.components.weather import (
ATTR_CONDITION_LIGHTNING_RAINY,
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_DEW_POINT,
ATTR_FORECAST_HUMIDITY,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TIME,
@ -59,6 +61,9 @@ DEFAULT_OBSERVATION = {
"windGust": 20,
}
CLEAR_NIGHT_OBSERVATION = DEFAULT_OBSERVATION.copy()
CLEAR_NIGHT_OBSERVATION["iconTime"] = "night"
SENSOR_EXPECTED_OBSERVATION_METRIC = {
"dewpoint": "5",
"temperature": "10",
@ -183,6 +188,9 @@ DEFAULT_FORECAST = [
"timestamp": "2019-08-12T23:53:00+00:00",
"iconTime": "night",
"iconWeather": (("lightning-rainy", 40), ("lightning-rainy", 90)),
"probabilityOfPrecipitation": 89,
"dewpoint": 4,
"relativeHumidity": 75,
},
]
@ -192,7 +200,9 @@ EXPECTED_FORECAST_IMPERIAL = {
ATTR_FORECAST_TEMP: 10,
ATTR_FORECAST_WIND_SPEED: 10,
ATTR_FORECAST_WIND_BEARING: 180,
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 90,
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 89,
ATTR_FORECAST_DEW_POINT: 4,
ATTR_FORECAST_HUMIDITY: 75,
}
EXPECTED_FORECAST_METRIC = {
@ -211,7 +221,14 @@ EXPECTED_FORECAST_METRIC = {
2,
),
ATTR_FORECAST_WIND_BEARING: 180,
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 90,
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 89,
ATTR_FORECAST_DEW_POINT: round(
TemperatureConverter.convert(
4, UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS
),
1,
),
ATTR_FORECAST_HUMIDITY: 75,
}
NONE_FORECAST = [{key: None for key in DEFAULT_FORECAST[0]}]

View File

@ -7,6 +7,7 @@ import pytest
from homeassistant.components import nws
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_SUNNY,
ATTR_FORECAST,
DOMAIN as WEATHER_DOMAIN,
@ -19,6 +20,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.util.unit_system import METRIC_SYSTEM, US_CUSTOMARY_SYSTEM
from .const import (
CLEAR_NIGHT_OBSERVATION,
EXPECTED_FORECAST_IMPERIAL,
EXPECTED_FORECAST_METRIC,
NONE_FORECAST,
@ -97,6 +99,23 @@ async def test_imperial_metric(
assert forecast[0].get(key) == value
async def test_night_clear(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None:
"""Test with clear-night in observation."""
instance = mock_simple_nws.return_value
instance.observation = CLEAR_NIGHT_OBSERVATION
entry = MockConfigEntry(
domain=nws.DOMAIN,
data=NWS_CONFIG,
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.abc_daynight")
assert state.state == ATTR_CONDITION_CLEAR_NIGHT
async def test_none_values(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None:
"""Test with none values in observation and forecast dicts."""
instance = mock_simple_nws.return_value