Allow hourly forecast in IPMA (#30979)
* update ipma component for pyipma 2.0 * fix wind speed; refactor forecast * update requirements*.txt * fix tests; update CODEOWNERS; update pyipma to 2.0.1 * minor changes as suggested in PR * make lint happy * fix mocking coroutines * restore old unique id * fix station lat/lon; update pyipma version * add hourly forecast option to IPMA * add forecast tests * use for instead of lambda
This commit is contained in:
parent
9eb0415234
commit
e6148d223a
4 changed files with 139 additions and 25 deletions
|
@ -2,10 +2,11 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE, CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import DOMAIN, HOME_LOCATION_NAME
|
||||
from .weather import FORECAST_MODE
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
|
@ -49,6 +50,7 @@ class IpmaFlowHandler(config_entries.ConfigFlow):
|
|||
vol.Required(CONF_NAME, default=name): str,
|
||||
vol.Required(CONF_LATITUDE, default=latitude): cv.latitude,
|
||||
vol.Required(CONF_LONGITUDE, default=longitude): cv.longitude,
|
||||
vol.Required(CONF_MODE, default="daily"): vol.In(FORECAST_MODE),
|
||||
}
|
||||
),
|
||||
errors=self._errors,
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
"data": {
|
||||
"name": "Name",
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude"
|
||||
"longitude": "Longitude",
|
||||
"mode": "Mode"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -13,13 +13,22 @@ from homeassistant.components.weather import (
|
|||
ATTR_FORECAST_TEMP,
|
||||
ATTR_FORECAST_TEMP_LOW,
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_FORECAST_WIND_SPEED,
|
||||
PLATFORM_SCHEMA,
|
||||
WeatherEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS
|
||||
from homeassistant.const import (
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
CONF_MODE,
|
||||
CONF_NAME,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.util.dt import now, parse_datetime
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -44,11 +53,14 @@ CONDITION_CLASSES = {
|
|||
"exceptional": [],
|
||||
}
|
||||
|
||||
FORECAST_MODE = ["hourly", "daily"]
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_LATITUDE): cv.latitude,
|
||||
vol.Optional(CONF_LONGITUDE): cv.longitude,
|
||||
vol.Optional(CONF_MODE, default="daily"): vol.In(FORECAST_MODE),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -96,10 +108,12 @@ async def async_get_location(hass, api, latitude, longitude):
|
|||
location = await Location.get(api, float(latitude), float(longitude))
|
||||
|
||||
_LOGGER.debug(
|
||||
"Initializing for coordinates %s, %s -> station %s",
|
||||
"Initializing for coordinates %s, %s -> station %s (%d, %d)",
|
||||
latitude,
|
||||
longitude,
|
||||
location.station,
|
||||
location.id_station,
|
||||
location.global_id_local,
|
||||
)
|
||||
|
||||
return location
|
||||
|
@ -112,6 +126,7 @@ class IPMAWeather(WeatherEntity):
|
|||
"""Initialise the platform with a data instance and station name."""
|
||||
self._api = api
|
||||
self._location_name = config.get(CONF_NAME, location.name)
|
||||
self._mode = config.get(CONF_MODE)
|
||||
self._location = location
|
||||
self._observation = None
|
||||
self._forecast = None
|
||||
|
@ -129,7 +144,7 @@ class IPMAWeather(WeatherEntity):
|
|||
_LOGGER.warning("Could not update weather observation")
|
||||
|
||||
if new_forecast:
|
||||
self._forecast = [f for f in new_forecast if f.forecasted_hours == 24]
|
||||
self._forecast = new_forecast
|
||||
else:
|
||||
_LOGGER.warning("Could not update weather forecast")
|
||||
|
||||
|
@ -220,22 +235,57 @@ class IPMAWeather(WeatherEntity):
|
|||
if not self._forecast:
|
||||
return []
|
||||
|
||||
fcdata_out = [
|
||||
{
|
||||
ATTR_FORECAST_TIME: data_in.forecast_date,
|
||||
ATTR_FORECAST_CONDITION: next(
|
||||
(
|
||||
k
|
||||
for k, v in CONDITION_CLASSES.items()
|
||||
if int(data_in.weather_type) in v
|
||||
if self._mode == "hourly":
|
||||
forecast_filtered = [
|
||||
x
|
||||
for x in self._forecast
|
||||
if x.forecasted_hours == 1
|
||||
and parse_datetime(x.forecast_date)
|
||||
> (now().utcnow() - timedelta(hours=1))
|
||||
]
|
||||
|
||||
fcdata_out = [
|
||||
{
|
||||
ATTR_FORECAST_TIME: data_in.forecast_date,
|
||||
ATTR_FORECAST_CONDITION: next(
|
||||
(
|
||||
k
|
||||
for k, v in CONDITION_CLASSES.items()
|
||||
if int(data_in.weather_type) in v
|
||||
),
|
||||
None,
|
||||
),
|
||||
None,
|
||||
),
|
||||
ATTR_FORECAST_TEMP_LOW: data_in.min_temperature,
|
||||
ATTR_FORECAST_TEMP: data_in.max_temperature,
|
||||
ATTR_FORECAST_PRECIPITATION: data_in.precipitation_probability,
|
||||
}
|
||||
for data_in in self._forecast
|
||||
]
|
||||
ATTR_FORECAST_TEMP: float(data_in.feels_like_temperature),
|
||||
ATTR_FORECAST_PRECIPITATION: (
|
||||
data_in.precipitation_probability
|
||||
if float(data_in.precipitation_probability) >= 0
|
||||
else None
|
||||
),
|
||||
ATTR_FORECAST_WIND_SPEED: data_in.wind_strength,
|
||||
ATTR_FORECAST_WIND_BEARING: data_in.wind_direction,
|
||||
}
|
||||
for data_in in forecast_filtered
|
||||
]
|
||||
else:
|
||||
forecast_filtered = [f for f in self._forecast if f.forecasted_hours == 24]
|
||||
fcdata_out = [
|
||||
{
|
||||
ATTR_FORECAST_TIME: data_in.forecast_date,
|
||||
ATTR_FORECAST_CONDITION: next(
|
||||
(
|
||||
k
|
||||
for k, v in CONDITION_CLASSES.items()
|
||||
if int(data_in.weather_type) in v
|
||||
),
|
||||
None,
|
||||
),
|
||||
ATTR_FORECAST_TEMP_LOW: data_in.min_temperature,
|
||||
ATTR_FORECAST_TEMP: data_in.max_temperature,
|
||||
ATTR_FORECAST_PRECIPITATION: data_in.precipitation_probability,
|
||||
ATTR_FORECAST_WIND_SPEED: data_in.wind_strength,
|
||||
ATTR_FORECAST_WIND_BEARING: data_in.wind_direction,
|
||||
}
|
||||
for data_in in forecast_filtered
|
||||
]
|
||||
|
||||
return fcdata_out
|
||||
|
|
|
@ -4,6 +4,14 @@ from unittest.mock import patch
|
|||
|
||||
from homeassistant.components import weather
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_FORECAST,
|
||||
ATTR_FORECAST_CONDITION,
|
||||
ATTR_FORECAST_PRECIPITATION,
|
||||
ATTR_FORECAST_TEMP,
|
||||
ATTR_FORECAST_TEMP_LOW,
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_FORECAST_WIND_SPEED,
|
||||
ATTR_WEATHER_HUMIDITY,
|
||||
ATTR_WEATHER_PRESSURE,
|
||||
ATTR_WEATHER_TEMPERATURE,
|
||||
|
@ -12,6 +20,7 @@ from homeassistant.components.weather import (
|
|||
DOMAIN as WEATHER_DOMAIN,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.dt import now
|
||||
|
||||
from tests.common import MockConfigEntry, mock_coro
|
||||
|
||||
|
@ -71,16 +80,16 @@ class MockLocation:
|
|||
"2020-01-15T07:51:00",
|
||||
9,
|
||||
"S",
|
||||
None,
|
||||
"10",
|
||||
),
|
||||
Forecast(
|
||||
"7.7",
|
||||
"2020-01-15T02:00:00",
|
||||
now().utcnow().strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
1,
|
||||
"86.9",
|
||||
None,
|
||||
None,
|
||||
"-99.0",
|
||||
"80.0",
|
||||
10.6,
|
||||
"2020-01-15T07:51:00",
|
||||
10,
|
||||
|
@ -122,7 +131,9 @@ async def test_setup_configuration(hass):
|
|||
return_value=mock_coro(MockLocation()),
|
||||
):
|
||||
assert await async_setup_component(
|
||||
hass, weather.DOMAIN, {"weather": {"name": "HomeTown", "platform": "ipma"}}
|
||||
hass,
|
||||
weather.DOMAIN,
|
||||
{"weather": {"name": "HomeTown", "platform": "ipma", "mode": "hourly"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -158,3 +169,53 @@ async def test_setup_config_flow(hass):
|
|||
assert data.get(ATTR_WEATHER_WIND_SPEED) == 3.94
|
||||
assert data.get(ATTR_WEATHER_WIND_BEARING) == "NW"
|
||||
assert state.attributes.get("friendly_name") == "HomeTown"
|
||||
|
||||
|
||||
async def test_daily_forecast(hass):
|
||||
"""Test for successfully getting daily forecast."""
|
||||
with patch(
|
||||
"homeassistant.components.ipma.weather.async_get_location",
|
||||
return_value=mock_coro(MockLocation()),
|
||||
):
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
weather.DOMAIN,
|
||||
{"weather": {"name": "HomeTown", "platform": "ipma", "mode": "daily"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("weather.hometown")
|
||||
assert state.state == "rainy"
|
||||
|
||||
forecast = state.attributes.get(ATTR_FORECAST)[0]
|
||||
assert forecast.get(ATTR_FORECAST_TIME) == "2020-01-15T00:00:00"
|
||||
assert forecast.get(ATTR_FORECAST_CONDITION) == "rainy"
|
||||
assert forecast.get(ATTR_FORECAST_TEMP) == 16.2
|
||||
assert forecast.get(ATTR_FORECAST_TEMP_LOW) == 10.6
|
||||
assert forecast.get(ATTR_FORECAST_PRECIPITATION) == "100.0"
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == "10"
|
||||
assert forecast.get(ATTR_FORECAST_WIND_BEARING) == "S"
|
||||
|
||||
|
||||
async def test_hourly_forecast(hass):
|
||||
"""Test for successfully getting daily forecast."""
|
||||
with patch(
|
||||
"homeassistant.components.ipma.weather.async_get_location",
|
||||
return_value=mock_coro(MockLocation()),
|
||||
):
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
weather.DOMAIN,
|
||||
{"weather": {"name": "HomeTown", "platform": "ipma", "mode": "hourly"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("weather.hometown")
|
||||
assert state.state == "rainy"
|
||||
|
||||
forecast = state.attributes.get(ATTR_FORECAST)[0]
|
||||
assert forecast.get(ATTR_FORECAST_CONDITION) == "rainy"
|
||||
assert forecast.get(ATTR_FORECAST_TEMP) == 7.7
|
||||
assert forecast.get(ATTR_FORECAST_PRECIPITATION) == "80.0"
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == "32.7"
|
||||
assert forecast.get(ATTR_FORECAST_WIND_BEARING) == "S"
|
||||
|
|
Loading…
Add table
Reference in a new issue