1 """Weather component that handles meteorological data for your location."""
3 from __future__
import annotations
6 from collections.abc
import Callable, Iterable
7 from contextlib
import suppress
8 from datetime
import timedelta
9 from functools
import partial
11 from typing
import Any, Final, Generic, Literal, Required, TypedDict, cast, final
13 from propcache
import cached_property
14 from typing_extensions
import TypeVar
15 import voluptuous
as vol
41 DataUpdateCoordinator,
42 TimestampDataUpdateCoordinator,
49 ATTR_WEATHER_APPARENT_TEMPERATURE,
50 ATTR_WEATHER_CLOUD_COVERAGE,
51 ATTR_WEATHER_DEW_POINT,
52 ATTR_WEATHER_HUMIDITY,
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,
73 from .websocket_api
import async_setup
as async_setup_ws_api
75 _LOGGER = logging.getLogger(__name__)
77 ENTITY_ID_FORMAT = DOMAIN +
".{}"
78 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
79 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
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"
123 ROUNDING_PRECISION = 2
125 SERVICE_GET_FORECASTS: Final =
"get_forecasts"
127 _ObservationUpdateCoordinatorT = TypeVar(
128 "_ObservationUpdateCoordinatorT",
129 bound=DataUpdateCoordinator[Any],
130 default=DataUpdateCoordinator[dict[str, Any]],
133 _DailyForecastUpdateCoordinatorT = TypeVar(
134 "_DailyForecastUpdateCoordinatorT",
135 bound=TimestampDataUpdateCoordinator[Any],
136 default=TimestampDataUpdateCoordinator[
None],
138 _HourlyForecastUpdateCoordinatorT = TypeVar(
139 "_HourlyForecastUpdateCoordinatorT",
140 bound=TimestampDataUpdateCoordinator[Any],
141 default=_DailyForecastUpdateCoordinatorT,
143 _TwiceDailyForecastUpdateCoordinatorT = TypeVar(
144 "_TwiceDailyForecastUpdateCoordinatorT",
145 bound=TimestampDataUpdateCoordinator[Any],
146 default=_DailyForecastUpdateCoordinatorT,
153 """Convert temperature into preferred precision for display."""
154 if temperature
is None:
158 if precision == PRECISION_HALVES:
159 temperature = round(temperature * 2) / 2.0
160 elif precision == PRECISION_TENTHS:
161 temperature = round(temperature, 1)
164 temperature = round(temperature)
170 """Typed weather forecast dict.
172 All attributes are in native units and old attributes kept
173 for backwards compatibility.
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
183 native_pressure: float |
None
185 native_temperature: float |
None
187 native_templow: float |
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
194 native_dew_point: float |
None
195 uv_index: float |
None
196 is_daytime: bool |
None
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
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,
209 WeatherEntityFeature.FORECAST_DAILY,
210 WeatherEntityFeature.FORECAST_HOURLY,
211 WeatherEntityFeature.FORECAST_TWICE_DAILY,
213 supports_response=SupportsResponse.ONLY,
215 async_setup_ws_api(hass)
216 await component.async_setup(config)
221 """Set up a config entry."""
226 """Unload a config entry."""
231 """A class that describes weather entities."""
235 """Meta class which calls __post_init__ after __new__ and __init__."""
237 def __call__(cls, *args: Any, **kwargs: Any) -> Any:
238 """Create an instance."""
239 instance: PostInit = super().
__call__(*args, **kwargs)
240 instance.__post_init__(*args, **kwargs)
245 """Class which calls __post_init__ after __new__ and __init__."""
249 """Finish initializing."""
252 CACHED_PROPERTIES_WITH_ATTR_ = {
253 "native_apparent_temperature",
254 "native_temperature",
255 "native_temperature_unit",
258 "native_pressure_unit",
260 "native_wind_gust_speed",
262 "native_wind_speed_unit",
268 "native_visibility_unit",
269 "native_precipitation_unit",
275 """ABC for weather data."""
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
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
300 _forecast_listeners: dict[
301 Literal[
"daily",
"hourly",
"twice_daily"],
302 list[Callable[[list[JsonValueType] |
None],
None]],
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
312 """Finish initializing."""
316 """Call when the weather entity is added to hass."""
324 """Return the apparent temperature in native units."""
325 return self._attr_native_apparent_temperature
329 """Return the temperature in native units."""
330 return self._attr_native_temperature
334 """Return the native unit of measurement for temperature."""
335 return self._attr_native_temperature_unit
339 """Return the dew point temperature in native units."""
340 return self._attr_native_dew_point
345 """Return the default unit of measurement for temperature.
347 Should not be set by integrations.
349 return self.
hasshass.config.units.temperature_unit
354 """Return the converted unit of measurement for temperature.
356 Should not be set by integrations.
361 return weather_option_temperature_unit
367 """Return the pressure in native units."""
368 return self._attr_native_pressure
372 """Return the native unit of measurement for pressure."""
373 return self._attr_native_pressure_unit
378 """Return the default unit of measurement for pressure.
380 Should not be set by integrations.
382 if self.
hasshass.config.units
is US_CUSTOMARY_SYSTEM:
383 return UnitOfPressure.INHG
384 return UnitOfPressure.HPA
389 """Return the converted unit of measurement for pressure.
391 Should not be set by integrations.
396 return weather_option_pressure_unit
402 """Return the humidity in native units."""
403 return self._attr_humidity
407 """Return the wind gust speed in native units."""
408 return self._attr_native_wind_gust_speed
412 """Return the wind speed in native units."""
413 return self._attr_native_wind_speed
417 """Return the native unit of measurement for wind speed."""
418 return self._attr_native_wind_speed_unit
423 """Return the default unit of measurement for wind speed.
425 Should not be set by integrations.
427 if self.
hasshass.config.units
is US_CUSTOMARY_SYSTEM:
428 return UnitOfSpeed.MILES_PER_HOUR
429 return UnitOfSpeed.KILOMETERS_PER_HOUR
434 """Return the converted unit of measurement for wind speed.
436 Should not be set by integrations.
441 return weather_option_wind_speed_unit
447 """Return the wind bearing."""
448 return self._attr_wind_bearing
452 """Return the ozone level."""
453 return self._attr_ozone
457 """Return the Cloud coverage in %."""
458 return self._attr_cloud_coverage
462 """Return the UV index."""
463 return self._attr_uv_index
467 """Return the visibility in native units."""
468 return self._attr_native_visibility
472 """Return the native unit of measurement for visibility."""
473 return self._attr_native_visibility_unit
478 """Return the default unit of measurement for visibility.
480 Should not be set by integrations.
482 return self.
hasshass.config.units.length_unit
487 """Return the converted unit of measurement for visibility.
489 Should not be set by integrations.
494 return weather_option_visibility_unit
499 """Return the daily forecast in native units."""
500 raise NotImplementedError
503 """Return the daily forecast in native units."""
504 raise NotImplementedError
507 """Return the hourly forecast in native units."""
508 raise NotImplementedError
512 """Return the native unit of measurement for accumulated precipitation."""
513 return self._attr_native_precipitation_unit
518 """Return the default unit of measurement for precipitation.
520 Should not be set by integrations.
522 return self.
hasshass.config.units.accumulated_precipitation_unit
527 """Return the converted unit of measurement for precipitation.
529 Should not be set by integrations.
534 return weather_option_precipitation_unit
540 """Return the precision of the temperature value, after unit conversion."""
541 if hasattr(self,
"_attr_precision"):
542 return self._attr_precision
552 """Return the state attributes, converted.
554 Attributes are configured from native units to user-configured units.
556 data: dict[str, Any] = {}
564 temperature_f =
float(temperature)
565 value_temp = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
566 temperature_f, from_unit, to_unit
569 value_temp, precision
571 except (TypeError, ValueError):
572 data[ATTR_WEATHER_TEMPERATURE] = temperature
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
583 value_apparent_temp, precision
585 except (TypeError, ValueError):
586 data[ATTR_WEATHER_APPARENT_TEMPERATURE] = apparent_temperature
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
597 value_dew_point, precision
599 except (TypeError, ValueError):
600 data[ATTR_WEATHER_DEW_POINT] = dew_point
604 if (humidity := self.
humidityhumidity)
is not None:
605 data[ATTR_WEATHER_HUMIDITY] = round(humidity)
607 if (ozone := self.
ozoneozone)
is not None:
608 data[ATTR_WEATHER_OZONE] = ozone
610 if (cloud_coverage := self.
cloud_coveragecloud_coverage)
is not None:
611 data[ATTR_WEATHER_CLOUD_COVERAGE] = cloud_coverage
613 if (uv_index := self.
uv_indexuv_index)
is not None:
614 data[ATTR_WEATHER_UV_INDEX] = uv_index
620 pressure_f =
float(pressure)
621 value_pressure = UNIT_CONVERSIONS[ATTR_WEATHER_PRESSURE_UNIT](
622 pressure_f, from_unit, to_unit
624 data[ATTR_WEATHER_PRESSURE] = round(value_pressure, ROUNDING_PRECISION)
625 except (TypeError, ValueError):
626 data[ATTR_WEATHER_PRESSURE] = pressure
628 data[ATTR_WEATHER_PRESSURE_UNIT] = self.
_pressure_unit_pressure_unit
630 if (wind_bearing := self.
wind_bearingwind_bearing)
is not None:
631 data[ATTR_WEATHER_WIND_BEARING] = wind_bearing
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
641 data[ATTR_WEATHER_WIND_GUST_SPEED] = round(
642 value_wind_gust_speed, ROUNDING_PRECISION
644 except (TypeError, ValueError):
645 data[ATTR_WEATHER_WIND_GUST_SPEED] = wind_gust_speed
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
655 data[ATTR_WEATHER_WIND_SPEED] = round(
656 value_wind_speed, ROUNDING_PRECISION
658 except (TypeError, ValueError):
659 data[ATTR_WEATHER_WIND_SPEED] = wind_speed
667 visibility_f =
float(visibility)
668 value_visibility = UNIT_CONVERSIONS[ATTR_WEATHER_VISIBILITY_UNIT](
669 visibility_f, from_unit, to_unit
671 data[ATTR_WEATHER_VISIBILITY] = round(
672 value_visibility, ROUNDING_PRECISION
674 except (TypeError, ValueError):
675 data[ATTR_WEATHER_VISIBILITY] = visibility
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] = []
693 for _forecast_entry
in native_forecast_list:
694 forecast_entry: dict[str, Any] =
dict(_forecast_entry)
696 temperature = forecast_entry.pop(
697 ATTR_FORECAST_NATIVE_TEMP, forecast_entry.get(ATTR_FORECAST_TEMP)
700 if temperature
is None:
701 forecast_entry[ATTR_FORECAST_TEMP] =
None
703 with suppress(TypeError, ValueError):
704 temperature_f =
float(temperature)
705 value_temp = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
711 value_temp, precision
715 forecast_apparent_temp := forecast_entry.pop(
716 ATTR_FORECAST_NATIVE_APPARENT_TEMP,
717 forecast_entry.get(ATTR_FORECAST_NATIVE_APPARENT_TEMP),
720 with suppress(TypeError, ValueError):
721 forecast_apparent_temp =
float(forecast_apparent_temp)
722 value_apparent_temp = UNIT_CONVERSIONS[
723 ATTR_WEATHER_TEMPERATURE_UNIT
725 forecast_apparent_temp,
731 value_apparent_temp, precision
735 forecast_temp_low := forecast_entry.pop(
736 ATTR_FORECAST_NATIVE_TEMP_LOW,
737 forecast_entry.get(ATTR_FORECAST_TEMP_LOW),
740 with suppress(TypeError, ValueError):
741 forecast_temp_low_f =
float(forecast_temp_low)
742 value_temp_low = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
749 value_temp_low, precision
753 forecast_dew_point := forecast_entry.pop(
754 ATTR_FORECAST_NATIVE_DEW_POINT,
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,
767 value_dew_point, precision
771 forecast_pressure := forecast_entry.pop(
772 ATTR_FORECAST_NATIVE_PRESSURE,
773 forecast_entry.get(ATTR_FORECAST_PRESSURE),
776 from_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](
792 forecast_wind_gust_speed := forecast_entry.pop(
793 ATTR_FORECAST_NATIVE_WIND_GUST_SPEED,
797 from_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,
813 forecast_wind_speed := forecast_entry.pop(
814 ATTR_FORECAST_NATIVE_WIND_SPEED,
815 forecast_entry.get(ATTR_FORECAST_WIND_SPEED),
818 from_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,
834 forecast_precipitation := forecast_entry.pop(
835 ATTR_FORECAST_NATIVE_PRECIPITATION,
836 forecast_entry.get(ATTR_FORECAST_PRECIPITATION),
839 from_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,
855 forecast_humidity := forecast_entry.pop(
856 ATTR_FORECAST_HUMIDITY,
860 with suppress(TypeError, ValueError):
861 forecast_humidity_f =
float(forecast_humidity)
862 forecast_entry[ATTR_FORECAST_HUMIDITY] = round(forecast_humidity_f)
864 converted_forecast_list.append(forecast_entry)
866 return converted_forecast_list
871 """Return the current state."""
876 """Return the current condition."""
877 return self._attr_condition
881 """Run when the entity registry entry has been updated."""
888 if weather_options := self.
registry_entryregistry_entry.options.get(DOMAIN):
890 custom_unit_temperature := weather_options.get(
891 ATTR_WEATHER_TEMPERATURE_UNIT
893 )
and custom_unit_temperature
in VALID_UNITS[ATTR_WEATHER_TEMPERATURE_UNIT]:
896 custom_unit_pressure := weather_options.get(ATTR_WEATHER_PRESSURE_UNIT)
897 )
and custom_unit_pressure
in VALID_UNITS[ATTR_WEATHER_PRESSURE_UNIT]:
900 custom_unit_precipitation := weather_options.get(
901 ATTR_WEATHER_PRECIPITATION_UNIT
903 )
and custom_unit_precipitation
in VALID_UNITS[
904 ATTR_WEATHER_PRECIPITATION_UNIT
908 custom_unit_wind_speed := weather_options.get(
909 ATTR_WEATHER_WIND_SPEED_UNIT
911 )
and custom_unit_wind_speed
in VALID_UNITS[ATTR_WEATHER_WIND_SPEED_UNIT]:
914 custom_unit_visibility := weather_options.get(
915 ATTR_WEATHER_VISIBILITY_UNIT
917 )
and custom_unit_visibility
in VALID_UNITS[ATTR_WEATHER_VISIBILITY_UNIT]:
923 forecast_type: Literal[
"daily",
"hourly",
"twice_daily"],
925 """Start subscription to forecast_type."""
930 forecast_type: Literal[
"daily",
"hourly",
"twice_daily"],
932 """End subscription to forecast_type."""
938 forecast_type: Literal[
"daily",
"hourly",
"twice_daily"],
939 forecast_listener: Callable[[list[JsonValueType] |
None],
None],
941 """Subscribe to forecast updates.
943 Called by websocket API.
947 if subscription_started:
951 def unsubscribe() -> None:
960 self, forecast_types: Iterable[Literal[
"daily",
"hourly",
"twice_daily"]] |
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:
969 native_forecast_list: list[Forecast] |
None = await getattr(
970 self, f
"async_forecast_{forecast_type}"
973 if native_forecast_list
is None:
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:
982 "is_daytime mandatory attribute for forecast_twice_daily is missing"
985 converted_forecast_list = self.
_convert_forecast_convert_forecast(native_forecast_list)
987 listener(converted_forecast_list)
991 """Raise error on attempt to get an unsupported forecast."""
993 f
"Weather entity '{entity_id}' does not support '{forecast_type}' forecast"
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:
1006 native_forecast_list = await weather.async_forecast_daily()
1007 elif forecast_type ==
"hourly":
1008 if (supported_features & WeatherEntityFeature.FORECAST_HOURLY) == 0:
1010 native_forecast_list = await weather.async_forecast_hourly()
1012 if (supported_features & WeatherEntityFeature.FORECAST_TWICE_DAILY) == 0:
1014 native_forecast_list = await weather.async_forecast_twice_daily()
1015 if native_forecast_list
is None:
1016 converted_forecast_list = []
1018 converted_forecast_list = weather._convert_forecast(native_forecast_list)
1020 "forecast": converted_forecast_list,
1025 CoordinatorEntity[_ObservationUpdateCoordinatorT],
1028 _ObservationUpdateCoordinatorT,
1029 _DailyForecastUpdateCoordinatorT,
1030 _HourlyForecastUpdateCoordinatorT,
1031 _TwiceDailyForecastUpdateCoordinatorT,
1034 """A class for weather entities using DataUpdateCoordinators."""
1038 observation_coordinator: _ObservationUpdateCoordinatorT,
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,
1049 super().
__init__(observation_coordinator, context)
1051 "daily": daily_coordinator,
1052 "hourly": hourly_coordinator,
1053 "twice_daily": twice_daily_coordinator,
1056 "daily": daily_forecast_valid,
1057 "hourly": hourly_forecast_valid,
1058 "twice_daily": twice_daily_forecast_valid,
1060 self.unsub_forecast: dict[str, Callable[[],
None] |
None] = {
1063 "twice_daily":
None,
1067 """When entity is added to hass."""
1074 self, forecast_type: Literal[
"daily",
"hourly",
"twice_daily"]
1076 """Remove weather forecast listener."""
1077 if unsub_fn := self.unsub_forecast[forecast_type]:
1079 self.unsub_forecast[forecast_type] =
None
1084 forecast_type: Literal[
"daily",
"hourly",
"twice_daily"],
1086 """Start subscription to forecast_type."""
1089 self.unsub_forecast[forecast_type] = coordinator.async_add_listener(
1095 """Handle updated data from the daily forecast coordinator."""
1099 """Handle updated data from the hourly forecast coordinator."""
1103 """Handle updated data from the twice daily forecast coordinator."""
1108 self, forecast_type: Literal[
"daily",
"hourly",
"twice_daily"]
1110 """Update forecast data."""
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(
1122 forecast_type: Literal[
"daily",
"hourly",
"twice_daily"],
1124 """End subscription to forecast_type."""
1130 coordinator: TimestampDataUpdateCoordinator[Any],
1131 forecast_valid_time: timedelta |
None,
1133 """Refresh stale forecast if needed."""
1134 if coordinator.update_interval
is None:
1136 if forecast_valid_time
is None:
1137 forecast_valid_time = coordinator.update_interval
1139 not (last_success_time := coordinator.last_update_success_time)
1140 or utcnow() - last_success_time >= coordinator.update_interval
1142 await coordinator.async_refresh()
1144 not (last_success_time := coordinator.last_update_success_time)
1145 or utcnow() - last_success_time >= forecast_valid_time
1152 """Return the daily forecast in native units."""
1153 raise NotImplementedError
1157 """Return the hourly forecast in native units."""
1158 raise NotImplementedError
1162 """Return the twice daily forecast in native units."""
1163 raise NotImplementedError
1167 self, forecast_type: Literal[
"daily",
"hourly",
"twice_daily"]
1168 ) -> list[Forecast] |
None:
1169 """Return the forecast in native units."""
1176 list[Forecast] |
None, getattr(self, f
"_async_forecast_{forecast_type}")()
1181 """Return the daily forecast in native units."""
1186 """Return the hourly forecast in native units."""
1191 """Return the twice daily forecast in native units."""
1196 CoordinatorWeatherEntity[
1197 _ObservationUpdateCoordinatorT, TimestampDataUpdateCoordinator[
None]
1200 """A class for weather entities using a single DataUpdateCoordinators.
1202 This class is added as a convenience.
1207 coordinator: _ObservationUpdateCoordinatorT,
1208 context: Any =
None,
1211 super().
__init__(coordinator, context=context)
1215 """Handle updated data from the coordinator."""
1217 assert self.coordinator.config_entry
1218 self.coordinator.config_entry.async_create_task(
None async_added_to_hass(self)
None _async_subscription_ended(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
list[Forecast]|None async_forecast_daily(self)
bool _async_refresh_forecast(self, TimestampDataUpdateCoordinator[Any] coordinator, timedelta|None forecast_valid_time)
list[Forecast]|None _async_forecast_hourly(self)
list[Forecast]|None async_forecast_hourly(self)
None _handle_forecast_update(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
list[Forecast]|None _async_forecast_daily(self)
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)
None _remove_forecast_listener(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
None _handle_daily_forecast_coordinator_update(self)
None _handle_twice_daily_forecast_coordinator_update(self)
None _handle_hourly_forecast_coordinator_update(self)
list[Forecast]|None async_forecast_twice_daily(self)
None _async_subscription_started(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
list[Forecast]|None _async_forecast_twice_daily(self)
list[Forecast]|None _async_forecast(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
Any __call__(cls, *Any args, **Any kwargs)
None __post_init__(self, *Any args, **Any kwargs)
None __init__(self, _ObservationUpdateCoordinatorT coordinator, Any context=None)
None _handle_coordinator_update(self)
None _async_subscription_started(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
list[JsonValueType] _convert_forecast(self, list[Forecast] native_forecast_list)
None async_update_listeners(self, Iterable[Literal["daily", "hourly", "twice_daily"]]|None forecast_types)
str _temperature_unit(self)
float|None humidity(self)
_weather_option_visibility_unit
str _visibility_unit(self)
str _default_pressure_unit(self)
float|None native_temperature(self)
list[Forecast]|None async_forecast_hourly(self)
dict[str, Any] state_attributes(self)
float|None native_dew_point(self)
str _default_wind_speed_unit(self)
str|None native_temperature_unit(self)
str|None native_pressure_unit(self)
list[Forecast]|None async_forecast_daily(self)
float|None native_wind_gust_speed(self)
str _default_precipitation_unit(self)
_weather_option_temperature_unit
str|None native_visibility_unit(self)
_weather_option_pressure_unit
float|None native_visibility(self)
_weather_option_precipitation_unit
float|str|None wind_bearing(self)
str _default_temperature_unit(self)
str|None native_precipitation_unit(self)
CALLBACK_TYPE async_subscribe_forecast(self, Literal["daily", "hourly", "twice_daily"] forecast_type, Callable[[list[JsonValueType]|None], None] forecast_listener)
None _async_subscription_ended(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
_weather_option_wind_speed_unit
list[Forecast]|None async_forecast_twice_daily(self)
float|None native_apparent_temperature(self)
None __post_init__(self, *Any args, **Any kwargs)
None async_internal_added_to_hass(self)
float|None uv_index(self)
str|None native_wind_speed_unit(self)
str _precipitation_unit(self)
None async_registry_entry_updated(self)
float|None native_pressure(self)
str _wind_speed_unit(self)
float|None native_wind_speed(self)
float|None cloud_coverage(self)
str _default_visibility_unit(self)
None async_on_remove(self, CALLBACK_TYPE func)
None async_registry_entry_updated(self)
None async_update_listeners(self)
bool remove(self, _T matcher)
ServiceResponse async_get_forecasts_service(WeatherEntity weather, ServiceCall service_call)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
bool async_setup(HomeAssistant hass, ConfigType config)
None raise_unsupported_forecast(str entity_id, str forecast_type)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
float|None round_temperature(float|None temperature, float precision)