Home Assistant Unofficial Reference 2024.12.1
weather.py
Go to the documentation of this file.
1 """Weather entity for Apple WeatherKit integration."""
2 
3 from typing import Any, cast
4 
5 from apple_weatherkit import DataSetType
6 
8  ATTR_CONDITION_CLOUDY,
9  ATTR_CONDITION_EXCEPTIONAL,
10  ATTR_CONDITION_FOG,
11  ATTR_CONDITION_HAIL,
12  ATTR_CONDITION_LIGHTNING,
13  ATTR_CONDITION_PARTLYCLOUDY,
14  ATTR_CONDITION_POURING,
15  ATTR_CONDITION_RAINY,
16  ATTR_CONDITION_SNOWY,
17  ATTR_CONDITION_SNOWY_RAINY,
18  ATTR_CONDITION_SUNNY,
19  ATTR_CONDITION_WINDY,
20  Forecast,
21  SingleCoordinatorWeatherEntity,
22  WeatherEntityFeature,
23 )
24 from homeassistant.config_entries import ConfigEntry
25 from homeassistant.const import (
26  UnitOfLength,
27  UnitOfPressure,
28  UnitOfSpeed,
29  UnitOfTemperature,
30 )
31 from homeassistant.core import HomeAssistant, callback
32 from homeassistant.helpers.entity_platform import AddEntitiesCallback
33 
34 from .const import (
35  ATTR_CURRENT_WEATHER,
36  ATTR_FORECAST_DAILY,
37  ATTR_FORECAST_HOURLY,
38  ATTRIBUTION,
39  DOMAIN,
40 )
41 from .coordinator import WeatherKitDataUpdateCoordinator
42 from .entity import WeatherKitEntity
43 
44 
46  hass: HomeAssistant,
47  config_entry: ConfigEntry,
48  async_add_entities: AddEntitiesCallback,
49 ) -> None:
50  """Add a weather entity from a config_entry."""
51  coordinator: WeatherKitDataUpdateCoordinator = hass.data[DOMAIN][
52  config_entry.entry_id
53  ]
54 
56 
57 
58 condition_code_to_hass = {
59  "BlowingDust": ATTR_CONDITION_WINDY,
60  "Clear": ATTR_CONDITION_SUNNY,
61  "Cloudy": ATTR_CONDITION_CLOUDY,
62  "Foggy": ATTR_CONDITION_FOG,
63  "Haze": ATTR_CONDITION_FOG,
64  "MostlyClear": ATTR_CONDITION_SUNNY,
65  "MostlyCloudy": ATTR_CONDITION_CLOUDY,
66  "PartlyCloudy": ATTR_CONDITION_PARTLYCLOUDY,
67  "Smoky": ATTR_CONDITION_FOG,
68  "Breezy": ATTR_CONDITION_WINDY,
69  "Windy": ATTR_CONDITION_WINDY,
70  "Drizzle": ATTR_CONDITION_RAINY,
71  "HeavyRain": ATTR_CONDITION_POURING,
72  "IsolatedThunderstorms": ATTR_CONDITION_LIGHTNING,
73  "Rain": ATTR_CONDITION_RAINY,
74  "SunShowers": ATTR_CONDITION_RAINY,
75  "ScatteredThunderstorms": ATTR_CONDITION_LIGHTNING,
76  "StrongStorms": ATTR_CONDITION_LIGHTNING,
77  "Thunderstorms": ATTR_CONDITION_LIGHTNING,
78  "Frigid": ATTR_CONDITION_SNOWY,
79  "Hail": ATTR_CONDITION_HAIL,
80  "Hot": ATTR_CONDITION_SUNNY,
81  "Flurries": ATTR_CONDITION_SNOWY,
82  "Sleet": ATTR_CONDITION_SNOWY,
83  "Snow": ATTR_CONDITION_SNOWY,
84  "SunFlurries": ATTR_CONDITION_SNOWY,
85  "WintryMix": ATTR_CONDITION_SNOWY,
86  "Blizzard": ATTR_CONDITION_SNOWY,
87  "BlowingSnow": ATTR_CONDITION_SNOWY,
88  "FreezingDrizzle": ATTR_CONDITION_SNOWY_RAINY,
89  "FreezingRain": ATTR_CONDITION_SNOWY_RAINY,
90  "HeavySnow": ATTR_CONDITION_SNOWY,
91  "Hurricane": ATTR_CONDITION_EXCEPTIONAL,
92  "TropicalStorm": ATTR_CONDITION_EXCEPTIONAL,
93 }
94 
95 
96 def _map_daily_forecast(forecast: dict[str, Any]) -> Forecast:
97  return {
98  "datetime": forecast["forecastStart"],
99  "condition": condition_code_to_hass[forecast["conditionCode"]],
100  "native_temperature": forecast["temperatureMax"],
101  "native_templow": forecast["temperatureMin"],
102  "native_precipitation": forecast["precipitationAmount"],
103  "precipitation_probability": forecast["precipitationChance"] * 100,
104  "uv_index": forecast["maxUvIndex"],
105  }
106 
107 
108 def _map_hourly_forecast(forecast: dict[str, Any]) -> Forecast:
109  return {
110  "datetime": forecast["forecastStart"],
111  "condition": condition_code_to_hass[forecast["conditionCode"]],
112  "native_temperature": forecast["temperature"],
113  "native_apparent_temperature": forecast["temperatureApparent"],
114  "native_dew_point": forecast.get("temperatureDewPoint"),
115  "native_pressure": forecast["pressure"],
116  "native_wind_gust_speed": forecast.get("windGust"),
117  "native_wind_speed": forecast["windSpeed"],
118  "wind_bearing": forecast.get("windDirection"),
119  "humidity": forecast["humidity"] * 100,
120  "native_precipitation": forecast.get("precipitationAmount"),
121  "precipitation_probability": forecast["precipitationChance"] * 100,
122  "cloud_coverage": forecast["cloudCover"] * 100,
123  "uv_index": forecast["uvIndex"],
124  }
125 
126 
128  SingleCoordinatorWeatherEntity[WeatherKitDataUpdateCoordinator], WeatherKitEntity
129 ):
130  """Weather entity for Apple WeatherKit integration."""
131 
132  _attr_attribution = ATTRIBUTION
133 
134  _attr_name = None
135 
136  _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
137  _attr_native_pressure_unit = UnitOfPressure.MBAR
138  _attr_native_visibility_unit = UnitOfLength.KILOMETERS
139  _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
140  _attr_native_precipitation_unit = UnitOfLength.MILLIMETERS
141 
142  def __init__(
143  self,
144  coordinator: WeatherKitDataUpdateCoordinator,
145  ) -> None:
146  """Initialize the platform with a coordinator."""
147  super().__init__(coordinator)
148  WeatherKitEntity.__init__(self, coordinator, unique_id_suffix=None)
149 
150  @property
151  def supported_features(self) -> WeatherEntityFeature:
152  """Determine supported features based on available data sets reported by WeatherKit."""
153  features = WeatherEntityFeature(0)
154 
155  if not self.coordinator.supported_data_sets:
156  return features
157 
158  if DataSetType.DAILY_FORECAST in self.coordinator.supported_data_sets:
159  features |= WeatherEntityFeature.FORECAST_DAILY
160  if DataSetType.HOURLY_FORECAST in self.coordinator.supported_data_sets:
161  features |= WeatherEntityFeature.FORECAST_HOURLY
162  return features
163 
164  @property
165  def data(self) -> dict[str, Any]:
166  """Return coordinator data."""
167  return self.coordinator.data
168 
169  @property
170  def current_weather(self) -> dict[str, Any]:
171  """Return current weather data."""
172  return self.datadatadata[ATTR_CURRENT_WEATHER]
173 
174  @property
175  def condition(self) -> str | None:
176  """Return the current condition."""
177  condition_code = cast(str, self.current_weathercurrent_weather.get("conditionCode"))
178  condition = condition_code_to_hass[condition_code]
179 
180  if condition == "sunny" and self.current_weathercurrent_weather.get("daylight") is False:
181  condition = "clear-night"
182 
183  return condition
184 
185  @property
186  def native_temperature(self) -> float | None:
187  """Return the current temperature."""
188  return self.current_weathercurrent_weather.get("temperature")
189 
190  @property
191  def native_apparent_temperature(self) -> float | None:
192  """Return the current apparent_temperature."""
193  return self.current_weathercurrent_weather.get("temperatureApparent")
194 
195  @property
196  def native_dew_point(self) -> float | None:
197  """Return the current dew_point."""
198  return self.current_weathercurrent_weather.get("temperatureDewPoint")
199 
200  @property
201  def native_pressure(self) -> float | None:
202  """Return the current pressure."""
203  return self.current_weathercurrent_weather.get("pressure")
204 
205  @property
206  def humidity(self) -> float | None:
207  """Return the current humidity."""
208  return cast(float, self.current_weathercurrent_weather.get("humidity")) * 100
209 
210  @property
211  def cloud_coverage(self) -> float | None:
212  """Return the current cloud_coverage."""
213  return cast(float, self.current_weathercurrent_weather.get("cloudCover")) * 100
214 
215  @property
216  def uv_index(self) -> float | None:
217  """Return the current uv_index."""
218  return self.current_weathercurrent_weather.get("uvIndex")
219 
220  @property
221  def native_visibility(self) -> float | None:
222  """Return the current visibility."""
223  return cast(float, self.current_weathercurrent_weather.get("visibility")) / 1000
224 
225  @property
226  def native_wind_gust_speed(self) -> float | None:
227  """Return the current wind_gust_speed."""
228  return self.current_weathercurrent_weather.get("windGust")
229 
230  @property
231  def native_wind_speed(self) -> float | None:
232  """Return the current wind_speed."""
233  return self.current_weathercurrent_weather.get("windSpeed")
234 
235  @property
236  def wind_bearing(self) -> float | None:
237  """Return the current wind_bearing."""
238  return self.current_weathercurrent_weather.get("windDirection")
239 
240  @callback
241  def _async_forecast_daily(self) -> list[Forecast] | None:
242  """Return the daily forecast."""
243  daily_forecast = self.datadatadata.get(ATTR_FORECAST_DAILY)
244  if not daily_forecast:
245  return None
246 
247  forecast = daily_forecast.get("days")
248  return [_map_daily_forecast(f) for f in forecast]
249 
250  @callback
251  def _async_forecast_hourly(self) -> list[Forecast] | None:
252  """Return the hourly forecast."""
253  hourly_forecast = self.datadatadata.get(ATTR_FORECAST_HOURLY)
254  if not hourly_forecast:
255  return None
256 
257  forecast = hourly_forecast.get("hours")
258  return [_map_hourly_forecast(f) for f in forecast]
None __init__(self, WeatherKitDataUpdateCoordinator coordinator)
Definition: weather.py:145
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
Forecast _map_hourly_forecast(dict[str, Any] forecast)
Definition: weather.py:108
Forecast _map_daily_forecast(dict[str, Any] forecast)
Definition: weather.py:96
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: weather.py:49