mirror of
https://github.com/home-assistant/core
synced 2024-08-02 23:40:32 +02:00
![Álvaro Fernández Rojas](/assets/img/avatar_default.png)
* aemet: drop isoformat() from timestamps
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
* aemet: fix forecast timestamp sensors
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
* aemet: move forecast timestamp sensor to lambda
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
* Revert "aemet: move forecast timestamp sensor to lambda"
This reverts commit 1f012c9adc
.
634 lines
21 KiB
Python
634 lines
21 KiB
Python
"""Weather data coordinator for the AEMET OpenData service."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import timedelta
|
|
import logging
|
|
|
|
from aemet_opendata.const import (
|
|
AEMET_ATTR_DATE,
|
|
AEMET_ATTR_DAY,
|
|
AEMET_ATTR_DIRECTION,
|
|
AEMET_ATTR_ELABORATED,
|
|
AEMET_ATTR_FORECAST,
|
|
AEMET_ATTR_HUMIDITY,
|
|
AEMET_ATTR_ID,
|
|
AEMET_ATTR_IDEMA,
|
|
AEMET_ATTR_MAX,
|
|
AEMET_ATTR_MIN,
|
|
AEMET_ATTR_NAME,
|
|
AEMET_ATTR_PRECIPITATION,
|
|
AEMET_ATTR_PRECIPITATION_PROBABILITY,
|
|
AEMET_ATTR_SKY_STATE,
|
|
AEMET_ATTR_SNOW,
|
|
AEMET_ATTR_SNOW_PROBABILITY,
|
|
AEMET_ATTR_SPEED,
|
|
AEMET_ATTR_STATION_DATE,
|
|
AEMET_ATTR_STATION_HUMIDITY,
|
|
AEMET_ATTR_STATION_LOCATION,
|
|
AEMET_ATTR_STATION_PRESSURE_SEA,
|
|
AEMET_ATTR_STATION_TEMPERATURE,
|
|
AEMET_ATTR_STORM_PROBABILITY,
|
|
AEMET_ATTR_TEMPERATURE,
|
|
AEMET_ATTR_TEMPERATURE_FEELING,
|
|
AEMET_ATTR_WIND,
|
|
AEMET_ATTR_WIND_GUST,
|
|
ATTR_DATA,
|
|
)
|
|
from aemet_opendata.helpers import (
|
|
get_forecast_day_value,
|
|
get_forecast_hour_value,
|
|
get_forecast_interval_value,
|
|
)
|
|
import async_timeout
|
|
|
|
from homeassistant.components.weather import (
|
|
ATTR_FORECAST_CONDITION,
|
|
ATTR_FORECAST_PRECIPITATION,
|
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
|
ATTR_FORECAST_TEMP,
|
|
ATTR_FORECAST_TEMP_LOW,
|
|
ATTR_FORECAST_TIME,
|
|
ATTR_FORECAST_WIND_BEARING,
|
|
ATTR_FORECAST_WIND_SPEED,
|
|
)
|
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
from .const import (
|
|
ATTR_API_CONDITION,
|
|
ATTR_API_FORECAST_DAILY,
|
|
ATTR_API_FORECAST_HOURLY,
|
|
ATTR_API_HUMIDITY,
|
|
ATTR_API_PRESSURE,
|
|
ATTR_API_RAIN,
|
|
ATTR_API_RAIN_PROB,
|
|
ATTR_API_SNOW,
|
|
ATTR_API_SNOW_PROB,
|
|
ATTR_API_STATION_ID,
|
|
ATTR_API_STATION_NAME,
|
|
ATTR_API_STATION_TIMESTAMP,
|
|
ATTR_API_STORM_PROB,
|
|
ATTR_API_TEMPERATURE,
|
|
ATTR_API_TEMPERATURE_FEELING,
|
|
ATTR_API_TOWN_ID,
|
|
ATTR_API_TOWN_NAME,
|
|
ATTR_API_TOWN_TIMESTAMP,
|
|
ATTR_API_WIND_BEARING,
|
|
ATTR_API_WIND_MAX_SPEED,
|
|
ATTR_API_WIND_SPEED,
|
|
CONDITIONS_MAP,
|
|
DOMAIN,
|
|
WIND_BEARING_MAP,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
STATION_MAX_DELTA = timedelta(hours=2)
|
|
WEATHER_UPDATE_INTERVAL = timedelta(minutes=10)
|
|
|
|
|
|
def format_condition(condition: str) -> str:
|
|
"""Return condition from dict CONDITIONS_MAP."""
|
|
for key, value in CONDITIONS_MAP.items():
|
|
if condition in value:
|
|
return key
|
|
_LOGGER.error('Condition "%s" not found in CONDITIONS_MAP', condition)
|
|
return condition
|
|
|
|
|
|
def format_float(value) -> float | None:
|
|
"""Try converting string to float."""
|
|
try:
|
|
return float(value)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def format_int(value) -> int | None:
|
|
"""Try converting string to int."""
|
|
try:
|
|
return int(value)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
class TownNotFound(UpdateFailed):
|
|
"""Raised when town is not found."""
|
|
|
|
|
|
class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
|
"""Weather data update coordinator."""
|
|
|
|
def __init__(self, hass, aemet, latitude, longitude, station_updates):
|
|
"""Initialize coordinator."""
|
|
super().__init__(
|
|
hass, _LOGGER, name=DOMAIN, update_interval=WEATHER_UPDATE_INTERVAL
|
|
)
|
|
|
|
self._aemet = aemet
|
|
self._station = None
|
|
self._town = None
|
|
self._latitude = latitude
|
|
self._longitude = longitude
|
|
self._station_updates = station_updates
|
|
self._data = {
|
|
"daily": None,
|
|
"hourly": None,
|
|
"station": None,
|
|
}
|
|
|
|
async def _async_update_data(self):
|
|
data = {}
|
|
async with async_timeout.timeout(120):
|
|
weather_response = await self._get_aemet_weather()
|
|
data = self._convert_weather_response(weather_response)
|
|
return data
|
|
|
|
async def _get_aemet_weather(self):
|
|
"""Poll weather data from AEMET OpenData."""
|
|
weather = await self.hass.async_add_executor_job(self._get_weather_and_forecast)
|
|
return weather
|
|
|
|
def _get_weather_station(self):
|
|
if not self._station:
|
|
self._station = (
|
|
self._aemet.get_conventional_observation_station_by_coordinates(
|
|
self._latitude, self._longitude
|
|
)
|
|
)
|
|
if self._station:
|
|
_LOGGER.debug(
|
|
"station found for coordinates [%s, %s]: %s",
|
|
self._latitude,
|
|
self._longitude,
|
|
self._station,
|
|
)
|
|
if not self._station:
|
|
_LOGGER.debug(
|
|
"station not found for coordinates [%s, %s]",
|
|
self._latitude,
|
|
self._longitude,
|
|
)
|
|
return self._station
|
|
|
|
def _get_weather_town(self):
|
|
if not self._town:
|
|
self._town = self._aemet.get_town_by_coordinates(
|
|
self._latitude, self._longitude
|
|
)
|
|
if self._town:
|
|
_LOGGER.debug(
|
|
"Town found for coordinates [%s, %s]: %s",
|
|
self._latitude,
|
|
self._longitude,
|
|
self._town,
|
|
)
|
|
if not self._town:
|
|
_LOGGER.error(
|
|
"Town not found for coordinates [%s, %s]",
|
|
self._latitude,
|
|
self._longitude,
|
|
)
|
|
raise TownNotFound
|
|
return self._town
|
|
|
|
def _get_weather_and_forecast(self):
|
|
"""Get weather and forecast data from AEMET OpenData."""
|
|
|
|
self._get_weather_town()
|
|
|
|
daily = self._aemet.get_specific_forecast_town_daily(self._town[AEMET_ATTR_ID])
|
|
if not daily:
|
|
_LOGGER.error(
|
|
'Error fetching daily data for town "%s"', self._town[AEMET_ATTR_ID]
|
|
)
|
|
|
|
hourly = self._aemet.get_specific_forecast_town_hourly(
|
|
self._town[AEMET_ATTR_ID]
|
|
)
|
|
if not hourly:
|
|
_LOGGER.error(
|
|
'Error fetching hourly data for town "%s"', self._town[AEMET_ATTR_ID]
|
|
)
|
|
|
|
station = None
|
|
if self._station_updates and self._get_weather_station():
|
|
station = self._aemet.get_conventional_observation_station_data(
|
|
self._station[AEMET_ATTR_IDEMA]
|
|
)
|
|
if not station:
|
|
_LOGGER.error(
|
|
'Error fetching data for station "%s"',
|
|
self._station[AEMET_ATTR_IDEMA],
|
|
)
|
|
|
|
if daily:
|
|
self._data["daily"] = daily
|
|
if hourly:
|
|
self._data["hourly"] = hourly
|
|
if station:
|
|
self._data["station"] = station
|
|
|
|
return AemetWeather(
|
|
self._data["daily"],
|
|
self._data["hourly"],
|
|
self._data["station"],
|
|
)
|
|
|
|
def _convert_weather_response(self, weather_response):
|
|
"""Format the weather response correctly."""
|
|
if not weather_response or not weather_response.hourly:
|
|
return None
|
|
|
|
elaborated = dt_util.parse_datetime(
|
|
weather_response.hourly[ATTR_DATA][0][AEMET_ATTR_ELABORATED] + "Z"
|
|
)
|
|
now = dt_util.now()
|
|
now_utc = dt_util.utcnow()
|
|
hour = now.hour
|
|
|
|
# Get current day
|
|
day = None
|
|
for cur_day in weather_response.hourly[ATTR_DATA][0][AEMET_ATTR_FORECAST][
|
|
AEMET_ATTR_DAY
|
|
]:
|
|
cur_day_date = dt_util.parse_datetime(cur_day[AEMET_ATTR_DATE])
|
|
if now.date() == cur_day_date.date():
|
|
day = cur_day
|
|
break
|
|
|
|
# Get latest station data
|
|
station_data = None
|
|
station_dt = None
|
|
if weather_response.station:
|
|
for _station_data in weather_response.station[ATTR_DATA]:
|
|
if AEMET_ATTR_STATION_DATE in _station_data:
|
|
_station_dt = dt_util.parse_datetime(
|
|
_station_data[AEMET_ATTR_STATION_DATE] + "Z"
|
|
)
|
|
if not station_dt or _station_dt > station_dt:
|
|
station_data = _station_data
|
|
station_dt = _station_dt
|
|
|
|
condition = None
|
|
humidity = None
|
|
pressure = None
|
|
rain = None
|
|
rain_prob = None
|
|
snow = None
|
|
snow_prob = None
|
|
station_id = None
|
|
station_name = None
|
|
station_timestamp = None
|
|
storm_prob = None
|
|
temperature = None
|
|
temperature_feeling = None
|
|
town_id = None
|
|
town_name = None
|
|
town_timestamp = dt_util.as_utc(elaborated)
|
|
wind_bearing = None
|
|
wind_max_speed = None
|
|
wind_speed = None
|
|
|
|
# Get weather values
|
|
if day:
|
|
condition = self._get_condition(day, hour)
|
|
humidity = self._get_humidity(day, hour)
|
|
rain = self._get_rain(day, hour)
|
|
rain_prob = self._get_rain_prob(day, hour)
|
|
snow = self._get_snow(day, hour)
|
|
snow_prob = self._get_snow_prob(day, hour)
|
|
station_id = self._get_station_id()
|
|
station_name = self._get_station_name()
|
|
storm_prob = self._get_storm_prob(day, hour)
|
|
temperature = self._get_temperature(day, hour)
|
|
temperature_feeling = self._get_temperature_feeling(day, hour)
|
|
town_id = self._get_town_id()
|
|
town_name = self._get_town_name()
|
|
wind_bearing = self._get_wind_bearing(day, hour)
|
|
wind_max_speed = self._get_wind_max_speed(day, hour)
|
|
wind_speed = self._get_wind_speed(day, hour)
|
|
|
|
# Overwrite weather values with closest station data (if present)
|
|
if station_data:
|
|
station_timestamp = dt_util.as_utc(station_dt)
|
|
if (now_utc - station_dt) <= STATION_MAX_DELTA:
|
|
if AEMET_ATTR_STATION_HUMIDITY in station_data:
|
|
humidity = format_float(station_data[AEMET_ATTR_STATION_HUMIDITY])
|
|
if AEMET_ATTR_STATION_PRESSURE_SEA in station_data:
|
|
pressure = format_float(
|
|
station_data[AEMET_ATTR_STATION_PRESSURE_SEA]
|
|
)
|
|
if AEMET_ATTR_STATION_TEMPERATURE in station_data:
|
|
temperature = format_float(
|
|
station_data[AEMET_ATTR_STATION_TEMPERATURE]
|
|
)
|
|
else:
|
|
_LOGGER.warning("Station data is outdated")
|
|
|
|
# Get forecast from weather data
|
|
forecast_daily = self._get_daily_forecast_from_weather_response(
|
|
weather_response, now
|
|
)
|
|
forecast_hourly = self._get_hourly_forecast_from_weather_response(
|
|
weather_response, now
|
|
)
|
|
|
|
return {
|
|
ATTR_API_CONDITION: condition,
|
|
ATTR_API_FORECAST_DAILY: forecast_daily,
|
|
ATTR_API_FORECAST_HOURLY: forecast_hourly,
|
|
ATTR_API_HUMIDITY: humidity,
|
|
ATTR_API_TEMPERATURE: temperature,
|
|
ATTR_API_TEMPERATURE_FEELING: temperature_feeling,
|
|
ATTR_API_PRESSURE: pressure,
|
|
ATTR_API_RAIN: rain,
|
|
ATTR_API_RAIN_PROB: rain_prob,
|
|
ATTR_API_SNOW: snow,
|
|
ATTR_API_SNOW_PROB: snow_prob,
|
|
ATTR_API_STATION_ID: station_id,
|
|
ATTR_API_STATION_NAME: station_name,
|
|
ATTR_API_STATION_TIMESTAMP: station_timestamp,
|
|
ATTR_API_STORM_PROB: storm_prob,
|
|
ATTR_API_TOWN_ID: town_id,
|
|
ATTR_API_TOWN_NAME: town_name,
|
|
ATTR_API_TOWN_TIMESTAMP: town_timestamp,
|
|
ATTR_API_WIND_BEARING: wind_bearing,
|
|
ATTR_API_WIND_MAX_SPEED: wind_max_speed,
|
|
ATTR_API_WIND_SPEED: wind_speed,
|
|
}
|
|
|
|
def _get_daily_forecast_from_weather_response(self, weather_response, now):
|
|
if weather_response.daily:
|
|
parse = False
|
|
forecast = []
|
|
for day in weather_response.daily[ATTR_DATA][0][AEMET_ATTR_FORECAST][
|
|
AEMET_ATTR_DAY
|
|
]:
|
|
day_date = dt_util.parse_datetime(day[AEMET_ATTR_DATE])
|
|
if now.date() == day_date.date():
|
|
parse = True
|
|
if parse:
|
|
cur_forecast = self._convert_forecast_day(day_date, day)
|
|
if cur_forecast:
|
|
forecast.append(cur_forecast)
|
|
return forecast
|
|
return None
|
|
|
|
def _get_hourly_forecast_from_weather_response(self, weather_response, now):
|
|
if weather_response.hourly:
|
|
parse = False
|
|
hour = now.hour
|
|
forecast = []
|
|
for day in weather_response.hourly[ATTR_DATA][0][AEMET_ATTR_FORECAST][
|
|
AEMET_ATTR_DAY
|
|
]:
|
|
day_date = dt_util.parse_datetime(day[AEMET_ATTR_DATE])
|
|
hour_start = 0
|
|
if now.date() == day_date.date():
|
|
parse = True
|
|
hour_start = now.hour
|
|
if parse:
|
|
for hour in range(hour_start, 24):
|
|
cur_forecast = self._convert_forecast_hour(day_date, day, hour)
|
|
if cur_forecast:
|
|
forecast.append(cur_forecast)
|
|
return forecast
|
|
return None
|
|
|
|
def _convert_forecast_day(self, date, day):
|
|
if not (condition := self._get_condition_day(day)):
|
|
return None
|
|
|
|
return {
|
|
ATTR_FORECAST_CONDITION: condition,
|
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: self._get_precipitation_prob_day(
|
|
day
|
|
),
|
|
ATTR_FORECAST_TEMP: self._get_temperature_day(day),
|
|
ATTR_FORECAST_TEMP_LOW: self._get_temperature_low_day(day),
|
|
ATTR_FORECAST_TIME: dt_util.as_utc(date).isoformat(),
|
|
ATTR_FORECAST_WIND_SPEED: self._get_wind_speed_day(day),
|
|
ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing_day(day),
|
|
}
|
|
|
|
def _convert_forecast_hour(self, date, day, hour):
|
|
if not (condition := self._get_condition(day, hour)):
|
|
return None
|
|
|
|
forecast_dt = date.replace(hour=hour, minute=0, second=0)
|
|
|
|
return {
|
|
ATTR_FORECAST_CONDITION: condition,
|
|
ATTR_FORECAST_PRECIPITATION: self._calc_precipitation(day, hour),
|
|
ATTR_FORECAST_PRECIPITATION_PROBABILITY: self._calc_precipitation_prob(
|
|
day, hour
|
|
),
|
|
ATTR_FORECAST_TEMP: self._get_temperature(day, hour),
|
|
ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt).isoformat(),
|
|
ATTR_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour),
|
|
ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour),
|
|
}
|
|
|
|
def _calc_precipitation(self, day, hour):
|
|
"""Calculate the precipitation."""
|
|
rain_value = self._get_rain(day, hour) or 0
|
|
snow_value = self._get_snow(day, hour) or 0
|
|
|
|
if round(rain_value + snow_value, 1) == 0:
|
|
return None
|
|
return round(rain_value + snow_value, 1)
|
|
|
|
def _calc_precipitation_prob(self, day, hour):
|
|
"""Calculate the precipitation probability (hour)."""
|
|
rain_value = self._get_rain_prob(day, hour) or 0
|
|
snow_value = self._get_snow_prob(day, hour) or 0
|
|
|
|
if rain_value == 0 and snow_value == 0:
|
|
return None
|
|
return max(rain_value, snow_value)
|
|
|
|
@staticmethod
|
|
def _get_condition(day_data, hour):
|
|
"""Get weather condition (hour) from weather data."""
|
|
val = get_forecast_hour_value(day_data[AEMET_ATTR_SKY_STATE], hour)
|
|
if val:
|
|
return format_condition(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_condition_day(day_data):
|
|
"""Get weather condition (day) from weather data."""
|
|
val = get_forecast_day_value(day_data[AEMET_ATTR_SKY_STATE])
|
|
if val:
|
|
return format_condition(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_humidity(day_data, hour):
|
|
"""Get humidity from weather data."""
|
|
val = get_forecast_hour_value(day_data[AEMET_ATTR_HUMIDITY], hour)
|
|
if val:
|
|
return format_int(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_precipitation_prob_day(day_data):
|
|
"""Get humidity from weather data."""
|
|
val = get_forecast_day_value(day_data[AEMET_ATTR_PRECIPITATION_PROBABILITY])
|
|
if val:
|
|
return format_int(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_rain(day_data, hour):
|
|
"""Get rain from weather data."""
|
|
val = get_forecast_hour_value(day_data[AEMET_ATTR_PRECIPITATION], hour)
|
|
if val:
|
|
return format_float(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_rain_prob(day_data, hour):
|
|
"""Get rain probability from weather data."""
|
|
val = get_forecast_interval_value(
|
|
day_data[AEMET_ATTR_PRECIPITATION_PROBABILITY], hour
|
|
)
|
|
if val:
|
|
return format_int(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_snow(day_data, hour):
|
|
"""Get snow from weather data."""
|
|
val = get_forecast_hour_value(day_data[AEMET_ATTR_SNOW], hour)
|
|
if val:
|
|
return format_float(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_snow_prob(day_data, hour):
|
|
"""Get snow probability from weather data."""
|
|
val = get_forecast_interval_value(day_data[AEMET_ATTR_SNOW_PROBABILITY], hour)
|
|
if val:
|
|
return format_int(val)
|
|
return None
|
|
|
|
def _get_station_id(self):
|
|
"""Get station ID from weather data."""
|
|
if self._station:
|
|
return self._station[AEMET_ATTR_IDEMA]
|
|
return None
|
|
|
|
def _get_station_name(self):
|
|
"""Get station name from weather data."""
|
|
if self._station:
|
|
return self._station[AEMET_ATTR_STATION_LOCATION]
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_storm_prob(day_data, hour):
|
|
"""Get storm probability from weather data."""
|
|
val = get_forecast_interval_value(day_data[AEMET_ATTR_STORM_PROBABILITY], hour)
|
|
if val:
|
|
return format_int(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_temperature(day_data, hour):
|
|
"""Get temperature (hour) from weather data."""
|
|
val = get_forecast_hour_value(day_data[AEMET_ATTR_TEMPERATURE], hour)
|
|
return format_int(val)
|
|
|
|
@staticmethod
|
|
def _get_temperature_day(day_data):
|
|
"""Get temperature (day) from weather data."""
|
|
val = get_forecast_day_value(
|
|
day_data[AEMET_ATTR_TEMPERATURE], key=AEMET_ATTR_MAX
|
|
)
|
|
return format_int(val)
|
|
|
|
@staticmethod
|
|
def _get_temperature_low_day(day_data):
|
|
"""Get temperature (day) from weather data."""
|
|
val = get_forecast_day_value(
|
|
day_data[AEMET_ATTR_TEMPERATURE], key=AEMET_ATTR_MIN
|
|
)
|
|
return format_int(val)
|
|
|
|
@staticmethod
|
|
def _get_temperature_feeling(day_data, hour):
|
|
"""Get temperature from weather data."""
|
|
val = get_forecast_hour_value(day_data[AEMET_ATTR_TEMPERATURE_FEELING], hour)
|
|
return format_int(val)
|
|
|
|
def _get_town_id(self):
|
|
"""Get town ID from weather data."""
|
|
if self._town:
|
|
return self._town[AEMET_ATTR_ID]
|
|
return None
|
|
|
|
def _get_town_name(self):
|
|
"""Get town name from weather data."""
|
|
if self._town:
|
|
return self._town[AEMET_ATTR_NAME]
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_wind_bearing(day_data, hour):
|
|
"""Get wind bearing (hour) from weather data."""
|
|
val = get_forecast_hour_value(
|
|
day_data[AEMET_ATTR_WIND_GUST], hour, key=AEMET_ATTR_DIRECTION
|
|
)[0]
|
|
if val in WIND_BEARING_MAP:
|
|
return WIND_BEARING_MAP[val]
|
|
_LOGGER.error("%s not found in Wind Bearing map", val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_wind_bearing_day(day_data):
|
|
"""Get wind bearing (day) from weather data."""
|
|
val = get_forecast_day_value(
|
|
day_data[AEMET_ATTR_WIND], key=AEMET_ATTR_DIRECTION
|
|
)
|
|
if val in WIND_BEARING_MAP:
|
|
return WIND_BEARING_MAP[val]
|
|
_LOGGER.error("%s not found in Wind Bearing map", val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_wind_max_speed(day_data, hour):
|
|
"""Get wind max speed from weather data."""
|
|
val = get_forecast_hour_value(day_data[AEMET_ATTR_WIND_GUST], hour)
|
|
if val:
|
|
return format_int(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_wind_speed(day_data, hour):
|
|
"""Get wind speed (hour) from weather data."""
|
|
val = get_forecast_hour_value(
|
|
day_data[AEMET_ATTR_WIND_GUST], hour, key=AEMET_ATTR_SPEED
|
|
)[0]
|
|
if val:
|
|
return format_int(val)
|
|
return None
|
|
|
|
@staticmethod
|
|
def _get_wind_speed_day(day_data):
|
|
"""Get wind speed (day) from weather data."""
|
|
val = get_forecast_day_value(day_data[AEMET_ATTR_WIND], key=AEMET_ATTR_SPEED)
|
|
if val:
|
|
return format_int(val)
|
|
return None
|
|
|
|
|
|
@dataclass
|
|
class AemetWeather:
|
|
"""Class to harmonize weather data model."""
|
|
|
|
daily: dict = field(default_factory=dict)
|
|
hourly: dict = field(default_factory=dict)
|
|
station: dict = field(default_factory=dict)
|