Home Assistant Unofficial Reference 2024.12.1
weather.py
Go to the documentation of this file.
1 """Support for Met Éireann weather service."""
2 
3 import logging
4 from types import MappingProxyType
5 from typing import Any, cast
6 
8  ATTR_FORECAST_CONDITION,
9  ATTR_FORECAST_TIME,
10  DOMAIN as WEATHER_DOMAIN,
11  Forecast,
12  SingleCoordinatorWeatherEntity,
13  WeatherEntityFeature,
14 )
15 from homeassistant.config_entries import ConfigEntry
16 from homeassistant.const import (
17  CONF_LATITUDE,
18  CONF_LONGITUDE,
19  CONF_NAME,
20  UnitOfPrecipitationDepth,
21  UnitOfPressure,
22  UnitOfSpeed,
23  UnitOfTemperature,
24 )
25 from homeassistant.core import HomeAssistant, callback
26 from homeassistant.helpers import entity_registry as er
27 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
28 from homeassistant.helpers.entity_platform import AddEntitiesCallback
29 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
30 from homeassistant.util import dt as dt_util
31 
32 from . import MetEireannWeatherData
33 from .const import CONDITION_MAP, DEFAULT_NAME, DOMAIN, FORECAST_MAP
34 
35 _LOGGER = logging.getLogger(__name__)
36 
37 
38 def format_condition(condition: str | None) -> str | None:
39  """Map the conditions provided by the weather API to those supported by the frontend."""
40  if condition is not None:
41  for key, value in CONDITION_MAP.items():
42  if condition in value:
43  return key
44  return condition
45 
46 
48  hass: HomeAssistant,
49  config_entry: ConfigEntry,
50  async_add_entities: AddEntitiesCallback,
51 ) -> None:
52  """Add a weather entity from a config_entry."""
53  coordinator = hass.data[DOMAIN][config_entry.entry_id]
54  entity_registry = er.async_get(hass)
55 
56  # Remove hourly entity from legacy config entries
57  if entity_id := entity_registry.async_get_entity_id(
58  WEATHER_DOMAIN,
59  DOMAIN,
60  _calculate_unique_id(config_entry.data, True),
61  ):
62  entity_registry.async_remove(entity_id)
63 
64  async_add_entities([MetEireannWeather(coordinator, config_entry.data)])
65 
66 
67 def _calculate_unique_id(config: MappingProxyType[str, Any], hourly: bool) -> str:
68  """Calculate unique ID."""
69  name_appendix = ""
70  if hourly:
71  name_appendix = "-hourly"
72 
73  return f"{config[CONF_LATITUDE]}-{config[CONF_LONGITUDE]}{name_appendix}"
74 
75 
77  SingleCoordinatorWeatherEntity[DataUpdateCoordinator[MetEireannWeatherData]]
78 ):
79  """Implementation of a Met Éireann weather condition."""
80 
81  _attr_attribution = "Data provided by Met Éireann"
82  _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
83  _attr_native_pressure_unit = UnitOfPressure.HPA
84  _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
85  _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
86  _attr_supported_features = (
87  WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
88  )
89 
90  def __init__(
91  self,
92  coordinator: DataUpdateCoordinator[MetEireannWeatherData],
93  config: MappingProxyType[str, Any],
94  ) -> None:
95  """Initialise the platform with a data instance and site."""
96  super().__init__(coordinator)
97  self._attr_unique_id_attr_unique_id = _calculate_unique_id(config, False)
98  self._config_config = config
99  if (name := self._config_config.get(CONF_NAME)) is not None:
100  self._attr_name_attr_name = name
101  else:
102  self._attr_name_attr_name = DEFAULT_NAME
103  self._attr_device_info_attr_device_info = DeviceInfo(
104  name="Forecast",
105  entry_type=DeviceEntryType.SERVICE,
106  identifiers={(DOMAIN,)}, # type: ignore[arg-type]
107  manufacturer="Met Éireann",
108  model="Forecast",
109  configuration_url="https://www.met.ie",
110  )
111 
112  @property
113  def condition(self) -> str | None:
114  """Return the current condition."""
115  return format_condition(
116  self.coordinator.data.current_weather_data.get("condition")
117  )
118 
119  @property
120  def native_temperature(self) -> float | None:
121  """Return the temperature."""
122  return self.coordinator.data.current_weather_data.get("temperature")
123 
124  @property
125  def native_pressure(self) -> float | None:
126  """Return the pressure."""
127  return self.coordinator.data.current_weather_data.get("pressure")
128 
129  @property
130  def humidity(self) -> float | None:
131  """Return the humidity."""
132  return self.coordinator.data.current_weather_data.get("humidity")
133 
134  @property
135  def native_wind_speed(self) -> float | None:
136  """Return the wind speed."""
137  return self.coordinator.data.current_weather_data.get("wind_speed")
138 
139  @property
140  def wind_bearing(self) -> float | None:
141  """Return the wind direction."""
142  return self.coordinator.data.current_weather_data.get("wind_bearing")
143 
144  def _forecast(self, hourly: bool) -> list[Forecast]:
145  """Return the forecast array."""
146  if hourly:
147  me_forecast = self.coordinator.data.hourly_forecast
148  else:
149  me_forecast = self.coordinator.data.daily_forecast
150  required_keys = {"temperature", "datetime"}
151 
152  ha_forecast: list[Forecast] = []
153 
154  for item in me_forecast:
155  if not set(item).issuperset(required_keys):
156  continue
157  ha_item: Forecast = cast(
158  Forecast,
159  {
160  k: item[v]
161  for k, v in FORECAST_MAP.items()
162  if item.get(v) is not None
163  },
164  )
165  # Convert condition
166  if item.get("condition"):
167  ha_item[ATTR_FORECAST_CONDITION] = format_condition(item["condition"])
168  # Convert timestamp to UTC string
169  if item.get("datetime"):
170  ha_item[ATTR_FORECAST_TIME] = dt_util.as_utc(
171  item["datetime"]
172  ).isoformat()
173  ha_forecast.append(ha_item)
174  return ha_forecast
175 
176  @callback
177  def _async_forecast_daily(self) -> list[Forecast]:
178  """Return the daily forecast in native units."""
179  return self._forecast_forecast(False)
180 
181  @callback
182  def _async_forecast_hourly(self) -> list[Forecast]:
183  """Return the hourly forecast in native units."""
184  return self._forecast_forecast(True)
None __init__(self, DataUpdateCoordinator[MetEireannWeatherData] coordinator, MappingProxyType[str, Any] config)
Definition: weather.py:94
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
str _calculate_unique_id(MappingProxyType[str, Any] config, bool hourly)
Definition: weather.py:67
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: weather.py:51
str|None format_condition(str|None condition)
Definition: weather.py:38