mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Update pyipma to 2.0 (#30746)
* 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
This commit is contained in:
parent
c2df4f56a3
commit
2aff913d9b
@ -168,7 +168,7 @@ homeassistant/components/intent/* @home-assistant/core
|
||||
homeassistant/components/intesishome/* @jnimmo
|
||||
homeassistant/components/ios/* @robbiet480
|
||||
homeassistant/components/iperf3/* @rohankapoorcom
|
||||
homeassistant/components/ipma/* @dgomes
|
||||
homeassistant/components/ipma/* @dgomes @abmantis
|
||||
homeassistant/components/iqvia/* @bachya
|
||||
homeassistant/components/irish_rail_transport/* @ttroy50
|
||||
homeassistant/components/izone/* @Swamp-Ig
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Instituto Português do Mar e Atmosfera (IPMA)",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/ipma",
|
||||
"requirements": ["pyipma==1.2.1"],
|
||||
"requirements": ["pyipma==2.0.2"],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@dgomes"]
|
||||
"codeowners": ["@dgomes", "@abmantis"]
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import async_timeout
|
||||
from pyipma import Station
|
||||
from pyipma.api import IPMA_API
|
||||
from pyipma.location import Location
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
@ -24,8 +25,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTRIBUTION = "Instituto Português do Mar e Atmosfera"
|
||||
|
||||
ATTR_WEATHER_DESCRIPTION = "description"
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30)
|
||||
|
||||
CONDITION_CLASSES = {
|
||||
@ -68,9 +67,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||
return
|
||||
|
||||
station = await async_get_station(hass, latitude, longitude)
|
||||
api = await async_get_api(hass)
|
||||
location = await async_get_location(hass, api, latitude, longitude)
|
||||
|
||||
async_add_entities([IPMAWeather(station, config)], True)
|
||||
async_add_entities([IPMAWeather(location, api, config)], True)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
@ -78,61 +78,71 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
latitude = config_entry.data[CONF_LATITUDE]
|
||||
longitude = config_entry.data[CONF_LONGITUDE]
|
||||
|
||||
station = await async_get_station(hass, latitude, longitude)
|
||||
api = await async_get_api(hass)
|
||||
location = await async_get_location(hass, api, latitude, longitude)
|
||||
|
||||
async_add_entities([IPMAWeather(station, config_entry.data)], True)
|
||||
async_add_entities([IPMAWeather(location, api, config_entry.data)], True)
|
||||
|
||||
|
||||
async def async_get_station(hass, latitude, longitude):
|
||||
"""Retrieve weather station, station name to be used as the entity name."""
|
||||
|
||||
async def async_get_api(hass):
|
||||
"""Get the pyipma api object."""
|
||||
websession = async_get_clientsession(hass)
|
||||
return IPMA_API(websession)
|
||||
|
||||
|
||||
async def async_get_location(hass, api, latitude, longitude):
|
||||
"""Retrieve pyipma location, location name to be used as the entity name."""
|
||||
with async_timeout.timeout(10):
|
||||
station = await Station.get(websession, float(latitude), float(longitude))
|
||||
location = await Location.get(api, float(latitude), float(longitude))
|
||||
|
||||
_LOGGER.debug(
|
||||
"Initializing for coordinates %s, %s -> station %s",
|
||||
latitude,
|
||||
longitude,
|
||||
station.local,
|
||||
location.station,
|
||||
)
|
||||
|
||||
return station
|
||||
return location
|
||||
|
||||
|
||||
class IPMAWeather(WeatherEntity):
|
||||
"""Representation of a weather condition."""
|
||||
|
||||
def __init__(self, station, config):
|
||||
def __init__(self, location: Location, api: IPMA_API, config):
|
||||
"""Initialise the platform with a data instance and station name."""
|
||||
self._station_name = config.get(CONF_NAME, station.local)
|
||||
self._station = station
|
||||
self._condition = None
|
||||
self._api = api
|
||||
self._location_name = config.get(CONF_NAME, location.name)
|
||||
self._location = location
|
||||
self._observation = None
|
||||
self._forecast = None
|
||||
self._description = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
async def async_update(self):
|
||||
"""Update Condition and Forecast."""
|
||||
with async_timeout.timeout(10):
|
||||
_new_condition = await self._station.observation()
|
||||
if _new_condition is None:
|
||||
_LOGGER.warning("Could not update weather conditions")
|
||||
return
|
||||
self._condition = _new_condition
|
||||
new_observation = await self._location.observation(self._api)
|
||||
new_forecast = await self._location.forecast(self._api)
|
||||
|
||||
if new_observation:
|
||||
self._observation = new_observation
|
||||
else:
|
||||
_LOGGER.warning("Could not update weather observation")
|
||||
|
||||
if new_forecast:
|
||||
self._forecast = [f for f in new_forecast if f.forecasted_hours == 24]
|
||||
else:
|
||||
_LOGGER.warning("Could not update weather forecast")
|
||||
|
||||
_LOGGER.debug(
|
||||
"Updating station %s, condition %s",
|
||||
self._station.local,
|
||||
self._condition,
|
||||
"Updated location %s, observation %s",
|
||||
self._location.name,
|
||||
self._observation,
|
||||
)
|
||||
self._forecast = await self._station.forecast()
|
||||
self._description = self._forecast[0].description
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique id."""
|
||||
return f"{self._station.latitude}, {self._station.longitude}"
|
||||
return f"{self._location.station_latitude}, {self._location.station_longitude}"
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
@ -142,7 +152,7 @@ class IPMAWeather(WeatherEntity):
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the station."""
|
||||
return self._station_name
|
||||
return self._location_name
|
||||
|
||||
@property
|
||||
def condition(self):
|
||||
@ -154,7 +164,7 @@ class IPMAWeather(WeatherEntity):
|
||||
(
|
||||
k
|
||||
for k, v in CONDITION_CLASSES.items()
|
||||
if self._forecast[0].idWeatherType in v
|
||||
if self._forecast[0].weather_type in v
|
||||
),
|
||||
None,
|
||||
)
|
||||
@ -162,42 +172,42 @@ class IPMAWeather(WeatherEntity):
|
||||
@property
|
||||
def temperature(self):
|
||||
"""Return the current temperature."""
|
||||
if not self._condition:
|
||||
if not self._observation:
|
||||
return None
|
||||
|
||||
return self._condition.temperature
|
||||
return self._observation.temperature
|
||||
|
||||
@property
|
||||
def pressure(self):
|
||||
"""Return the current pressure."""
|
||||
if not self._condition:
|
||||
if not self._observation:
|
||||
return None
|
||||
|
||||
return self._condition.pressure
|
||||
return self._observation.pressure
|
||||
|
||||
@property
|
||||
def humidity(self):
|
||||
"""Return the name of the sensor."""
|
||||
if not self._condition:
|
||||
if not self._observation:
|
||||
return None
|
||||
|
||||
return self._condition.humidity
|
||||
return self._observation.humidity
|
||||
|
||||
@property
|
||||
def wind_speed(self):
|
||||
"""Return the current windspeed."""
|
||||
if not self._condition:
|
||||
if not self._observation:
|
||||
return None
|
||||
|
||||
return self._condition.windspeed
|
||||
return self._observation.wind_intensity_km
|
||||
|
||||
@property
|
||||
def wind_bearing(self):
|
||||
"""Return the current wind bearing (degrees)."""
|
||||
if not self._condition:
|
||||
if not self._observation:
|
||||
return None
|
||||
|
||||
return self._condition.winddirection
|
||||
return self._observation.wind_direction
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
@ -207,33 +217,25 @@ class IPMAWeather(WeatherEntity):
|
||||
@property
|
||||
def forecast(self):
|
||||
"""Return the forecast array."""
|
||||
if self._forecast:
|
||||
fcdata_out = []
|
||||
for data_in in self._forecast:
|
||||
data_out = {}
|
||||
data_out[ATTR_FORECAST_TIME] = data_in.forecastDate
|
||||
data_out[ATTR_FORECAST_CONDITION] = next(
|
||||
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.idWeatherType) in v
|
||||
if int(data_in.weather_type) in v
|
||||
),
|
||||
None,
|
||||
)
|
||||
data_out[ATTR_FORECAST_TEMP_LOW] = data_in.tMin
|
||||
data_out[ATTR_FORECAST_TEMP] = data_in.tMax
|
||||
data_out[ATTR_FORECAST_PRECIPITATION] = data_in.precipitaProb
|
||||
),
|
||||
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
|
||||
]
|
||||
|
||||
fcdata_out.append(data_out)
|
||||
|
||||
return fcdata_out
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
data = dict()
|
||||
|
||||
if self._description:
|
||||
data[ATTR_WEATHER_DESCRIPTION] = self._description
|
||||
|
||||
return data
|
||||
return fcdata_out
|
||||
|
@ -1297,7 +1297,7 @@ pyicloud==0.9.1
|
||||
pyintesishome==1.6
|
||||
|
||||
# homeassistant.components.ipma
|
||||
pyipma==1.2.1
|
||||
pyipma==2.0.2
|
||||
|
||||
# homeassistant.components.iqvia
|
||||
pyiqvia==0.2.1
|
||||
|
@ -451,7 +451,7 @@ pyhomematic==0.1.63
|
||||
pyicloud==0.9.1
|
||||
|
||||
# homeassistant.components.ipma
|
||||
pyipma==1.2.1
|
||||
pyipma==2.0.2
|
||||
|
||||
# homeassistant.components.iqvia
|
||||
pyiqvia==0.2.1
|
||||
|
@ -18,73 +18,99 @@ from tests.common import MockConfigEntry, mock_coro
|
||||
TEST_CONFIG = {"name": "HomeTown", "latitude": "40.00", "longitude": "-8.00"}
|
||||
|
||||
|
||||
class MockStation:
|
||||
"""Mock Station from pyipma."""
|
||||
class MockLocation:
|
||||
"""Mock Location from pyipma."""
|
||||
|
||||
async def observation(self):
|
||||
async def observation(self, api):
|
||||
"""Mock Observation."""
|
||||
Observation = namedtuple(
|
||||
"Observation",
|
||||
[
|
||||
"temperature",
|
||||
"accumulated_precipitation",
|
||||
"humidity",
|
||||
"windspeed",
|
||||
"winddirection",
|
||||
"precipitation",
|
||||
"pressure",
|
||||
"description",
|
||||
"radiation",
|
||||
"temperature",
|
||||
"wind_direction",
|
||||
"wind_intensity_km",
|
||||
],
|
||||
)
|
||||
|
||||
return Observation(18, 71.0, 3.94, "NW", 0, 1000.0, "---")
|
||||
return Observation(0.0, 71.0, 1000.0, 0.0, 18.0, "NW", 3.94)
|
||||
|
||||
async def forecast(self):
|
||||
async def forecast(self, api):
|
||||
"""Mock Forecast."""
|
||||
Forecast = namedtuple(
|
||||
"Forecast",
|
||||
[
|
||||
"precipitaProb",
|
||||
"tMin",
|
||||
"tMax",
|
||||
"predWindDir",
|
||||
"idWeatherType",
|
||||
"classWindSpeed",
|
||||
"longitude",
|
||||
"forecastDate",
|
||||
"classPrecInt",
|
||||
"latitude",
|
||||
"description",
|
||||
"feels_like_temperature",
|
||||
"forecast_date",
|
||||
"forecasted_hours",
|
||||
"humidity",
|
||||
"max_temperature",
|
||||
"min_temperature",
|
||||
"precipitation_probability",
|
||||
"temperature",
|
||||
"update_date",
|
||||
"weather_type",
|
||||
"wind_direction",
|
||||
"wind_strength",
|
||||
],
|
||||
)
|
||||
|
||||
return [
|
||||
Forecast(
|
||||
73.0,
|
||||
13.7,
|
||||
18.7,
|
||||
"NW",
|
||||
6,
|
||||
2,
|
||||
-8.64,
|
||||
"2018-05-31",
|
||||
2,
|
||||
40.61,
|
||||
"Aguaceiros, com vento Moderado de Noroeste",
|
||||
)
|
||||
None,
|
||||
"2020-01-15T00:00:00",
|
||||
24,
|
||||
None,
|
||||
16.2,
|
||||
10.6,
|
||||
"100.0",
|
||||
13.4,
|
||||
"2020-01-15T07:51:00",
|
||||
9,
|
||||
"S",
|
||||
None,
|
||||
),
|
||||
Forecast(
|
||||
"7.7",
|
||||
"2020-01-15T02:00:00",
|
||||
1,
|
||||
"86.9",
|
||||
None,
|
||||
None,
|
||||
"-99.0",
|
||||
10.6,
|
||||
"2020-01-15T07:51:00",
|
||||
10,
|
||||
"S",
|
||||
"32.7",
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
def name(self):
|
||||
"""Mock location."""
|
||||
return "HomeTown"
|
||||
|
||||
@property
|
||||
def latitude(self):
|
||||
def station_latitude(self):
|
||||
"""Mock latitude."""
|
||||
return 0
|
||||
|
||||
@property
|
||||
def longitude(self):
|
||||
def global_id_local(self):
|
||||
"""Mock global identifier of the location."""
|
||||
return 1130600
|
||||
|
||||
@property
|
||||
def id_station(self):
|
||||
"""Mock identifier of the station."""
|
||||
return 1200545
|
||||
|
||||
@property
|
||||
def station_longitude(self):
|
||||
"""Mock longitude."""
|
||||
return 0
|
||||
|
||||
@ -92,8 +118,8 @@ class MockStation:
|
||||
async def test_setup_configuration(hass):
|
||||
"""Test for successfully setting up the IPMA platform."""
|
||||
with patch(
|
||||
"homeassistant.components.ipma.weather.async_get_station",
|
||||
return_value=mock_coro(MockStation()),
|
||||
"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"}}
|
||||
@ -115,8 +141,8 @@ async def test_setup_configuration(hass):
|
||||
async def test_setup_config_flow(hass):
|
||||
"""Test for successfully setting up the IPMA platform."""
|
||||
with patch(
|
||||
"homeassistant.components.ipma.weather.async_get_station",
|
||||
return_value=mock_coro(MockStation()),
|
||||
"homeassistant.components.ipma.weather.async_get_location",
|
||||
return_value=mock_coro(MockLocation()),
|
||||
):
|
||||
entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG)
|
||||
await hass.config_entries.async_forward_entry_setup(entry, WEATHER_DOMAIN)
|
||||
|
Loading…
x
Reference in New Issue
Block a user