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