Home Assistant Unofficial Reference 2024.12.1
weather.py
Go to the documentation of this file.
1 """Support for Open-Meteo weather."""
2 
3 from __future__ import annotations
4 
5 from open_meteo import Forecast as OpenMeteoForecast
6 
8  ATTR_FORECAST_CONDITION,
9  ATTR_FORECAST_NATIVE_PRECIPITATION,
10  ATTR_FORECAST_NATIVE_TEMP,
11  ATTR_FORECAST_NATIVE_TEMP_LOW,
12  ATTR_FORECAST_NATIVE_WIND_SPEED,
13  ATTR_FORECAST_WIND_BEARING,
14  Forecast,
15  SingleCoordinatorWeatherEntity,
16  WeatherEntityFeature,
17 )
18 from homeassistant.config_entries import ConfigEntry
19 from homeassistant.const import UnitOfPrecipitationDepth, UnitOfSpeed, UnitOfTemperature
20 from homeassistant.core import HomeAssistant, callback
21 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
22 from homeassistant.helpers.entity_platform import AddEntitiesCallback
23 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
24 from homeassistant.util import dt as dt_util
25 
26 from .const import DOMAIN, WMO_TO_HA_CONDITION_MAP
27 
28 
30  hass: HomeAssistant,
31  entry: ConfigEntry,
32  async_add_entities: AddEntitiesCallback,
33 ) -> None:
34  """Set up Open-Meteo weather entity based on a config entry."""
35  coordinator = hass.data[DOMAIN][entry.entry_id]
36  async_add_entities([OpenMeteoWeatherEntity(entry=entry, coordinator=coordinator)])
37 
38 
40  SingleCoordinatorWeatherEntity[DataUpdateCoordinator[OpenMeteoForecast]]
41 ):
42  """Defines an Open-Meteo weather entity."""
43 
44  _attr_has_entity_name = True
45  _attr_name = None
46  _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
47  _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
48  _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
49  _attr_supported_features = (
50  WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
51  )
52 
53  def __init__(
54  self,
55  *,
56  entry: ConfigEntry,
57  coordinator: DataUpdateCoordinator[OpenMeteoForecast],
58  ) -> None:
59  """Initialize Open-Meteo weather entity."""
60  super().__init__(coordinator=coordinator)
61  self._attr_unique_id_attr_unique_id = entry.entry_id
62 
63  self._attr_device_info_attr_device_info = DeviceInfo(
64  entry_type=DeviceEntryType.SERVICE,
65  identifiers={(DOMAIN, entry.entry_id)},
66  manufacturer="Open-Meteo",
67  name=entry.title,
68  )
69 
70  @property
71  def condition(self) -> str | None:
72  """Return the current condition."""
73  if not self.coordinator.data.current_weather:
74  return None
75  return WMO_TO_HA_CONDITION_MAP.get(
76  self.coordinator.data.current_weather.weather_code
77  )
78 
79  @property
80  def native_temperature(self) -> float | None:
81  """Return the platform temperature."""
82  if not self.coordinator.data.current_weather:
83  return None
84  return self.coordinator.data.current_weather.temperature
85 
86  @property
87  def native_wind_speed(self) -> float | None:
88  """Return the wind speed."""
89  if not self.coordinator.data.current_weather:
90  return None
91  return self.coordinator.data.current_weather.wind_speed
92 
93  @property
94  def wind_bearing(self) -> float | str | None:
95  """Return the wind bearing."""
96  if not self.coordinator.data.current_weather:
97  return None
98  return self.coordinator.data.current_weather.wind_direction
99 
100  @callback
101  def _async_forecast_daily(self) -> list[Forecast] | None:
102  """Return the daily forecast in native units."""
103  if self.coordinator.data.daily is None:
104  return None
105 
106  forecasts: list[Forecast] = []
107 
108  daily = self.coordinator.data.daily
109  for index, date in enumerate(self.coordinator.data.daily.time):
110  forecast = Forecast(
111  datetime=date.isoformat(),
112  )
113 
114  if daily.weathercode is not None:
115  forecast[ATTR_FORECAST_CONDITION] = WMO_TO_HA_CONDITION_MAP.get(
116  daily.weathercode[index]
117  )
118 
119  if daily.precipitation_sum is not None:
120  forecast[ATTR_FORECAST_NATIVE_PRECIPITATION] = daily.precipitation_sum[
121  index
122  ]
123 
124  if daily.temperature_2m_max is not None:
125  forecast[ATTR_FORECAST_NATIVE_TEMP] = daily.temperature_2m_max[index]
126 
127  if daily.temperature_2m_min is not None:
128  forecast[ATTR_FORECAST_NATIVE_TEMP_LOW] = daily.temperature_2m_min[
129  index
130  ]
131 
132  if daily.wind_direction_10m_dominant is not None:
133  forecast[ATTR_FORECAST_WIND_BEARING] = (
134  daily.wind_direction_10m_dominant[index]
135  )
136 
137  if daily.wind_speed_10m_max is not None:
138  forecast[ATTR_FORECAST_NATIVE_WIND_SPEED] = daily.wind_speed_10m_max[
139  index
140  ]
141 
142  forecasts.append(forecast)
143 
144  return forecasts
145 
146  @callback
147  def _async_forecast_hourly(self) -> list[Forecast] | None:
148  """Return the daily forecast in native units."""
149  if self.coordinator.data.hourly is None:
150  return None
151 
152  forecasts: list[Forecast] = []
153 
154  # Can have data in the past: https://github.com/open-meteo/open-meteo/issues/699
155  today = dt_util.utcnow()
156 
157  hourly = self.coordinator.data.hourly
158  for index, datetime in enumerate(self.coordinator.data.hourly.time):
159  if dt_util.as_utc(datetime) < today:
160  continue
161 
162  forecast = Forecast(
163  datetime=datetime.isoformat(),
164  )
165 
166  if hourly.weather_code is not None:
167  forecast[ATTR_FORECAST_CONDITION] = WMO_TO_HA_CONDITION_MAP.get(
168  hourly.weather_code[index]
169  )
170 
171  if hourly.precipitation is not None:
172  forecast[ATTR_FORECAST_NATIVE_PRECIPITATION] = hourly.precipitation[
173  index
174  ]
175 
176  if hourly.temperature_2m is not None:
177  forecast[ATTR_FORECAST_NATIVE_TEMP] = hourly.temperature_2m[index]
178 
179  forecasts.append(forecast)
180 
181  return forecasts
None __init__(self, *ConfigEntry entry, DataUpdateCoordinator[OpenMeteoForecast] coordinator)
Definition: weather.py:58
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: weather.py:33