Home Assistant Unofficial Reference 2024.12.1
weather.py
Go to the documentation of this file.
1 """Support for UK Met Office weather service."""
2 
3 from __future__ import annotations
4 
5 from typing import Any, cast
6 
7 from datapoint.Timestep import Timestep
8 
10  ATTR_FORECAST_CONDITION,
11  ATTR_FORECAST_NATIVE_TEMP,
12  ATTR_FORECAST_NATIVE_WIND_SPEED,
13  ATTR_FORECAST_PRECIPITATION_PROBABILITY,
14  ATTR_FORECAST_WIND_BEARING,
15  DOMAIN as WEATHER_DOMAIN,
16  CoordinatorWeatherEntity,
17  Forecast,
18  WeatherEntityFeature,
19 )
20 from homeassistant.config_entries import ConfigEntry
21 from homeassistant.const import UnitOfPressure, UnitOfSpeed, UnitOfTemperature
22 from homeassistant.core import HomeAssistant, callback
23 from homeassistant.helpers import entity_registry as er
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator
26 
27 from . import get_device_info
28 from .const import (
29  ATTRIBUTION,
30  CONDITION_MAP,
31  DOMAIN,
32  METOFFICE_COORDINATES,
33  METOFFICE_DAILY_COORDINATOR,
34  METOFFICE_HOURLY_COORDINATOR,
35  METOFFICE_NAME,
36  MODE_DAILY,
37 )
38 from .data import MetOfficeData
39 
40 
42  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
43 ) -> None:
44  """Set up the Met Office weather sensor platform."""
45  entity_registry = er.async_get(hass)
46  hass_data = hass.data[DOMAIN][entry.entry_id]
47 
48  # Remove hourly entity from legacy config entries
49  if entity_id := entity_registry.async_get_entity_id(
50  WEATHER_DOMAIN,
51  DOMAIN,
52  _calculate_unique_id(hass_data[METOFFICE_COORDINATES], True),
53  ):
54  entity_registry.async_remove(entity_id)
55 
57  [
59  hass_data[METOFFICE_DAILY_COORDINATOR],
60  hass_data[METOFFICE_HOURLY_COORDINATOR],
61  hass_data,
62  )
63  ],
64  False,
65  )
66 
67 
68 def _build_forecast_data(timestep: Timestep) -> Forecast:
69  data = Forecast(datetime=timestep.date.isoformat())
70  if timestep.weather:
71  data[ATTR_FORECAST_CONDITION] = CONDITION_MAP.get(timestep.weather.value)
72  if timestep.precipitation:
73  data[ATTR_FORECAST_PRECIPITATION_PROBABILITY] = timestep.precipitation.value
74  if timestep.temperature:
75  data[ATTR_FORECAST_NATIVE_TEMP] = timestep.temperature.value
76  if timestep.wind_direction:
77  data[ATTR_FORECAST_WIND_BEARING] = timestep.wind_direction.value
78  if timestep.wind_speed:
79  data[ATTR_FORECAST_NATIVE_WIND_SPEED] = timestep.wind_speed.value
80  return data
81 
82 
83 def _calculate_unique_id(coordinates: str, use_3hourly: bool) -> str:
84  """Calculate unique ID."""
85  if use_3hourly:
86  return coordinates
87  return f"{coordinates}_{MODE_DAILY}"
88 
89 
91  CoordinatorWeatherEntity[
92  TimestampDataUpdateCoordinator[MetOfficeData],
93  TimestampDataUpdateCoordinator[MetOfficeData],
94  ]
95 ):
96  """Implementation of a Met Office weather condition."""
97 
98  _attr_attribution = ATTRIBUTION
99  _attr_has_entity_name = True
100 
101  _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
102  _attr_native_pressure_unit = UnitOfPressure.HPA
103  _attr_native_wind_speed_unit = UnitOfSpeed.MILES_PER_HOUR
104  _attr_supported_features = (
105  WeatherEntityFeature.FORECAST_HOURLY | WeatherEntityFeature.FORECAST_DAILY
106  )
107 
108  def __init__(
109  self,
110  coordinator_daily: TimestampDataUpdateCoordinator[MetOfficeData],
111  coordinator_hourly: TimestampDataUpdateCoordinator[MetOfficeData],
112  hass_data: dict[str, Any],
113  ) -> None:
114  """Initialise the platform with a data instance."""
115  observation_coordinator = coordinator_daily
116  super().__init__(
117  observation_coordinator,
118  daily_coordinator=coordinator_daily,
119  hourly_coordinator=coordinator_hourly,
120  )
121 
122  self._attr_device_info_attr_device_info = get_device_info(
123  coordinates=hass_data[METOFFICE_COORDINATES], name=hass_data[METOFFICE_NAME]
124  )
125  self._attr_name_attr_name = "Daily"
126  self._attr_unique_id_attr_unique_id = _calculate_unique_id(
127  hass_data[METOFFICE_COORDINATES], False
128  )
129 
130  @property
131  def condition(self) -> str | None:
132  """Return the current condition."""
133  if self.coordinator.data.now:
134  return CONDITION_MAP.get(self.coordinator.data.now.weather.value)
135  return None
136 
137  @property
138  def native_temperature(self) -> float | None:
139  """Return the platform temperature."""
140  weather_now = self.coordinator.data.now
141  if weather_now.temperature:
142  value = weather_now.temperature.value
143  return float(value) if value is not None else None
144  return None
145 
146  @property
147  def native_pressure(self) -> float | None:
148  """Return the mean sea-level pressure."""
149  weather_now = self.coordinator.data.now
150  if weather_now and weather_now.pressure:
151  value = weather_now.pressure.value
152  return float(value) if value is not None else None
153  return None
154 
155  @property
156  def humidity(self) -> float | None:
157  """Return the relative humidity."""
158  weather_now = self.coordinator.data.now
159  if weather_now and weather_now.humidity:
160  value = weather_now.humidity.value
161  return float(value) if value is not None else None
162  return None
163 
164  @property
165  def native_wind_speed(self) -> float | None:
166  """Return the wind speed."""
167  weather_now = self.coordinator.data.now
168  if weather_now and weather_now.wind_speed:
169  value = weather_now.wind_speed.value
170  return float(value) if value is not None else None
171  return None
172 
173  @property
174  def wind_bearing(self) -> str | None:
175  """Return the wind bearing."""
176  weather_now = self.coordinator.data.now
177  if weather_now and weather_now.wind_direction:
178  value = weather_now.wind_direction.value
179  return str(value) if value is not None else None
180  return None
181 
182  @callback
183  def _async_forecast_daily(self) -> list[Forecast] | None:
184  """Return the twice daily forecast in native units."""
185  coordinator = cast(
186  TimestampDataUpdateCoordinator[MetOfficeData],
187  self.forecast_coordinatorsforecast_coordinators["daily"],
188  )
189  return [
190  _build_forecast_data(timestep) for timestep in coordinator.data.forecast
191  ]
192 
193  @callback
194  def _async_forecast_hourly(self) -> list[Forecast] | None:
195  """Return the hourly forecast in native units."""
196  coordinator = cast(
197  TimestampDataUpdateCoordinator[MetOfficeData],
198  self.forecast_coordinatorsforecast_coordinators["hourly"],
199  )
200  return [
201  _build_forecast_data(timestep) for timestep in coordinator.data.forecast
202  ]
None __init__(self, TimestampDataUpdateCoordinator[MetOfficeData] coordinator_daily, TimestampDataUpdateCoordinator[MetOfficeData] coordinator_hourly, dict[str, Any] hass_data)
Definition: weather.py:113
str _calculate_unique_id(str coordinates, bool use_3hourly)
Definition: weather.py:83
Forecast _build_forecast_data(Timestep timestep)
Definition: weather.py:68
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: weather.py:43
DeviceInfo get_device_info(str coordinates, str name)
Definition: __init__.py:156