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:
Abílio Costa 2020-02-14 17:04:41 +00:00 committed by GitHub
parent 9eb0415234
commit e6148d223a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 25 deletions

View file

@ -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,

View file

@ -8,7 +8,8 @@
"data": {
"name": "Name",
"latitude": "Latitude",
"longitude": "Longitude"
"longitude": "Longitude",
"mode": "Mode"
}
}
},

View file

@ -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

View file

@ -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"