Home Assistant Unofficial Reference 2024.12.1
weather.py
Go to the documentation of this file.
1 """Support for Meteo-France weather service."""
2 
3 import logging
4 import time
5 
6 from meteofrance_api.model.forecast import Forecast as MeteoFranceForecast
7 
9  ATTR_FORECAST_CONDITION,
10  ATTR_FORECAST_HUMIDITY,
11  ATTR_FORECAST_NATIVE_PRECIPITATION,
12  ATTR_FORECAST_NATIVE_TEMP,
13  ATTR_FORECAST_NATIVE_TEMP_LOW,
14  ATTR_FORECAST_NATIVE_WIND_SPEED,
15  ATTR_FORECAST_TIME,
16  ATTR_FORECAST_WIND_BEARING,
17  Forecast,
18  WeatherEntity,
19  WeatherEntityFeature,
20 )
21 from homeassistant.config_entries import ConfigEntry
22 from homeassistant.const import (
23  CONF_MODE,
24  UnitOfPrecipitationDepth,
25  UnitOfPressure,
26  UnitOfSpeed,
27  UnitOfTemperature,
28 )
29 from homeassistant.core import HomeAssistant, callback
30 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
31 from homeassistant.helpers.entity_platform import AddEntitiesCallback
33  CoordinatorEntity,
34  DataUpdateCoordinator,
35 )
36 from homeassistant.util import dt as dt_util
37 
38 from .const import (
39  ATTRIBUTION,
40  CONDITION_MAP,
41  COORDINATOR_FORECAST,
42  DOMAIN,
43  FORECAST_MODE_DAILY,
44  FORECAST_MODE_HOURLY,
45  MANUFACTURER,
46  MODEL,
47 )
48 
49 _LOGGER = logging.getLogger(__name__)
50 
51 
52 def format_condition(condition: str):
53  """Return condition from dict CONDITION_MAP."""
54  return CONDITION_MAP.get(condition, condition)
55 
56 
58  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
59 ) -> None:
60  """Set up the Meteo-France weather platform."""
61  coordinator: DataUpdateCoordinator[MeteoFranceForecast] = hass.data[DOMAIN][
62  entry.entry_id
63  ][COORDINATOR_FORECAST]
64 
66  [
68  coordinator,
69  entry.options.get(CONF_MODE, FORECAST_MODE_DAILY),
70  )
71  ],
72  True,
73  )
74  _LOGGER.debug(
75  "Weather entity (%s) added for %s",
76  entry.options.get(CONF_MODE, FORECAST_MODE_DAILY),
77  coordinator.data.position["name"],
78  )
79 
80 
82  CoordinatorEntity[DataUpdateCoordinator[MeteoFranceForecast]], WeatherEntity
83 ):
84  """Representation of a weather condition."""
85 
86  _attr_attribution = ATTRIBUTION
87  _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
88  _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
89  _attr_native_pressure_unit = UnitOfPressure.HPA
90  _attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND
91  _attr_supported_features = (
92  WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
93  )
94 
95  def __init__(
96  self, coordinator: DataUpdateCoordinator[MeteoFranceForecast], mode: str
97  ) -> None:
98  """Initialise the platform with a data instance and station name."""
99  super().__init__(coordinator)
100  self._city_name_city_name = self.coordinator.data.position["name"]
101  self._mode_mode = mode
102  self._unique_id_unique_id = f"{self.coordinator.data.position['lat']},{self.coordinator.data.position['lon']}"
103 
104  @callback
105  def _handle_coordinator_update(self) -> None:
106  """Handle updated data from the coordinator."""
108  assert self.platformplatform.config_entry
109  self.platformplatform.config_entry.async_create_task(
110  self.hasshasshass, self.async_update_listenersasync_update_listenersasync_update_listeners(("daily", "hourly"))
111  )
112 
113  @property
114  def unique_id(self) -> str:
115  """Return the unique id of the sensor."""
116  return self._unique_id_unique_id
117 
118  @property
119  def name(self):
120  """Return the name of the sensor."""
121  return self._city_name_city_name
122 
123  @property
124  def device_info(self) -> DeviceInfo:
125  """Return the device info."""
126  assert self.platformplatform.config_entry and self.platformplatform.config_entry.unique_id
127  return DeviceInfo(
128  entry_type=DeviceEntryType.SERVICE,
129  identifiers={(DOMAIN, self.platformplatform.config_entry.unique_id)},
130  manufacturer=MANUFACTURER,
131  model=MODEL,
132  name=self.coordinator.name,
133  )
134 
135  @property
136  def condition(self):
137  """Return the current condition."""
138  return format_condition(
139  self.coordinator.data.current_forecast["weather"]["desc"]
140  )
141 
142  @property
144  """Return the temperature."""
145  return self.coordinator.data.current_forecast["T"]["value"]
146 
147  @property
148  def native_pressure(self):
149  """Return the pressure."""
150  return self.coordinator.data.current_forecast["sea_level"]
151 
152  @property
153  def humidity(self):
154  """Return the humidity."""
155  return self.coordinator.data.current_forecast["humidity"]
156 
157  @property
158  def native_wind_speed(self):
159  """Return the wind speed."""
160  return self.coordinator.data.current_forecast["wind"]["speed"]
161 
162  @property
163  def wind_bearing(self):
164  """Return the wind bearing."""
165  wind_bearing = self.coordinator.data.current_forecast["wind"]["direction"]
166  if wind_bearing != -1:
167  return wind_bearing
168  return None
169 
170  def _forecast(self, mode: str) -> list[Forecast]:
171  """Return the forecast."""
172  forecast_data: list[Forecast] = []
173 
174  if mode == FORECAST_MODE_HOURLY:
175  today = time.time()
176  for forecast in self.coordinator.data.forecast:
177  # Can have data in the past
178  if forecast["dt"] < today:
179  continue
180  forecast_data.append(
181  {
182  ATTR_FORECAST_TIME: dt_util.utc_from_timestamp(
183  forecast["dt"]
184  ).isoformat(),
185  ATTR_FORECAST_CONDITION: format_condition(
186  forecast["weather"]["desc"]
187  ),
188  ATTR_FORECAST_HUMIDITY: forecast["humidity"],
189  ATTR_FORECAST_NATIVE_TEMP: forecast["T"]["value"],
190  ATTR_FORECAST_NATIVE_PRECIPITATION: forecast["rain"].get("1h"),
191  ATTR_FORECAST_NATIVE_WIND_SPEED: forecast["wind"]["speed"],
192  ATTR_FORECAST_WIND_BEARING: forecast["wind"]["direction"]
193  if forecast["wind"]["direction"] != -1
194  else None,
195  }
196  )
197  else:
198  for forecast in self.coordinator.data.daily_forecast:
199  # stop when we don't have a weather condition (can happen around last days of forecast, max 14)
200  if not forecast.get("weather12H"):
201  break
202  forecast_data.append(
203  {
204  ATTR_FORECAST_TIME: dt_util.utc_from_timestamp(
205  forecast["dt"]
206  ).isoformat(),
207  ATTR_FORECAST_CONDITION: format_condition(
208  forecast["weather12H"]["desc"]
209  ),
210  ATTR_FORECAST_HUMIDITY: forecast["humidity"]["max"],
211  ATTR_FORECAST_NATIVE_TEMP: forecast["T"]["max"],
212  ATTR_FORECAST_NATIVE_TEMP_LOW: forecast["T"]["min"],
213  ATTR_FORECAST_NATIVE_PRECIPITATION: forecast["precipitation"][
214  "24h"
215  ],
216  }
217  )
218  return forecast_data
219 
220  async def async_forecast_daily(self) -> list[Forecast]:
221  """Return the daily forecast in native units."""
222  return self._forecast_forecast(FORECAST_MODE_DAILY)
223 
224  async def async_forecast_hourly(self) -> list[Forecast]:
225  """Return the hourly forecast in native units."""
226  return self._forecast_forecast(FORECAST_MODE_HOURLY)
None __init__(self, DataUpdateCoordinator[MeteoFranceForecast] coordinator, str mode)
Definition: weather.py:97
None async_update_listeners(self, Iterable[Literal["daily", "hourly", "twice_daily"]]|None forecast_types)
Definition: __init__.py:961
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: weather.py:59