Home Assistant Unofficial Reference 2024.12.1
weather.py
Go to the documentation of this file.
1 """Support for Met.no weather service."""
2 
3 from __future__ import annotations
4 
5 from types import MappingProxyType
6 from typing import TYPE_CHECKING, Any
7 
9  ATTR_FORECAST_CONDITION,
10  ATTR_FORECAST_TIME,
11  ATTR_WEATHER_CLOUD_COVERAGE,
12  ATTR_WEATHER_DEW_POINT,
13  ATTR_WEATHER_HUMIDITY,
14  ATTR_WEATHER_PRESSURE,
15  ATTR_WEATHER_TEMPERATURE,
16  ATTR_WEATHER_UV_INDEX,
17  ATTR_WEATHER_WIND_BEARING,
18  ATTR_WEATHER_WIND_GUST_SPEED,
19  ATTR_WEATHER_WIND_SPEED,
20  DOMAIN as WEATHER_DOMAIN,
21  Forecast,
22  SingleCoordinatorWeatherEntity,
23  WeatherEntityFeature,
24 )
25 from homeassistant.const import (
26  CONF_LATITUDE,
27  CONF_LONGITUDE,
28  CONF_NAME,
29  UnitOfPrecipitationDepth,
30  UnitOfPressure,
31  UnitOfSpeed,
32  UnitOfTemperature,
33 )
34 from homeassistant.core import HomeAssistant, callback
35 from homeassistant.helpers import entity_registry as er, sun
36 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
37 from homeassistant.helpers.entity_platform import AddEntitiesCallback
38 from homeassistant.util.unit_system import METRIC_SYSTEM
39 
40 from . import MetWeatherConfigEntry
41 from .const import (
42  ATTR_CONDITION_CLEAR_NIGHT,
43  ATTR_CONDITION_SUNNY,
44  ATTR_MAP,
45  CONDITIONS_MAP,
46  CONF_TRACK_HOME,
47  DOMAIN,
48  FORECAST_MAP,
49 )
50 from .coordinator import MetDataUpdateCoordinator
51 
52 DEFAULT_NAME = "Met.no"
53 
54 
56  hass: HomeAssistant,
57  config_entry: MetWeatherConfigEntry,
58  async_add_entities: AddEntitiesCallback,
59 ) -> None:
60  """Add a weather entity from a config_entry."""
61  coordinator = config_entry.runtime_data
62  entity_registry = er.async_get(hass)
63 
64  name: str | None
65  is_metric = hass.config.units is METRIC_SYSTEM
66  if config_entry.data.get(CONF_TRACK_HOME, False):
67  name = hass.config.location_name
68  else:
69  name = config_entry.data.get(CONF_NAME, DEFAULT_NAME)
70  if TYPE_CHECKING:
71  assert isinstance(name, str)
72 
73  entities = [MetWeather(coordinator, config_entry, name, is_metric)]
74 
75  # Remove hourly entity from legacy config entries
76  if hourly_entity_id := entity_registry.async_get_entity_id(
77  WEATHER_DOMAIN,
78  DOMAIN,
79  _calculate_unique_id(config_entry.data, True),
80  ):
81  entity_registry.async_remove(hourly_entity_id)
82 
83  async_add_entities(entities)
84 
85 
86 def _calculate_unique_id(config: MappingProxyType[str, Any], hourly: bool) -> str:
87  """Calculate unique ID."""
88  name_appendix = ""
89  if hourly:
90  name_appendix = "-hourly"
91  if config.get(CONF_TRACK_HOME):
92  return f"home{name_appendix}"
93 
94  return f"{config[CONF_LATITUDE]}-{config[CONF_LONGITUDE]}{name_appendix}"
95 
96 
97 def format_condition(condition: str) -> str:
98  """Return condition from dict CONDITIONS_MAP."""
99  for key, value in CONDITIONS_MAP.items():
100  if condition in value:
101  return key
102  return condition
103 
104 
105 class MetWeather(SingleCoordinatorWeatherEntity[MetDataUpdateCoordinator]):
106  """Implementation of a Met.no weather condition."""
107 
108  _attr_attribution = (
109  "Weather forecast from met.no, delivered by the Norwegian "
110  "Meteorological Institute."
111  )
112  _attr_has_entity_name = True
113  _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
114  _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
115  _attr_native_pressure_unit = UnitOfPressure.HPA
116  _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
117  _attr_supported_features = (
118  WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
119  )
120 
121  def __init__(
122  self,
123  coordinator: MetDataUpdateCoordinator,
124  config_entry: MetWeatherConfigEntry,
125  name: str,
126  is_metric: bool,
127  ) -> None:
128  """Initialise the platform with a data instance and site."""
129  super().__init__(coordinator)
130  self._attr_unique_id_attr_unique_id = _calculate_unique_id(config_entry.data, False)
131  self._config_config_config = config_entry.data
132  self._is_metric_is_metric = is_metric
133  self._attr_device_info_attr_device_info = DeviceInfo(
134  name="Forecast",
135  entry_type=DeviceEntryType.SERVICE,
136  identifiers={(DOMAIN, config_entry.entry_id)},
137  manufacturer="Met.no",
138  model="Forecast",
139  configuration_url="https://www.met.no/en",
140  )
141  self._attr_track_home_attr_track_home = self._config_config_config.get(CONF_TRACK_HOME, False)
142  self._attr_name_attr_name = name
143 
144  @property
145  def condition(self) -> str | None:
146  """Return the current condition."""
147  condition = self.coordinator.data.current_weather_data.get("condition")
148  if condition is None:
149  return None
150 
151  if condition == ATTR_CONDITION_SUNNY and not sun.is_up(self.hasshasshasshass):
152  condition = ATTR_CONDITION_CLEAR_NIGHT
153 
154  return format_condition(condition)
155 
156  @property
157  def native_temperature(self) -> float | None:
158  """Return the temperature."""
159  return self.coordinator.data.current_weather_data.get(
160  ATTR_MAP[ATTR_WEATHER_TEMPERATURE]
161  )
162 
163  @property
164  def native_pressure(self) -> float | None:
165  """Return the pressure."""
166  return self.coordinator.data.current_weather_data.get(
167  ATTR_MAP[ATTR_WEATHER_PRESSURE]
168  )
169 
170  @property
171  def humidity(self) -> float | None:
172  """Return the humidity."""
173  return self.coordinator.data.current_weather_data.get(
174  ATTR_MAP[ATTR_WEATHER_HUMIDITY]
175  )
176 
177  @property
178  def native_wind_speed(self) -> float | None:
179  """Return the wind speed."""
180  return self.coordinator.data.current_weather_data.get(
181  ATTR_MAP[ATTR_WEATHER_WIND_SPEED]
182  )
183 
184  @property
185  def wind_bearing(self) -> float | str | None:
186  """Return the wind direction."""
187  return self.coordinator.data.current_weather_data.get(
188  ATTR_MAP[ATTR_WEATHER_WIND_BEARING]
189  )
190 
191  @property
192  def native_wind_gust_speed(self) -> float | None:
193  """Return the wind gust speed in native units."""
194  return self.coordinator.data.current_weather_data.get(
195  ATTR_MAP[ATTR_WEATHER_WIND_GUST_SPEED]
196  )
197 
198  @property
199  def cloud_coverage(self) -> float | None:
200  """Return the cloud coverage."""
201  return self.coordinator.data.current_weather_data.get(
202  ATTR_MAP[ATTR_WEATHER_CLOUD_COVERAGE]
203  )
204 
205  @property
206  def native_dew_point(self) -> float | None:
207  """Return the dew point."""
208  return self.coordinator.data.current_weather_data.get(
209  ATTR_MAP[ATTR_WEATHER_DEW_POINT]
210  )
211 
212  @property
213  def uv_index(self) -> float | None:
214  """Return the uv index."""
215  return self.coordinator.data.current_weather_data.get(
216  ATTR_MAP[ATTR_WEATHER_UV_INDEX]
217  )
218 
219  def _forecast(self, hourly: bool) -> list[Forecast] | None:
220  """Return the forecast array."""
221  if hourly:
222  met_forecast = self.coordinator.data.hourly_forecast
223  else:
224  met_forecast = self.coordinator.data.daily_forecast
225  required_keys = {"temperature", ATTR_FORECAST_TIME}
226  ha_forecast: list[Forecast] = []
227  for met_item in met_forecast:
228  if not set(met_item).issuperset(required_keys):
229  continue
230  ha_item = {
231  k: met_item[v]
232  for k, v in FORECAST_MAP.items()
233  if met_item.get(v) is not None
234  }
235  if ha_item.get(ATTR_FORECAST_CONDITION):
236  ha_item[ATTR_FORECAST_CONDITION] = format_condition(
237  ha_item[ATTR_FORECAST_CONDITION]
238  )
239  ha_forecast.append(ha_item) # type: ignore[arg-type]
240  return ha_forecast
241 
242  @callback
243  def _async_forecast_daily(self) -> list[Forecast] | None:
244  """Return the daily forecast in native units."""
245  return self._forecast_forecast(False)
246 
247  @callback
248  def _async_forecast_hourly(self) -> list[Forecast] | None:
249  """Return the hourly forecast in native units."""
250  return self._forecast_forecast(True)
list[Forecast]|None _async_forecast_daily(self)
Definition: weather.py:243
None __init__(self, MetDataUpdateCoordinator coordinator, MetWeatherConfigEntry config_entry, str name, bool is_metric)
Definition: weather.py:127
list[Forecast]|None _forecast(self, bool hourly)
Definition: weather.py:219
list[Forecast]|None _async_forecast_hourly(self)
Definition: weather.py:248
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
str format_condition(str condition)
Definition: weather.py:97
None async_setup_entry(HomeAssistant hass, MetWeatherConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: weather.py:59
str _calculate_unique_id(MappingProxyType[str, Any] config, bool hourly)
Definition: weather.py:86