Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Weather component that handles meteorological data for your location."""
2 
3 from __future__ import annotations
4 
5 import abc
6 from collections.abc import Callable, Iterable
7 from contextlib import suppress
8 from datetime import timedelta
9 from functools import partial
10 import logging
11 from typing import Any, Final, Generic, Literal, Required, TypedDict, cast, final
12 
13 from propcache import cached_property
14 from typing_extensions import TypeVar
15 import voluptuous as vol
16 
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.const import (
19  PRECISION_HALVES,
20  PRECISION_TENTHS,
21  PRECISION_WHOLE,
22  UnitOfPressure,
23  UnitOfSpeed,
24  UnitOfTemperature,
25 )
26 from homeassistant.core import (
27  CALLBACK_TYPE,
28  HomeAssistant,
29  ServiceCall,
30  ServiceResponse,
31  SupportsResponse,
32  callback,
33 )
34 from homeassistant.exceptions import HomeAssistantError
35 from homeassistant.helpers import config_validation as cv
36 from homeassistant.helpers.entity import ABCCachedProperties, Entity, EntityDescription
37 from homeassistant.helpers.entity_component import EntityComponent
38 from homeassistant.helpers.typing import ConfigType
40  CoordinatorEntity,
41  DataUpdateCoordinator,
42  TimestampDataUpdateCoordinator,
43 )
44 from homeassistant.util.dt import utcnow
45 from homeassistant.util.json import JsonValueType
46 from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
47 
48 from .const import ( # noqa: F401
49  ATTR_WEATHER_APPARENT_TEMPERATURE,
50  ATTR_WEATHER_CLOUD_COVERAGE,
51  ATTR_WEATHER_DEW_POINT,
52  ATTR_WEATHER_HUMIDITY,
53  ATTR_WEATHER_OZONE,
54  ATTR_WEATHER_PRECIPITATION_UNIT,
55  ATTR_WEATHER_PRESSURE,
56  ATTR_WEATHER_PRESSURE_UNIT,
57  ATTR_WEATHER_TEMPERATURE,
58  ATTR_WEATHER_TEMPERATURE_UNIT,
59  ATTR_WEATHER_UV_INDEX,
60  ATTR_WEATHER_VISIBILITY,
61  ATTR_WEATHER_VISIBILITY_UNIT,
62  ATTR_WEATHER_WIND_BEARING,
63  ATTR_WEATHER_WIND_GUST_SPEED,
64  ATTR_WEATHER_WIND_SPEED,
65  ATTR_WEATHER_WIND_SPEED_UNIT,
66  DATA_COMPONENT,
67  DOMAIN,
68  INTENT_GET_WEATHER,
69  UNIT_CONVERSIONS,
70  VALID_UNITS,
71  WeatherEntityFeature,
72 )
73 from .websocket_api import async_setup as async_setup_ws_api
74 
75 _LOGGER = logging.getLogger(__name__)
76 
77 ENTITY_ID_FORMAT = DOMAIN + ".{}"
78 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
79 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
80 SCAN_INTERVAL = timedelta(seconds=30)
81 
82 ATTR_CONDITION_CLASS = "condition_class"
83 ATTR_CONDITION_CLEAR_NIGHT = "clear-night"
84 ATTR_CONDITION_CLOUDY = "cloudy"
85 ATTR_CONDITION_EXCEPTIONAL = "exceptional"
86 ATTR_CONDITION_FOG = "fog"
87 ATTR_CONDITION_HAIL = "hail"
88 ATTR_CONDITION_LIGHTNING = "lightning"
89 ATTR_CONDITION_LIGHTNING_RAINY = "lightning-rainy"
90 ATTR_CONDITION_PARTLYCLOUDY = "partlycloudy"
91 ATTR_CONDITION_POURING = "pouring"
92 ATTR_CONDITION_RAINY = "rainy"
93 ATTR_CONDITION_SNOWY = "snowy"
94 ATTR_CONDITION_SNOWY_RAINY = "snowy-rainy"
95 ATTR_CONDITION_SUNNY = "sunny"
96 ATTR_CONDITION_WINDY = "windy"
97 ATTR_CONDITION_WINDY_VARIANT = "windy-variant"
98 ATTR_FORECAST_IS_DAYTIME: Final = "is_daytime"
99 ATTR_FORECAST_CONDITION: Final = "condition"
100 ATTR_FORECAST_HUMIDITY: Final = "humidity"
101 ATTR_FORECAST_NATIVE_PRECIPITATION: Final = "native_precipitation"
102 ATTR_FORECAST_PRECIPITATION: Final = "precipitation"
103 ATTR_FORECAST_PRECIPITATION_PROBABILITY: Final = "precipitation_probability"
104 ATTR_FORECAST_NATIVE_PRESSURE: Final = "native_pressure"
105 ATTR_FORECAST_PRESSURE: Final = "pressure"
106 ATTR_FORECAST_NATIVE_APPARENT_TEMP: Final = "native_apparent_temperature"
107 ATTR_FORECAST_APPARENT_TEMP: Final = "apparent_temperature"
108 ATTR_FORECAST_NATIVE_TEMP: Final = "native_temperature"
109 ATTR_FORECAST_TEMP: Final = "temperature"
110 ATTR_FORECAST_NATIVE_TEMP_LOW: Final = "native_templow"
111 ATTR_FORECAST_TEMP_LOW: Final = "templow"
112 ATTR_FORECAST_TIME: Final = "datetime"
113 ATTR_FORECAST_WIND_BEARING: Final = "wind_bearing"
114 ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: Final = "native_wind_gust_speed"
115 ATTR_FORECAST_WIND_GUST_SPEED: Final = "wind_gust_speed"
116 ATTR_FORECAST_NATIVE_WIND_SPEED: Final = "native_wind_speed"
117 ATTR_FORECAST_WIND_SPEED: Final = "wind_speed"
118 ATTR_FORECAST_NATIVE_DEW_POINT: Final = "native_dew_point"
119 ATTR_FORECAST_DEW_POINT: Final = "dew_point"
120 ATTR_FORECAST_CLOUD_COVERAGE: Final = "cloud_coverage"
121 ATTR_FORECAST_UV_INDEX: Final = "uv_index"
122 
123 ROUNDING_PRECISION = 2
124 
125 SERVICE_GET_FORECASTS: Final = "get_forecasts"
126 
127 _ObservationUpdateCoordinatorT = TypeVar(
128  "_ObservationUpdateCoordinatorT",
129  bound=DataUpdateCoordinator[Any],
130  default=DataUpdateCoordinator[dict[str, Any]],
131 )
132 
133 _DailyForecastUpdateCoordinatorT = TypeVar(
134  "_DailyForecastUpdateCoordinatorT",
135  bound=TimestampDataUpdateCoordinator[Any],
136  default=TimestampDataUpdateCoordinator[None],
137 )
138 _HourlyForecastUpdateCoordinatorT = TypeVar(
139  "_HourlyForecastUpdateCoordinatorT",
140  bound=TimestampDataUpdateCoordinator[Any],
141  default=_DailyForecastUpdateCoordinatorT,
142 )
143 _TwiceDailyForecastUpdateCoordinatorT = TypeVar(
144  "_TwiceDailyForecastUpdateCoordinatorT",
145  bound=TimestampDataUpdateCoordinator[Any],
146  default=_DailyForecastUpdateCoordinatorT,
147 )
148 
149 # mypy: disallow-any-generics
150 
151 
152 def round_temperature(temperature: float | None, precision: float) -> float | None:
153  """Convert temperature into preferred precision for display."""
154  if temperature is None:
155  return None
156 
157  # Round in the units appropriate
158  if precision == PRECISION_HALVES:
159  temperature = round(temperature * 2) / 2.0
160  elif precision == PRECISION_TENTHS:
161  temperature = round(temperature, 1)
162  # Integer as a fall back (PRECISION_WHOLE)
163  else:
164  temperature = round(temperature)
165 
166  return temperature
167 
168 
169 class Forecast(TypedDict, total=False):
170  """Typed weather forecast dict.
171 
172  All attributes are in native units and old attributes kept
173  for backwards compatibility.
174  """
175 
176  condition: str | None
177  datetime: Required[str]
178  humidity: float | None
179  precipitation_probability: int | None
180  cloud_coverage: int | None
181  native_precipitation: float | None
182  precipitation: None
183  native_pressure: float | None
184  pressure: None
185  native_temperature: float | None
186  temperature: None
187  native_templow: float | None
188  templow: None
189  native_apparent_temperature: float | None
190  wind_bearing: float | str | None
191  native_wind_gust_speed: float | None
192  native_wind_speed: float | None
193  wind_speed: None
194  native_dew_point: float | None
195  uv_index: float | None
196  is_daytime: bool | None # Mandatory to use with forecast_twice_daily
197 
198 
199 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
200  """Set up the weather component."""
201  component = hass.data[DATA_COMPONENT] = EntityComponent[WeatherEntity](
202  _LOGGER, DOMAIN, hass, SCAN_INTERVAL
203  )
204  component.async_register_entity_service(
205  SERVICE_GET_FORECASTS,
206  {vol.Required("type"): vol.In(("daily", "hourly", "twice_daily"))},
207  async_get_forecasts_service,
208  required_features=[
209  WeatherEntityFeature.FORECAST_DAILY,
210  WeatherEntityFeature.FORECAST_HOURLY,
211  WeatherEntityFeature.FORECAST_TWICE_DAILY,
212  ],
213  supports_response=SupportsResponse.ONLY,
214  )
215  async_setup_ws_api(hass)
216  await component.async_setup(config)
217  return True
218 
219 
220 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
221  """Set up a config entry."""
222  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
223 
224 
225 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
226  """Unload a config entry."""
227  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
228 
229 
230 class WeatherEntityDescription(EntityDescription, frozen_or_thawed=True):
231  """A class that describes weather entities."""
232 
233 
235  """Meta class which calls __post_init__ after __new__ and __init__."""
236 
237  def __call__(cls, *args: Any, **kwargs: Any) -> Any: # noqa: N805 ruff bug, ruff does not understand this is a metaclass
238  """Create an instance."""
239  instance: PostInit = super().__call__(*args, **kwargs)
240  instance.__post_init__(*args, **kwargs)
241  return instance
242 
243 
244 class PostInit(metaclass=PostInitMeta):
245  """Class which calls __post_init__ after __new__ and __init__."""
246 
247  @abc.abstractmethod
248  def __post_init__(self, *args: Any, **kwargs: Any) -> None:
249  """Finish initializing."""
250 
251 
252 CACHED_PROPERTIES_WITH_ATTR_ = {
253  "native_apparent_temperature",
254  "native_temperature",
255  "native_temperature_unit",
256  "native_dew_point",
257  "native_pressure",
258  "native_pressure_unit",
259  "humidity",
260  "native_wind_gust_speed",
261  "native_wind_speed",
262  "native_wind_speed_unit",
263  "wind_bearing",
264  "ozone",
265  "cloud_coverage",
266  "uv_index",
267  "native_visibility",
268  "native_visibility_unit",
269  "native_precipitation_unit",
270  "condition",
271 }
272 
273 
274 class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
275  """ABC for weather data."""
276 
277  entity_description: WeatherEntityDescription
278  _attr_condition: str | None = None
279  _attr_humidity: float | None = None
280  _attr_ozone: float | None = None
281  _attr_cloud_coverage: int | None = None
282  _attr_uv_index: float | None = None
283  _attr_precision: float
284  _attr_state: None = None
285  _attr_wind_bearing: float | str | None = None
286 
287  _attr_native_pressure: float | None = None
288  _attr_native_pressure_unit: str | None = None
289  _attr_native_apparent_temperature: float | None = None
290  _attr_native_temperature: float | None = None
291  _attr_native_temperature_unit: str | None = None
292  _attr_native_visibility: float | None = None
293  _attr_native_visibility_unit: str | None = None
294  _attr_native_precipitation_unit: str | None = None
295  _attr_native_wind_gust_speed: float | None = None
296  _attr_native_wind_speed: float | None = None
297  _attr_native_wind_speed_unit: str | None = None
298  _attr_native_dew_point: float | None = None
299 
300  _forecast_listeners: dict[
301  Literal["daily", "hourly", "twice_daily"],
302  list[Callable[[list[JsonValueType] | None], None]],
303  ]
304 
305  _weather_option_temperature_unit: str | None = None
306  _weather_option_pressure_unit: str | None = None
307  _weather_option_visibility_unit: str | None = None
308  _weather_option_precipitation_unit: str | None = None
309  _weather_option_wind_speed_unit: str | None = None
310 
311  def __post_init__(self, *args: Any, **kwargs: Any) -> None:
312  """Finish initializing."""
313  self._forecast_listeners_forecast_listeners = {"daily": [], "hourly": [], "twice_daily": []}
314 
315  async def async_internal_added_to_hass(self) -> None:
316  """Call when the weather entity is added to hass."""
317  await super().async_internal_added_to_hass()
318  if not self.registry_entryregistry_entry:
319  return
320  self.async_registry_entry_updatedasync_registry_entry_updatedasync_registry_entry_updated()
321 
322  @cached_property
323  def native_apparent_temperature(self) -> float | None:
324  """Return the apparent temperature in native units."""
325  return self._attr_native_apparent_temperature
326 
327  @cached_property
328  def native_temperature(self) -> float | None:
329  """Return the temperature in native units."""
330  return self._attr_native_temperature
331 
332  @cached_property
333  def native_temperature_unit(self) -> str | None:
334  """Return the native unit of measurement for temperature."""
335  return self._attr_native_temperature_unit
336 
337  @cached_property
338  def native_dew_point(self) -> float | None:
339  """Return the dew point temperature in native units."""
340  return self._attr_native_dew_point
341 
342  @final
343  @property
344  def _default_temperature_unit(self) -> str:
345  """Return the default unit of measurement for temperature.
346 
347  Should not be set by integrations.
348  """
349  return self.hasshass.config.units.temperature_unit
350 
351  @final
352  @property
353  def _temperature_unit(self) -> str:
354  """Return the converted unit of measurement for temperature.
355 
356  Should not be set by integrations.
357  """
358  if (
359  weather_option_temperature_unit := self._weather_option_temperature_unit_weather_option_temperature_unit
360  ) is not None:
361  return weather_option_temperature_unit
362 
363  return self._default_temperature_unit_default_temperature_unit
364 
365  @cached_property
366  def native_pressure(self) -> float | None:
367  """Return the pressure in native units."""
368  return self._attr_native_pressure
369 
370  @cached_property
371  def native_pressure_unit(self) -> str | None:
372  """Return the native unit of measurement for pressure."""
373  return self._attr_native_pressure_unit
374 
375  @final
376  @property
377  def _default_pressure_unit(self) -> str:
378  """Return the default unit of measurement for pressure.
379 
380  Should not be set by integrations.
381  """
382  if self.hasshass.config.units is US_CUSTOMARY_SYSTEM:
383  return UnitOfPressure.INHG
384  return UnitOfPressure.HPA
385 
386  @final
387  @property
388  def _pressure_unit(self) -> str:
389  """Return the converted unit of measurement for pressure.
390 
391  Should not be set by integrations.
392  """
393  if (
394  weather_option_pressure_unit := self._weather_option_pressure_unit_weather_option_pressure_unit
395  ) is not None:
396  return weather_option_pressure_unit
397 
398  return self._default_pressure_unit_default_pressure_unit
399 
400  @cached_property
401  def humidity(self) -> float | None:
402  """Return the humidity in native units."""
403  return self._attr_humidity
404 
405  @cached_property
406  def native_wind_gust_speed(self) -> float | None:
407  """Return the wind gust speed in native units."""
408  return self._attr_native_wind_gust_speed
409 
410  @cached_property
411  def native_wind_speed(self) -> float | None:
412  """Return the wind speed in native units."""
413  return self._attr_native_wind_speed
414 
415  @cached_property
416  def native_wind_speed_unit(self) -> str | None:
417  """Return the native unit of measurement for wind speed."""
418  return self._attr_native_wind_speed_unit
419 
420  @final
421  @property
422  def _default_wind_speed_unit(self) -> str:
423  """Return the default unit of measurement for wind speed.
424 
425  Should not be set by integrations.
426  """
427  if self.hasshass.config.units is US_CUSTOMARY_SYSTEM:
428  return UnitOfSpeed.MILES_PER_HOUR
429  return UnitOfSpeed.KILOMETERS_PER_HOUR
430 
431  @final
432  @property
433  def _wind_speed_unit(self) -> str:
434  """Return the converted unit of measurement for wind speed.
435 
436  Should not be set by integrations.
437  """
438  if (
439  weather_option_wind_speed_unit := self._weather_option_wind_speed_unit_weather_option_wind_speed_unit
440  ) is not None:
441  return weather_option_wind_speed_unit
442 
443  return self._default_wind_speed_unit_default_wind_speed_unit
444 
445  @cached_property
446  def wind_bearing(self) -> float | str | None:
447  """Return the wind bearing."""
448  return self._attr_wind_bearing
449 
450  @cached_property
451  def ozone(self) -> float | None:
452  """Return the ozone level."""
453  return self._attr_ozone
454 
455  @cached_property
456  def cloud_coverage(self) -> float | None:
457  """Return the Cloud coverage in %."""
458  return self._attr_cloud_coverage
459 
460  @cached_property
461  def uv_index(self) -> float | None:
462  """Return the UV index."""
463  return self._attr_uv_index
464 
465  @cached_property
466  def native_visibility(self) -> float | None:
467  """Return the visibility in native units."""
468  return self._attr_native_visibility
469 
470  @cached_property
471  def native_visibility_unit(self) -> str | None:
472  """Return the native unit of measurement for visibility."""
473  return self._attr_native_visibility_unit
474 
475  @final
476  @property
477  def _default_visibility_unit(self) -> str:
478  """Return the default unit of measurement for visibility.
479 
480  Should not be set by integrations.
481  """
482  return self.hasshass.config.units.length_unit
483 
484  @final
485  @property
486  def _visibility_unit(self) -> str:
487  """Return the converted unit of measurement for visibility.
488 
489  Should not be set by integrations.
490  """
491  if (
492  weather_option_visibility_unit := self._weather_option_visibility_unit_weather_option_visibility_unit
493  ) is not None:
494  return weather_option_visibility_unit
495 
496  return self._default_visibility_unit_default_visibility_unit
497 
498  async def async_forecast_daily(self) -> list[Forecast] | None:
499  """Return the daily forecast in native units."""
500  raise NotImplementedError
501 
502  async def async_forecast_twice_daily(self) -> list[Forecast] | None:
503  """Return the daily forecast in native units."""
504  raise NotImplementedError
505 
506  async def async_forecast_hourly(self) -> list[Forecast] | None:
507  """Return the hourly forecast in native units."""
508  raise NotImplementedError
509 
510  @cached_property
511  def native_precipitation_unit(self) -> str | None:
512  """Return the native unit of measurement for accumulated precipitation."""
513  return self._attr_native_precipitation_unit
514 
515  @final
516  @property
517  def _default_precipitation_unit(self) -> str:
518  """Return the default unit of measurement for precipitation.
519 
520  Should not be set by integrations.
521  """
522  return self.hasshass.config.units.accumulated_precipitation_unit
523 
524  @final
525  @property
526  def _precipitation_unit(self) -> str:
527  """Return the converted unit of measurement for precipitation.
528 
529  Should not be set by integrations.
530  """
531  if (
532  weather_option_precipitation_unit := self._weather_option_precipitation_unit_weather_option_precipitation_unit
533  ) is not None:
534  return weather_option_precipitation_unit
535 
536  return self._default_precipitation_unit_default_precipitation_unit
537 
538  @property
539  def precision(self) -> float:
540  """Return the precision of the temperature value, after unit conversion."""
541  if hasattr(self, "_attr_precision"):
542  return self._attr_precision
543  return (
544  PRECISION_TENTHS
545  if self._temperature_unit_temperature_unit_temperature_unit == UnitOfTemperature.CELSIUS
546  else PRECISION_WHOLE
547  )
548 
549  @final
550  @property
551  def state_attributes(self) -> dict[str, Any]:
552  """Return the state attributes, converted.
553 
554  Attributes are configured from native units to user-configured units.
555  """
556  data: dict[str, Any] = {}
557 
558  precision = self.precisionprecision
559 
560  if (temperature := self.native_temperaturenative_temperature) is not None:
561  from_unit = self.native_temperature_unitnative_temperature_unit or self._default_temperature_unit_default_temperature_unit
562  to_unit = self._temperature_unit_temperature_unit_temperature_unit
563  try:
564  temperature_f = float(temperature)
565  value_temp = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
566  temperature_f, from_unit, to_unit
567  )
568  data[ATTR_WEATHER_TEMPERATURE] = round_temperature(
569  value_temp, precision
570  )
571  except (TypeError, ValueError):
572  data[ATTR_WEATHER_TEMPERATURE] = temperature
573 
574  if (apparent_temperature := self.native_apparent_temperaturenative_apparent_temperature) is not None:
575  from_unit = self.native_temperature_unitnative_temperature_unit or self._default_temperature_unit_default_temperature_unit
576  to_unit = self._temperature_unit_temperature_unit_temperature_unit
577  try:
578  apparent_temperature_f = float(apparent_temperature)
579  value_apparent_temp = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
580  apparent_temperature_f, from_unit, to_unit
581  )
582  data[ATTR_WEATHER_APPARENT_TEMPERATURE] = round_temperature(
583  value_apparent_temp, precision
584  )
585  except (TypeError, ValueError):
586  data[ATTR_WEATHER_APPARENT_TEMPERATURE] = apparent_temperature
587 
588  if (dew_point := self.native_dew_pointnative_dew_point) is not None:
589  from_unit = self.native_temperature_unitnative_temperature_unit or self._default_temperature_unit_default_temperature_unit
590  to_unit = self._temperature_unit_temperature_unit_temperature_unit
591  try:
592  dew_point_f = float(dew_point)
593  value_dew_point = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
594  dew_point_f, from_unit, to_unit
595  )
596  data[ATTR_WEATHER_DEW_POINT] = round_temperature(
597  value_dew_point, precision
598  )
599  except (TypeError, ValueError):
600  data[ATTR_WEATHER_DEW_POINT] = dew_point
601 
602  data[ATTR_WEATHER_TEMPERATURE_UNIT] = self._temperature_unit_temperature_unit_temperature_unit
603 
604  if (humidity := self.humidityhumidity) is not None:
605  data[ATTR_WEATHER_HUMIDITY] = round(humidity)
606 
607  if (ozone := self.ozoneozone) is not None:
608  data[ATTR_WEATHER_OZONE] = ozone
609 
610  if (cloud_coverage := self.cloud_coveragecloud_coverage) is not None:
611  data[ATTR_WEATHER_CLOUD_COVERAGE] = cloud_coverage
612 
613  if (uv_index := self.uv_indexuv_index) is not None:
614  data[ATTR_WEATHER_UV_INDEX] = uv_index
615 
616  if (pressure := self.native_pressurenative_pressure) is not None:
617  from_unit = self.native_pressure_unitnative_pressure_unit or self._default_pressure_unit_default_pressure_unit
618  to_unit = self._pressure_unit_pressure_unit
619  try:
620  pressure_f = float(pressure)
621  value_pressure = UNIT_CONVERSIONS[ATTR_WEATHER_PRESSURE_UNIT](
622  pressure_f, from_unit, to_unit
623  )
624  data[ATTR_WEATHER_PRESSURE] = round(value_pressure, ROUNDING_PRECISION)
625  except (TypeError, ValueError):
626  data[ATTR_WEATHER_PRESSURE] = pressure
627 
628  data[ATTR_WEATHER_PRESSURE_UNIT] = self._pressure_unit_pressure_unit
629 
630  if (wind_bearing := self.wind_bearingwind_bearing) is not None:
631  data[ATTR_WEATHER_WIND_BEARING] = wind_bearing
632 
633  if (wind_gust_speed := self.native_wind_gust_speednative_wind_gust_speed) is not None:
634  from_unit = self.native_wind_speed_unitnative_wind_speed_unit or self._default_wind_speed_unit_default_wind_speed_unit
635  to_unit = self._wind_speed_unit_wind_speed_unit
636  try:
637  wind_gust_speed_f = float(wind_gust_speed)
638  value_wind_gust_speed = UNIT_CONVERSIONS[ATTR_WEATHER_WIND_SPEED_UNIT](
639  wind_gust_speed_f, from_unit, to_unit
640  )
641  data[ATTR_WEATHER_WIND_GUST_SPEED] = round(
642  value_wind_gust_speed, ROUNDING_PRECISION
643  )
644  except (TypeError, ValueError):
645  data[ATTR_WEATHER_WIND_GUST_SPEED] = wind_gust_speed
646 
647  if (wind_speed := self.native_wind_speednative_wind_speed) is not None:
648  from_unit = self.native_wind_speed_unitnative_wind_speed_unit or self._default_wind_speed_unit_default_wind_speed_unit
649  to_unit = self._wind_speed_unit_wind_speed_unit
650  try:
651  wind_speed_f = float(wind_speed)
652  value_wind_speed = UNIT_CONVERSIONS[ATTR_WEATHER_WIND_SPEED_UNIT](
653  wind_speed_f, from_unit, to_unit
654  )
655  data[ATTR_WEATHER_WIND_SPEED] = round(
656  value_wind_speed, ROUNDING_PRECISION
657  )
658  except (TypeError, ValueError):
659  data[ATTR_WEATHER_WIND_SPEED] = wind_speed
660 
661  data[ATTR_WEATHER_WIND_SPEED_UNIT] = self._wind_speed_unit_wind_speed_unit
662 
663  if (visibility := self.native_visibilitynative_visibility) is not None:
664  from_unit = self.native_visibility_unitnative_visibility_unit or self._default_visibility_unit_default_visibility_unit
665  to_unit = self._visibility_unit_visibility_unit
666  try:
667  visibility_f = float(visibility)
668  value_visibility = UNIT_CONVERSIONS[ATTR_WEATHER_VISIBILITY_UNIT](
669  visibility_f, from_unit, to_unit
670  )
671  data[ATTR_WEATHER_VISIBILITY] = round(
672  value_visibility, ROUNDING_PRECISION
673  )
674  except (TypeError, ValueError):
675  data[ATTR_WEATHER_VISIBILITY] = visibility
676 
677  data[ATTR_WEATHER_VISIBILITY_UNIT] = self._visibility_unit_visibility_unit
678  data[ATTR_WEATHER_PRECIPITATION_UNIT] = self._precipitation_unit_precipitation_unit
679 
680  return data
681 
682  @final
684  self, native_forecast_list: list[Forecast]
685  ) -> list[JsonValueType]:
686  """Convert a forecast in native units to the unit configured by the user."""
687  converted_forecast_list: list[JsonValueType] = []
688  precision = self.precisionprecision
689 
690  from_temp_unit = self.native_temperature_unitnative_temperature_unit or self._default_temperature_unit_default_temperature_unit
691  to_temp_unit = self._temperature_unit_temperature_unit_temperature_unit
692 
693  for _forecast_entry in native_forecast_list:
694  forecast_entry: dict[str, Any] = dict(_forecast_entry)
695 
696  temperature = forecast_entry.pop(
697  ATTR_FORECAST_NATIVE_TEMP, forecast_entry.get(ATTR_FORECAST_TEMP)
698  )
699 
700  if temperature is None:
701  forecast_entry[ATTR_FORECAST_TEMP] = None
702  else:
703  with suppress(TypeError, ValueError):
704  temperature_f = float(temperature)
705  value_temp = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
706  temperature_f,
707  from_temp_unit,
708  to_temp_unit,
709  )
710  forecast_entry[ATTR_FORECAST_TEMP] = round_temperature(
711  value_temp, precision
712  )
713 
714  if (
715  forecast_apparent_temp := forecast_entry.pop(
716  ATTR_FORECAST_NATIVE_APPARENT_TEMP,
717  forecast_entry.get(ATTR_FORECAST_NATIVE_APPARENT_TEMP),
718  )
719  ) is not None:
720  with suppress(TypeError, ValueError):
721  forecast_apparent_temp = float(forecast_apparent_temp)
722  value_apparent_temp = UNIT_CONVERSIONS[
723  ATTR_WEATHER_TEMPERATURE_UNIT
724  ](
725  forecast_apparent_temp,
726  from_temp_unit,
727  to_temp_unit,
728  )
729 
730  forecast_entry[ATTR_FORECAST_APPARENT_TEMP] = round_temperature(
731  value_apparent_temp, precision
732  )
733 
734  if (
735  forecast_temp_low := forecast_entry.pop(
736  ATTR_FORECAST_NATIVE_TEMP_LOW,
737  forecast_entry.get(ATTR_FORECAST_TEMP_LOW),
738  )
739  ) is not None:
740  with suppress(TypeError, ValueError):
741  forecast_temp_low_f = float(forecast_temp_low)
742  value_temp_low = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
743  forecast_temp_low_f,
744  from_temp_unit,
745  to_temp_unit,
746  )
747 
748  forecast_entry[ATTR_FORECAST_TEMP_LOW] = round_temperature(
749  value_temp_low, precision
750  )
751 
752  if (
753  forecast_dew_point := forecast_entry.pop(
754  ATTR_FORECAST_NATIVE_DEW_POINT,
755  None,
756  )
757  ) is not None:
758  with suppress(TypeError, ValueError):
759  forecast_dew_point_f = float(forecast_dew_point)
760  value_dew_point = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
761  forecast_dew_point_f,
762  from_temp_unit,
763  to_temp_unit,
764  )
765 
766  forecast_entry[ATTR_FORECAST_DEW_POINT] = round_temperature(
767  value_dew_point, precision
768  )
769 
770  if (
771  forecast_pressure := forecast_entry.pop(
772  ATTR_FORECAST_NATIVE_PRESSURE,
773  forecast_entry.get(ATTR_FORECAST_PRESSURE),
774  )
775  ) is not None:
776  from_pressure_unit = (
777  self.native_pressure_unitnative_pressure_unit or self._default_pressure_unit_default_pressure_unit
778  )
779  to_pressure_unit = self._pressure_unit_pressure_unit
780  with suppress(TypeError, ValueError):
781  forecast_pressure_f = float(forecast_pressure)
782  forecast_entry[ATTR_FORECAST_PRESSURE] = round(
783  UNIT_CONVERSIONS[ATTR_WEATHER_PRESSURE_UNIT](
784  forecast_pressure_f,
785  from_pressure_unit,
786  to_pressure_unit,
787  ),
788  ROUNDING_PRECISION,
789  )
790 
791  if (
792  forecast_wind_gust_speed := forecast_entry.pop(
793  ATTR_FORECAST_NATIVE_WIND_GUST_SPEED,
794  None,
795  )
796  ) is not None:
797  from_wind_speed_unit = (
798  self.native_wind_speed_unitnative_wind_speed_unit or self._default_wind_speed_unit_default_wind_speed_unit
799  )
800  to_wind_speed_unit = self._wind_speed_unit_wind_speed_unit
801  with suppress(TypeError, ValueError):
802  forecast_wind_gust_speed_f = float(forecast_wind_gust_speed)
803  forecast_entry[ATTR_FORECAST_WIND_GUST_SPEED] = round(
804  UNIT_CONVERSIONS[ATTR_WEATHER_WIND_SPEED_UNIT](
805  forecast_wind_gust_speed_f,
806  from_wind_speed_unit,
807  to_wind_speed_unit,
808  ),
809  ROUNDING_PRECISION,
810  )
811 
812  if (
813  forecast_wind_speed := forecast_entry.pop(
814  ATTR_FORECAST_NATIVE_WIND_SPEED,
815  forecast_entry.get(ATTR_FORECAST_WIND_SPEED),
816  )
817  ) is not None:
818  from_wind_speed_unit = (
819  self.native_wind_speed_unitnative_wind_speed_unit or self._default_wind_speed_unit_default_wind_speed_unit
820  )
821  to_wind_speed_unit = self._wind_speed_unit_wind_speed_unit
822  with suppress(TypeError, ValueError):
823  forecast_wind_speed_f = float(forecast_wind_speed)
824  forecast_entry[ATTR_FORECAST_WIND_SPEED] = round(
825  UNIT_CONVERSIONS[ATTR_WEATHER_WIND_SPEED_UNIT](
826  forecast_wind_speed_f,
827  from_wind_speed_unit,
828  to_wind_speed_unit,
829  ),
830  ROUNDING_PRECISION,
831  )
832 
833  if (
834  forecast_precipitation := forecast_entry.pop(
835  ATTR_FORECAST_NATIVE_PRECIPITATION,
836  forecast_entry.get(ATTR_FORECAST_PRECIPITATION),
837  )
838  ) is not None:
839  from_precipitation_unit = (
840  self.native_precipitation_unitnative_precipitation_unit or self._default_precipitation_unit_default_precipitation_unit
841  )
842  to_precipitation_unit = self._precipitation_unit_precipitation_unit
843  with suppress(TypeError, ValueError):
844  forecast_precipitation_f = float(forecast_precipitation)
845  forecast_entry[ATTR_FORECAST_PRECIPITATION] = round(
846  UNIT_CONVERSIONS[ATTR_WEATHER_PRECIPITATION_UNIT](
847  forecast_precipitation_f,
848  from_precipitation_unit,
849  to_precipitation_unit,
850  ),
851  ROUNDING_PRECISION,
852  )
853 
854  if (
855  forecast_humidity := forecast_entry.pop(
856  ATTR_FORECAST_HUMIDITY,
857  None,
858  )
859  ) is not None:
860  with suppress(TypeError, ValueError):
861  forecast_humidity_f = float(forecast_humidity)
862  forecast_entry[ATTR_FORECAST_HUMIDITY] = round(forecast_humidity_f)
863 
864  converted_forecast_list.append(forecast_entry)
865 
866  return converted_forecast_list
867 
868  @property
869  @final
870  def state(self) -> str | None:
871  """Return the current state."""
872  return self.conditioncondition
873 
874  @cached_property
875  def condition(self) -> str | None:
876  """Return the current condition."""
877  return self._attr_condition
878 
879  @callback
880  def async_registry_entry_updated(self) -> None:
881  """Run when the entity registry entry has been updated."""
882  assert self.registry_entryregistry_entry
883  self._weather_option_temperature_unit_weather_option_temperature_unit = None
884  self._weather_option_pressure_unit_weather_option_pressure_unit = None
885  self._weather_option_precipitation_unit_weather_option_precipitation_unit = None
886  self._weather_option_wind_speed_unit_weather_option_wind_speed_unit = None
887  self._weather_option_visibility_unit_weather_option_visibility_unit = None
888  if weather_options := self.registry_entryregistry_entry.options.get(DOMAIN):
889  if (
890  custom_unit_temperature := weather_options.get(
891  ATTR_WEATHER_TEMPERATURE_UNIT
892  )
893  ) and custom_unit_temperature in VALID_UNITS[ATTR_WEATHER_TEMPERATURE_UNIT]:
894  self._weather_option_temperature_unit_weather_option_temperature_unit = custom_unit_temperature
895  if (
896  custom_unit_pressure := weather_options.get(ATTR_WEATHER_PRESSURE_UNIT)
897  ) and custom_unit_pressure in VALID_UNITS[ATTR_WEATHER_PRESSURE_UNIT]:
898  self._weather_option_pressure_unit_weather_option_pressure_unit = custom_unit_pressure
899  if (
900  custom_unit_precipitation := weather_options.get(
901  ATTR_WEATHER_PRECIPITATION_UNIT
902  )
903  ) and custom_unit_precipitation in VALID_UNITS[
904  ATTR_WEATHER_PRECIPITATION_UNIT
905  ]:
906  self._weather_option_precipitation_unit_weather_option_precipitation_unit = custom_unit_precipitation
907  if (
908  custom_unit_wind_speed := weather_options.get(
909  ATTR_WEATHER_WIND_SPEED_UNIT
910  )
911  ) and custom_unit_wind_speed in VALID_UNITS[ATTR_WEATHER_WIND_SPEED_UNIT]:
912  self._weather_option_wind_speed_unit_weather_option_wind_speed_unit = custom_unit_wind_speed
913  if (
914  custom_unit_visibility := weather_options.get(
915  ATTR_WEATHER_VISIBILITY_UNIT
916  )
917  ) and custom_unit_visibility in VALID_UNITS[ATTR_WEATHER_VISIBILITY_UNIT]:
918  self._weather_option_visibility_unit_weather_option_visibility_unit = custom_unit_visibility
919 
920  @callback
922  self,
923  forecast_type: Literal["daily", "hourly", "twice_daily"],
924  ) -> None:
925  """Start subscription to forecast_type."""
926 
927  @callback
929  self,
930  forecast_type: Literal["daily", "hourly", "twice_daily"],
931  ) -> None:
932  """End subscription to forecast_type."""
933 
934  @final
935  @callback
937  self,
938  forecast_type: Literal["daily", "hourly", "twice_daily"],
939  forecast_listener: Callable[[list[JsonValueType] | None], None],
940  ) -> CALLBACK_TYPE:
941  """Subscribe to forecast updates.
942 
943  Called by websocket API.
944  """
945  subscription_started = not self._forecast_listeners_forecast_listeners[forecast_type]
946  self._forecast_listeners_forecast_listeners[forecast_type].append(forecast_listener)
947  if subscription_started:
948  self._async_subscription_started_async_subscription_started(forecast_type)
949 
950  @callback
951  def unsubscribe() -> None:
952  self._forecast_listeners_forecast_listeners[forecast_type].remove(forecast_listener)
953  if not self._forecast_listeners_forecast_listeners[forecast_type]:
954  self._async_subscription_ended_async_subscription_ended(forecast_type)
955 
956  return unsubscribe
957 
958  @final
960  self, forecast_types: Iterable[Literal["daily", "hourly", "twice_daily"]] | None
961  ) -> None:
962  """Push updated forecast to all listeners."""
963  if forecast_types is None:
964  forecast_types = {"daily", "hourly", "twice_daily"}
965  for forecast_type in forecast_types:
966  if not self._forecast_listeners_forecast_listeners[forecast_type]:
967  continue
968 
969  native_forecast_list: list[Forecast] | None = await getattr(
970  self, f"async_forecast_{forecast_type}"
971  )()
972 
973  if native_forecast_list is None:
974  for listener in self._forecast_listeners_forecast_listeners[forecast_type]:
975  listener(None)
976  continue
977 
978  if forecast_type == "twice_daily":
979  for fc_twice_daily in native_forecast_list:
980  if fc_twice_daily.get(ATTR_FORECAST_IS_DAYTIME) is None:
981  raise ValueError(
982  "is_daytime mandatory attribute for forecast_twice_daily is missing"
983  )
984 
985  converted_forecast_list = self._convert_forecast_convert_forecast(native_forecast_list)
986  for listener in self._forecast_listeners_forecast_listeners[forecast_type]:
987  listener(converted_forecast_list)
988 
989 
990 def raise_unsupported_forecast(entity_id: str, forecast_type: str) -> None:
991  """Raise error on attempt to get an unsupported forecast."""
992  raise HomeAssistantError(
993  f"Weather entity '{entity_id}' does not support '{forecast_type}' forecast"
994  )
995 
996 
998  weather: WeatherEntity, service_call: ServiceCall
999 ) -> ServiceResponse:
1000  """Get weather forecast."""
1001  forecast_type = service_call.data["type"]
1002  supported_features = weather.supported_features or 0
1003  if forecast_type == "daily":
1004  if (supported_features & WeatherEntityFeature.FORECAST_DAILY) == 0:
1005  raise_unsupported_forecast(weather.entity_id, forecast_type)
1006  native_forecast_list = await weather.async_forecast_daily()
1007  elif forecast_type == "hourly":
1008  if (supported_features & WeatherEntityFeature.FORECAST_HOURLY) == 0:
1009  raise_unsupported_forecast(weather.entity_id, forecast_type)
1010  native_forecast_list = await weather.async_forecast_hourly()
1011  else:
1012  if (supported_features & WeatherEntityFeature.FORECAST_TWICE_DAILY) == 0:
1013  raise_unsupported_forecast(weather.entity_id, forecast_type)
1014  native_forecast_list = await weather.async_forecast_twice_daily()
1015  if native_forecast_list is None:
1016  converted_forecast_list = []
1017  else:
1018  converted_forecast_list = weather._convert_forecast(native_forecast_list) # noqa: SLF001
1019  return {
1020  "forecast": converted_forecast_list,
1021  }
1022 
1023 
1025  CoordinatorEntity[_ObservationUpdateCoordinatorT],
1026  WeatherEntity,
1027  Generic[
1028  _ObservationUpdateCoordinatorT,
1029  _DailyForecastUpdateCoordinatorT,
1030  _HourlyForecastUpdateCoordinatorT,
1031  _TwiceDailyForecastUpdateCoordinatorT,
1032  ],
1033 ):
1034  """A class for weather entities using DataUpdateCoordinators."""
1035 
1037  self,
1038  observation_coordinator: _ObservationUpdateCoordinatorT,
1039  *,
1040  context: Any = None,
1041  daily_coordinator: _DailyForecastUpdateCoordinatorT | None = None,
1042  hourly_coordinator: _HourlyForecastUpdateCoordinatorT | None = None,
1043  twice_daily_coordinator: _TwiceDailyForecastUpdateCoordinatorT | None = None,
1044  daily_forecast_valid: timedelta | None = None,
1045  hourly_forecast_valid: timedelta | None = None,
1046  twice_daily_forecast_valid: timedelta | None = None,
1047  ) -> None:
1048  """Initialize."""
1049  super().__init__(observation_coordinator, context)
1050  self.forecast_coordinatorsforecast_coordinators = {
1051  "daily": daily_coordinator,
1052  "hourly": hourly_coordinator,
1053  "twice_daily": twice_daily_coordinator,
1054  }
1055  self.forecast_validforecast_valid = {
1056  "daily": daily_forecast_valid,
1057  "hourly": hourly_forecast_valid,
1058  "twice_daily": twice_daily_forecast_valid,
1059  }
1060  self.unsub_forecast: dict[str, Callable[[], None] | None] = {
1061  "daily": None,
1062  "hourly": None,
1063  "twice_daily": None,
1064  }
1065 
1066  async def async_added_to_hass(self) -> None:
1067  """When entity is added to hass."""
1068  await super().async_added_to_hass()
1069  self.async_on_removeasync_on_remove(partial(self._remove_forecast_listener_remove_forecast_listener, "daily"))
1070  self.async_on_removeasync_on_remove(partial(self._remove_forecast_listener_remove_forecast_listener, "hourly"))
1071  self.async_on_removeasync_on_remove(partial(self._remove_forecast_listener_remove_forecast_listener, "twice_daily"))
1072 
1074  self, forecast_type: Literal["daily", "hourly", "twice_daily"]
1075  ) -> None:
1076  """Remove weather forecast listener."""
1077  if unsub_fn := self.unsub_forecast[forecast_type]:
1078  unsub_fn()
1079  self.unsub_forecast[forecast_type] = None
1080 
1081  @callback
1083  self,
1084  forecast_type: Literal["daily", "hourly", "twice_daily"],
1085  ) -> None:
1086  """Start subscription to forecast_type."""
1087  if not (coordinator := self.forecast_coordinatorsforecast_coordinators[forecast_type]):
1088  return
1089  self.unsub_forecast[forecast_type] = coordinator.async_add_listener(
1090  partial(self._handle_forecast_update_handle_forecast_update, forecast_type)
1091  )
1092 
1093  @callback
1095  """Handle updated data from the daily forecast coordinator."""
1096 
1097  @callback
1099  """Handle updated data from the hourly forecast coordinator."""
1100 
1101  @callback
1103  """Handle updated data from the twice daily forecast coordinator."""
1104 
1105  @final
1106  @callback
1108  self, forecast_type: Literal["daily", "hourly", "twice_daily"]
1109  ) -> None:
1110  """Update forecast data."""
1111  coordinator = self.forecast_coordinatorsforecast_coordinators[forecast_type]
1112  assert coordinator
1113  assert coordinator.config_entry is not None
1114  getattr(self, f"_handle_{forecast_type}_forecast_coordinator_update")()
1115  coordinator.config_entry.async_create_task(
1116  self.hasshass, self.async_update_listenersasync_update_listeners((forecast_type,))
1117  )
1118 
1119  @callback
1121  self,
1122  forecast_type: Literal["daily", "hourly", "twice_daily"],
1123  ) -> None:
1124  """End subscription to forecast_type."""
1125  self._remove_forecast_listener_remove_forecast_listener(forecast_type)
1126 
1127  @final
1129  self,
1130  coordinator: TimestampDataUpdateCoordinator[Any],
1131  forecast_valid_time: timedelta | None,
1132  ) -> bool:
1133  """Refresh stale forecast if needed."""
1134  if coordinator.update_interval is None:
1135  return True
1136  if forecast_valid_time is None:
1137  forecast_valid_time = coordinator.update_interval
1138  if (
1139  not (last_success_time := coordinator.last_update_success_time)
1140  or utcnow() - last_success_time >= coordinator.update_interval
1141  ):
1142  await coordinator.async_refresh()
1143  if (
1144  not (last_success_time := coordinator.last_update_success_time)
1145  or utcnow() - last_success_time >= forecast_valid_time
1146  ):
1147  return False
1148  return True
1149 
1150  @callback
1151  def _async_forecast_daily(self) -> list[Forecast] | None:
1152  """Return the daily forecast in native units."""
1153  raise NotImplementedError
1154 
1155  @callback
1156  def _async_forecast_hourly(self) -> list[Forecast] | None:
1157  """Return the hourly forecast in native units."""
1158  raise NotImplementedError
1159 
1160  @callback
1161  def _async_forecast_twice_daily(self) -> list[Forecast] | None:
1162  """Return the twice daily forecast in native units."""
1163  raise NotImplementedError
1164 
1165  @final
1166  async def _async_forecast(
1167  self, forecast_type: Literal["daily", "hourly", "twice_daily"]
1168  ) -> list[Forecast] | None:
1169  """Return the forecast in native units."""
1170  coordinator = self.forecast_coordinatorsforecast_coordinators[forecast_type]
1171  if coordinator and not await self._async_refresh_forecast_async_refresh_forecast(
1172  coordinator, self.forecast_validforecast_valid[forecast_type]
1173  ):
1174  return None
1175  return cast(
1176  list[Forecast] | None, getattr(self, f"_async_forecast_{forecast_type}")()
1177  )
1178 
1179  @final
1180  async def async_forecast_daily(self) -> list[Forecast] | None:
1181  """Return the daily forecast in native units."""
1182  return await self._async_forecast_async_forecast("daily")
1183 
1184  @final
1185  async def async_forecast_hourly(self) -> list[Forecast] | None:
1186  """Return the hourly forecast in native units."""
1187  return await self._async_forecast_async_forecast("hourly")
1188 
1189  @final
1190  async def async_forecast_twice_daily(self) -> list[Forecast] | None:
1191  """Return the twice daily forecast in native units."""
1192  return await self._async_forecast_async_forecast("twice_daily")
1193 
1194 
1196  CoordinatorWeatherEntity[
1197  _ObservationUpdateCoordinatorT, TimestampDataUpdateCoordinator[None]
1198  ],
1199 ):
1200  """A class for weather entities using a single DataUpdateCoordinators.
1201 
1202  This class is added as a convenience.
1203  """
1204 
1206  self,
1207  coordinator: _ObservationUpdateCoordinatorT,
1208  context: Any = None,
1209  ) -> None:
1210  """Initialize."""
1211  super().__init__(coordinator, context=context)
1212 
1213  @callback
1214  def _handle_coordinator_update(self) -> None:
1215  """Handle updated data from the coordinator."""
1216  super()._handle_coordinator_update()
1217  assert self.coordinator.config_entry
1218  self.coordinator.config_entry.async_create_task(
1219  self.hasshasshass, self.async_update_listenersasync_update_listenersasync_update_listeners(None)
1220  )
None _async_subscription_ended(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
Definition: __init__.py:1123
bool _async_refresh_forecast(self, TimestampDataUpdateCoordinator[Any] coordinator, timedelta|None forecast_valid_time)
Definition: __init__.py:1132
None _handle_forecast_update(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
Definition: __init__.py:1109
None __init__(self, _ObservationUpdateCoordinatorT observation_coordinator, *Any context=None, _DailyForecastUpdateCoordinatorT|None daily_coordinator=None, _HourlyForecastUpdateCoordinatorT|None hourly_coordinator=None, _TwiceDailyForecastUpdateCoordinatorT|None twice_daily_coordinator=None, timedelta|None daily_forecast_valid=None, timedelta|None hourly_forecast_valid=None, timedelta|None twice_daily_forecast_valid=None)
Definition: __init__.py:1047
None _remove_forecast_listener(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
Definition: __init__.py:1075
None _async_subscription_started(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
Definition: __init__.py:1085
list[Forecast]|None _async_forecast(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
Definition: __init__.py:1168
Any __call__(cls, *Any args, **Any kwargs)
Definition: __init__.py:237
None __post_init__(self, *Any args, **Any kwargs)
Definition: __init__.py:248
None __init__(self, _ObservationUpdateCoordinatorT coordinator, Any context=None)
Definition: __init__.py:1209
None _async_subscription_started(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
Definition: __init__.py:924
list[JsonValueType] _convert_forecast(self, list[Forecast] native_forecast_list)
Definition: __init__.py:685
None async_update_listeners(self, Iterable[Literal["daily", "hourly", "twice_daily"]]|None forecast_types)
Definition: __init__.py:961
list[Forecast]|None async_forecast_hourly(self)
Definition: __init__.py:506
list[Forecast]|None async_forecast_daily(self)
Definition: __init__.py:498
CALLBACK_TYPE async_subscribe_forecast(self, Literal["daily", "hourly", "twice_daily"] forecast_type, Callable[[list[JsonValueType]|None], None] forecast_listener)
Definition: __init__.py:940
None _async_subscription_ended(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
Definition: __init__.py:931
list[Forecast]|None async_forecast_twice_daily(self)
Definition: __init__.py:502
None __post_init__(self, *Any args, **Any kwargs)
Definition: __init__.py:311
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
bool remove(self, _T matcher)
Definition: match.py:214
ServiceResponse async_get_forecasts_service(WeatherEntity weather, ServiceCall service_call)
Definition: __init__.py:999
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:220
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:199
None raise_unsupported_forecast(str entity_id, str forecast_type)
Definition: __init__.py:990
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:225
float|None round_temperature(float|None temperature, float precision)
Definition: __init__.py:152