Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Weather data coordinator for the OpenWeatherMap (OWM) service."""
2 
3 from datetime import timedelta
4 import logging
5 
6 from pyopenweathermap import (
7  CurrentWeather,
8  DailyWeatherForecast,
9  HourlyWeatherForecast,
10  OWMClient,
11  RequestError,
12  WeatherReport,
13 )
14 
16  ATTR_CONDITION_CLEAR_NIGHT,
17  ATTR_CONDITION_SUNNY,
18  Forecast,
19 )
20 from homeassistant.core import HomeAssistant
21 from homeassistant.helpers import sun
22 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
23 from homeassistant.util import dt as dt_util
24 
25 from .const import (
26  ATTR_API_CLOUDS,
27  ATTR_API_CONDITION,
28  ATTR_API_CURRENT,
29  ATTR_API_DAILY_FORECAST,
30  ATTR_API_DEW_POINT,
31  ATTR_API_FEELS_LIKE_TEMPERATURE,
32  ATTR_API_HOURLY_FORECAST,
33  ATTR_API_HUMIDITY,
34  ATTR_API_PRECIPITATION_KIND,
35  ATTR_API_PRESSURE,
36  ATTR_API_RAIN,
37  ATTR_API_SNOW,
38  ATTR_API_TEMPERATURE,
39  ATTR_API_UV_INDEX,
40  ATTR_API_VISIBILITY_DISTANCE,
41  ATTR_API_WEATHER,
42  ATTR_API_WEATHER_CODE,
43  ATTR_API_WIND_BEARING,
44  ATTR_API_WIND_GUST,
45  ATTR_API_WIND_SPEED,
46  CONDITION_MAP,
47  DOMAIN,
48  WEATHER_CODE_SUNNY_OR_CLEAR_NIGHT,
49 )
50 
51 _LOGGER = logging.getLogger(__name__)
52 
53 WEATHER_UPDATE_INTERVAL = timedelta(minutes=10)
54 
55 
57  """Weather data update coordinator."""
58 
59  def __init__(
60  self,
61  owm_client: OWMClient,
62  latitude,
63  longitude,
64  hass: HomeAssistant,
65  ) -> None:
66  """Initialize coordinator."""
67  self._owm_client_owm_client = owm_client
68  self._latitude_latitude = latitude
69  self._longitude_longitude = longitude
70 
71  super().__init__(
72  hass, _LOGGER, name=DOMAIN, update_interval=WEATHER_UPDATE_INTERVAL
73  )
74 
75  async def _async_update_data(self):
76  """Update the data."""
77  try:
78  weather_report = await self._owm_client_owm_client.get_weather(
79  self._latitude_latitude, self._longitude_longitude
80  )
81  except RequestError as error:
82  raise UpdateFailed(error) from error
83  return self._convert_weather_response_convert_weather_response(weather_report)
84 
85  def _convert_weather_response(self, weather_report: WeatherReport):
86  """Format the weather response correctly."""
87  _LOGGER.debug("OWM weather response: %s", weather_report)
88 
89  current_weather = (
90  self._get_current_weather_data_get_current_weather_data(weather_report.current)
91  if weather_report.current is not None
92  else {}
93  )
94 
95  return {
96  ATTR_API_CURRENT: current_weather,
97  ATTR_API_HOURLY_FORECAST: [
98  self._get_hourly_forecast_weather_data_get_hourly_forecast_weather_data(item)
99  for item in weather_report.hourly_forecast
100  ],
101  ATTR_API_DAILY_FORECAST: [
102  self._get_daily_forecast_weather_data_get_daily_forecast_weather_data(item)
103  for item in weather_report.daily_forecast
104  ],
105  }
106 
107  def _get_current_weather_data(self, current_weather: CurrentWeather):
108  return {
109  ATTR_API_CONDITION: self._get_condition_get_condition(current_weather.condition.id),
110  ATTR_API_TEMPERATURE: current_weather.temperature,
111  ATTR_API_FEELS_LIKE_TEMPERATURE: current_weather.feels_like,
112  ATTR_API_PRESSURE: current_weather.pressure,
113  ATTR_API_HUMIDITY: current_weather.humidity,
114  ATTR_API_DEW_POINT: current_weather.dew_point,
115  ATTR_API_CLOUDS: current_weather.cloud_coverage,
116  ATTR_API_WIND_SPEED: current_weather.wind_speed,
117  ATTR_API_WIND_GUST: current_weather.wind_gust,
118  ATTR_API_WIND_BEARING: current_weather.wind_bearing,
119  ATTR_API_WEATHER: current_weather.condition.description,
120  ATTR_API_WEATHER_CODE: current_weather.condition.id,
121  ATTR_API_UV_INDEX: current_weather.uv_index,
122  ATTR_API_VISIBILITY_DISTANCE: current_weather.visibility,
123  ATTR_API_RAIN: self._get_precipitation_value_get_precipitation_value(current_weather.rain),
124  ATTR_API_SNOW: self._get_precipitation_value_get_precipitation_value(current_weather.snow),
125  ATTR_API_PRECIPITATION_KIND: self._calc_precipitation_kind_calc_precipitation_kind(
126  current_weather.rain, current_weather.snow
127  ),
128  }
129 
130  def _get_hourly_forecast_weather_data(self, forecast: HourlyWeatherForecast):
131  uv_index = float(forecast.uv_index) if forecast.uv_index is not None else None
132 
133  return Forecast(
134  datetime=forecast.date_time.isoformat(),
135  condition=self._get_condition_get_condition(forecast.condition.id),
136  temperature=forecast.temperature,
137  native_apparent_temperature=forecast.feels_like,
138  pressure=forecast.pressure,
139  humidity=forecast.humidity,
140  native_dew_point=forecast.dew_point,
141  cloud_coverage=forecast.cloud_coverage,
142  wind_speed=forecast.wind_speed,
143  native_wind_gust_speed=forecast.wind_gust,
144  wind_bearing=forecast.wind_bearing,
145  uv_index=uv_index,
146  precipitation_probability=round(forecast.precipitation_probability * 100),
147  precipitation=self._calc_precipitation_calc_precipitation(forecast.rain, forecast.snow),
148  )
149 
150  def _get_daily_forecast_weather_data(self, forecast: DailyWeatherForecast):
151  uv_index = float(forecast.uv_index) if forecast.uv_index is not None else None
152 
153  return Forecast(
154  datetime=forecast.date_time.isoformat(),
155  condition=self._get_condition_get_condition(forecast.condition.id),
156  temperature=forecast.temperature.max,
157  templow=forecast.temperature.min,
158  native_apparent_temperature=forecast.feels_like,
159  pressure=forecast.pressure,
160  humidity=forecast.humidity,
161  native_dew_point=forecast.dew_point,
162  cloud_coverage=forecast.cloud_coverage,
163  wind_speed=forecast.wind_speed,
164  native_wind_gust_speed=forecast.wind_gust,
165  wind_bearing=forecast.wind_bearing,
166  uv_index=uv_index,
167  precipitation_probability=round(forecast.precipitation_probability * 100),
168  precipitation=round(forecast.rain + forecast.snow, 2),
169  )
170 
171  @staticmethod
172  def _calc_precipitation(rain, snow):
173  """Calculate the precipitation."""
174  rain_value = WeatherUpdateCoordinator._get_precipitation_value(rain)
175  snow_value = WeatherUpdateCoordinator._get_precipitation_value(snow)
176  return round(rain_value + snow_value, 2)
177 
178  @staticmethod
179  def _calc_precipitation_kind(rain, snow):
180  """Determine the precipitation kind."""
181  rain_value = WeatherUpdateCoordinator._get_precipitation_value(rain)
182  snow_value = WeatherUpdateCoordinator._get_precipitation_value(snow)
183  if rain_value != 0:
184  if snow_value != 0:
185  return "Snow and Rain"
186  return "Rain"
187 
188  if snow_value != 0:
189  return "Snow"
190  return "None"
191 
192  @staticmethod
193  def _get_precipitation_value(precipitation):
194  """Get precipitation value from weather data."""
195  if precipitation is not None:
196  if "all" in precipitation:
197  return round(precipitation["all"], 2)
198  if "3h" in precipitation:
199  return round(precipitation["3h"], 2)
200  if "1h" in precipitation:
201  return round(precipitation["1h"], 2)
202  return 0
203 
204  def _get_condition(self, weather_code, timestamp=None):
205  """Get weather condition from weather data."""
206  if weather_code == WEATHER_CODE_SUNNY_OR_CLEAR_NIGHT:
207  if timestamp:
208  timestamp = dt_util.utc_from_timestamp(timestamp)
209 
210  if sun.is_up(self.hasshass, timestamp):
211  return ATTR_CONDITION_SUNNY
212  return ATTR_CONDITION_CLEAR_NIGHT
213 
214  return CONDITION_MAP.get(weather_code)
def _get_daily_forecast_weather_data(self, DailyWeatherForecast forecast)
Definition: coordinator.py:150
def _get_hourly_forecast_weather_data(self, HourlyWeatherForecast forecast)
Definition: coordinator.py:130
None __init__(self, OWMClient owm_client, latitude, longitude, HomeAssistant hass)
Definition: coordinator.py:65