1 """Template platform that aggregates meteorological data."""
3 from __future__
import annotations
5 from dataclasses
import asdict, dataclass
6 from functools
import partial
7 from typing
import Any, Literal, Self
9 import voluptuous
as vol
12 ATTR_CONDITION_CLEAR_NIGHT,
13 ATTR_CONDITION_CLOUDY,
14 ATTR_CONDITION_EXCEPTIONAL,
17 ATTR_CONDITION_LIGHTNING,
18 ATTR_CONDITION_LIGHTNING_RAINY,
19 ATTR_CONDITION_PARTLYCLOUDY,
20 ATTR_CONDITION_POURING,
23 ATTR_CONDITION_SNOWY_RAINY,
26 ATTR_CONDITION_WINDY_VARIANT,
27 DOMAIN
as WEATHER_DOMAIN,
29 PLATFORM_SCHEMA
as WEATHER_PLATFORM_SCHEMA,
36 CONF_TEMPERATURE_UNIT,
55 from .coordinator
import TriggerUpdateCoordinator
56 from .template_entity
import TemplateEntity, rewrite_common_legacy_to_modern_conf
57 from .trigger_entity
import TriggerEntity
59 CHECK_FORECAST_KEYS = (
61 .union(Forecast.__annotations__.keys())
64 .union((
"apparent_temperature",
"wind_gust_speed",
"dew_point"))
68 ATTR_CONDITION_CLEAR_NIGHT,
69 ATTR_CONDITION_CLOUDY,
72 ATTR_CONDITION_LIGHTNING,
73 ATTR_CONDITION_LIGHTNING_RAINY,
74 ATTR_CONDITION_PARTLYCLOUDY,
75 ATTR_CONDITION_POURING,
78 ATTR_CONDITION_SNOWY_RAINY,
81 ATTR_CONDITION_WINDY_VARIANT,
82 ATTR_CONDITION_EXCEPTIONAL,
85 CONF_WEATHER =
"weather"
86 CONF_TEMPERATURE_TEMPLATE =
"temperature_template"
87 CONF_HUMIDITY_TEMPLATE =
"humidity_template"
88 CONF_CONDITION_TEMPLATE =
"condition_template"
89 CONF_ATTRIBUTION_TEMPLATE =
"attribution_template"
90 CONF_PRESSURE_TEMPLATE =
"pressure_template"
91 CONF_WIND_SPEED_TEMPLATE =
"wind_speed_template"
92 CONF_WIND_BEARING_TEMPLATE =
"wind_bearing_template"
93 CONF_OZONE_TEMPLATE =
"ozone_template"
94 CONF_VISIBILITY_TEMPLATE =
"visibility_template"
95 CONF_FORECAST_DAILY_TEMPLATE =
"forecast_daily_template"
96 CONF_FORECAST_HOURLY_TEMPLATE =
"forecast_hourly_template"
97 CONF_FORECAST_TWICE_DAILY_TEMPLATE =
"forecast_twice_daily_template"
98 CONF_PRESSURE_UNIT =
"pressure_unit"
99 CONF_WIND_SPEED_UNIT =
"wind_speed_unit"
100 CONF_VISIBILITY_UNIT =
"visibility_unit"
101 CONF_PRECIPITATION_UNIT =
"precipitation_unit"
102 CONF_WIND_GUST_SPEED_TEMPLATE =
"wind_gust_speed_template"
103 CONF_CLOUD_COVERAGE_TEMPLATE =
"cloud_coverage_template"
104 CONF_DEW_POINT_TEMPLATE =
"dew_point_template"
105 CONF_APPARENT_TEMPERATURE_TEMPLATE =
"apparent_temperature_template"
107 WEATHER_SCHEMA = vol.Schema(
109 vol.Required(CONF_NAME): cv.template,
110 vol.Required(CONF_CONDITION_TEMPLATE): cv.template,
111 vol.Required(CONF_TEMPERATURE_TEMPLATE): cv.template,
112 vol.Required(CONF_HUMIDITY_TEMPLATE): cv.template,
113 vol.Optional(CONF_ATTRIBUTION_TEMPLATE): cv.template,
114 vol.Optional(CONF_PRESSURE_TEMPLATE): cv.template,
115 vol.Optional(CONF_WIND_SPEED_TEMPLATE): cv.template,
116 vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template,
117 vol.Optional(CONF_OZONE_TEMPLATE): cv.template,
118 vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template,
119 vol.Optional(CONF_FORECAST_DAILY_TEMPLATE): cv.template,
120 vol.Optional(CONF_FORECAST_HOURLY_TEMPLATE): cv.template,
121 vol.Optional(CONF_FORECAST_TWICE_DAILY_TEMPLATE): cv.template,
122 vol.Optional(CONF_UNIQUE_ID): cv.string,
123 vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(TemperatureConverter.VALID_UNITS),
124 vol.Optional(CONF_PRESSURE_UNIT): vol.In(PressureConverter.VALID_UNITS),
125 vol.Optional(CONF_WIND_SPEED_UNIT): vol.In(SpeedConverter.VALID_UNITS),
126 vol.Optional(CONF_VISIBILITY_UNIT): vol.In(DistanceConverter.VALID_UNITS),
127 vol.Optional(CONF_PRECIPITATION_UNIT): vol.In(DistanceConverter.VALID_UNITS),
128 vol.Optional(CONF_WIND_GUST_SPEED_TEMPLATE): cv.template,
129 vol.Optional(CONF_CLOUD_COVERAGE_TEMPLATE): cv.template,
130 vol.Optional(CONF_DEW_POINT_TEMPLATE): cv.template,
131 vol.Optional(CONF_APPARENT_TEMPERATURE_TEMPLATE): cv.template,
135 PLATFORM_SCHEMA = WEATHER_PLATFORM_SCHEMA.extend(WEATHER_SCHEMA.schema)
141 async_add_entities: AddEntitiesCallback,
142 discovery_info: DiscoveryInfoType |
None =
None,
144 """Set up the Template weather."""
145 if discovery_info
and "coordinator" in discovery_info:
148 for config
in discovery_info[
"entities"]
153 unique_id = config.get(CONF_UNIQUE_ID)
167 """Representation of a weather condition."""
169 _attr_should_poll =
False
175 unique_id: str |
None,
177 """Initialize the Template weather."""
178 super().
__init__(hass, config=config, unique_id=unique_id)
193 CONF_FORECAST_TWICE_DAILY_TEMPLATE
199 CONF_APPARENT_TEMPERATURE_TEMPLATE
223 self._forecast_daily: list[Forecast] = []
224 self._forecast_hourly: list[Forecast] = []
225 self._forecast_twice_daily: list[Forecast] = []
237 """Return the current condition."""
242 """Return the temperature."""
247 """Return the humidity."""
252 """Return the wind speed."""
257 """Return the wind bearing."""
262 """Return the ozone level."""
267 """Return the visibility."""
272 """Return the air pressure."""
277 """Return the wind gust speed."""
282 """Return the cloud coverage."""
287 """Return the dew point."""
292 """Return the apparent temperature."""
296 """Return the daily forecast in native units."""
297 return self._forecast_daily
300 """Return the daily forecast in native units."""
301 return self._forecast_hourly
304 """Return the daily forecast in native units."""
305 return self._forecast_twice_daily
309 """Return the attribution."""
311 return "Powered by Home Assistant"
316 """Set up templates."""
322 lambda condition: condition
if condition
in CONDITION_CLASSES
else None,
381 "_apparent_temperature",
401 "_forecast_twice_daily",
412 forecast_type: Literal[
"daily",
"hourly",
"twice_daily"],
413 result: list[Forecast] | TemplateError,
415 """Save template result and trigger forecast listener."""
416 attr_result =
None if isinstance(result, TemplateError)
else result
417 setattr(self, f
"_forecast_{forecast_type}", attr_result)
418 self.
hasshass.async_create_task(
425 forecast_type: Literal[
"daily",
"hourly",
"twice_daily"],
427 ) -> list[Forecast] |
None:
428 """Validate the forecasts."""
432 if not isinstance(result, list):
434 "Forecasts is not a list, see Weather documentation https://www.home-assistant.io/integrations/weather/"
436 for forecast
in result:
437 if not isinstance(forecast, dict):
439 "Forecast in list is not a dict, see Weather documentation https://www.home-assistant.io/integrations/weather/"
441 diff_result = set().union(forecast.keys()).difference(CHECK_FORECAST_KEYS)
444 f
"Only valid keys in Forecast are allowed, unallowed keys: ({diff_result}), "
445 "see Weather documentation https://www.home-assistant.io/integrations/weather/"
447 if forecast_type ==
"twice_daily" and "is_daytime" not in forecast:
449 "`is_daytime` is missing in twice_daily forecast, see Weather documentation https://www.home-assistant.io/integrations/weather/"
451 if "datetime" not in forecast:
453 "`datetime` is required in forecasts, see Weather documentation https://www.home-assistant.io/integrations/weather/"
459 @dataclass(kw_only=True)
461 """Object to hold extra stored data."""
463 last_apparent_temperature: float |
None
464 last_cloud_coverage: int |
None
465 last_dew_point: float |
None
466 last_humidity: float |
None
467 last_ozone: float |
None
468 last_pressure: float |
None
469 last_temperature: float |
None
470 last_visibility: float |
None
471 last_wind_bearing: float | str |
None
472 last_wind_gust_speed: float |
None
473 last_wind_speed: float |
None
476 """Return a dict representation of the event data."""
480 def from_dict(cls, restored: dict[str, Any]) -> Self |
None:
481 """Initialize a stored event state from a dict."""
484 last_apparent_temperature=restored[
"last_apparent_temperature"],
485 last_cloud_coverage=restored[
"last_cloud_coverage"],
486 last_dew_point=restored[
"last_dew_point"],
487 last_humidity=restored[
"last_humidity"],
488 last_ozone=restored[
"last_ozone"],
489 last_pressure=restored[
"last_pressure"],
490 last_temperature=restored[
"last_temperature"],
491 last_visibility=restored[
"last_visibility"],
492 last_wind_bearing=restored[
"last_wind_bearing"],
493 last_wind_gust_speed=restored[
"last_wind_gust_speed"],
494 last_wind_speed=restored[
"last_wind_speed"],
501 """Sensor entity based on trigger data."""
503 domain = WEATHER_DOMAIN
504 extra_template_keys = (
505 CONF_CONDITION_TEMPLATE,
506 CONF_TEMPERATURE_TEMPLATE,
507 CONF_HUMIDITY_TEMPLATE,
513 coordinator: TriggerUpdateCoordinator,
517 super().
__init__(hass, coordinator, config)
525 if config.get(CONF_FORECAST_DAILY_TEMPLATE):
527 if config.get(CONF_FORECAST_HOURLY_TEMPLATE):
529 if config.get(CONF_FORECAST_TWICE_DAILY_TEMPLATE):
533 CONF_APPARENT_TEMPERATURE_TEMPLATE,
534 CONF_CLOUD_COVERAGE_TEMPLATE,
535 CONF_DEW_POINT_TEMPLATE,
536 CONF_FORECAST_DAILY_TEMPLATE,
537 CONF_FORECAST_HOURLY_TEMPLATE,
538 CONF_FORECAST_TWICE_DAILY_TEMPLATE,
540 CONF_PRESSURE_TEMPLATE,
541 CONF_VISIBILITY_TEMPLATE,
542 CONF_WIND_BEARING_TEMPLATE,
543 CONF_WIND_GUST_SPEED_TEMPLATE,
544 CONF_WIND_SPEED_TEMPLATE,
546 if isinstance(config.get(key), template.Template):
547 self._to_render_simple.append(key)
548 self._parse_result.
add(key)
551 """Restore last state."""
555 and state.state
is not None
556 and state.state
not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
559 self._rendered[CONF_APPARENT_TEMPERATURE_TEMPLATE] = (
560 weather_data.last_apparent_temperature
562 self._rendered[CONF_CLOUD_COVERAGE_TEMPLATE] = (
563 weather_data.last_cloud_coverage
565 self._rendered[CONF_CONDITION_TEMPLATE] = state.state
566 self._rendered[CONF_DEW_POINT_TEMPLATE] = weather_data.last_dew_point
567 self._rendered[CONF_HUMIDITY_TEMPLATE] = weather_data.last_humidity
568 self._rendered[CONF_OZONE_TEMPLATE] = weather_data.last_ozone
569 self._rendered[CONF_PRESSURE_TEMPLATE] = weather_data.last_pressure
570 self._rendered[CONF_TEMPERATURE_TEMPLATE] = weather_data.last_temperature
571 self._rendered[CONF_VISIBILITY_TEMPLATE] = weather_data.last_visibility
572 self._rendered[CONF_WIND_BEARING_TEMPLATE] = weather_data.last_wind_bearing
573 self._rendered[CONF_WIND_GUST_SPEED_TEMPLATE] = (
574 weather_data.last_wind_gust_speed
576 self._rendered[CONF_WIND_SPEED_TEMPLATE] = weather_data.last_wind_speed
580 """Return the current condition."""
581 return self._rendered.
get(CONF_CONDITION_TEMPLATE)
585 """Return the temperature."""
586 return vol.Any(vol.Coerce(float),
None)(
587 self._rendered.
get(CONF_TEMPERATURE_TEMPLATE)
592 """Return the humidity."""
593 return vol.Any(vol.Coerce(float),
None)(
594 self._rendered.
get(CONF_HUMIDITY_TEMPLATE)
599 """Return the wind speed."""
600 return vol.Any(vol.Coerce(float),
None)(
601 self._rendered.
get(CONF_WIND_SPEED_TEMPLATE)
606 """Return the wind bearing."""
607 return vol.Any(vol.Coerce(float), vol.Coerce(str),
None)(
608 self._rendered.
get(CONF_WIND_BEARING_TEMPLATE)
613 """Return the ozone level."""
614 return vol.Any(vol.Coerce(float),
None)(
615 self._rendered.
get(CONF_OZONE_TEMPLATE),
620 """Return the visibility."""
621 return vol.Any(vol.Coerce(float),
None)(
622 self._rendered.
get(CONF_VISIBILITY_TEMPLATE)
627 """Return the air pressure."""
628 return vol.Any(vol.Coerce(float),
None)(
629 self._rendered.
get(CONF_PRESSURE_TEMPLATE)
634 """Return the wind gust speed."""
635 return vol.Any(vol.Coerce(float),
None)(
636 self._rendered.
get(CONF_WIND_GUST_SPEED_TEMPLATE)
641 """Return the cloud coverage."""
642 return vol.Any(vol.Coerce(float),
None)(
643 self._rendered.
get(CONF_CLOUD_COVERAGE_TEMPLATE)
648 """Return the dew point."""
649 return vol.Any(vol.Coerce(float),
None)(
650 self._rendered.
get(CONF_DEW_POINT_TEMPLATE)
655 """Return the apparent temperature."""
656 return vol.Any(vol.Coerce(float),
None)(
657 self._rendered.
get(CONF_APPARENT_TEMPERATURE_TEMPLATE)
661 """Return the daily forecast in native units."""
662 return vol.Any(vol.Coerce(list),
None)(
663 self._rendered.
get(CONF_FORECAST_DAILY_TEMPLATE)
667 """Return the daily forecast in native units."""
668 return vol.Any(vol.Coerce(list),
None)(
669 self._rendered.
get(CONF_FORECAST_HOURLY_TEMPLATE)
673 """Return the daily forecast in native units."""
674 return vol.Any(vol.Coerce(list),
None)(
675 self._rendered.
get(CONF_FORECAST_TWICE_DAILY_TEMPLATE)
680 """Return weather specific state data to be restored."""
682 last_apparent_temperature=self._rendered.
get(
683 CONF_APPARENT_TEMPERATURE_TEMPLATE
685 last_cloud_coverage=self._rendered.
get(CONF_CLOUD_COVERAGE_TEMPLATE),
686 last_dew_point=self._rendered.
get(CONF_DEW_POINT_TEMPLATE),
687 last_humidity=self._rendered.
get(CONF_HUMIDITY_TEMPLATE),
688 last_ozone=self._rendered.
get(CONF_OZONE_TEMPLATE),
689 last_pressure=self._rendered.
get(CONF_PRESSURE_TEMPLATE),
690 last_temperature=self._rendered.
get(CONF_TEMPERATURE_TEMPLATE),
691 last_visibility=self._rendered.
get(CONF_VISIBILITY_TEMPLATE),
692 last_wind_bearing=self._rendered.
get(CONF_WIND_BEARING_TEMPLATE),
693 last_wind_gust_speed=self._rendered.
get(CONF_WIND_GUST_SPEED_TEMPLATE),
694 last_wind_speed=self._rendered.
get(CONF_WIND_SPEED_TEMPLATE),
698 """Restore weather specific state data."""
701 return WeatherExtraStoredData.from_dict(restored_last_extra_data.as_dict())
None add_template_attribute(self, str attribute, Template template, Callable[[Any], Any]|None validator=None, Callable[[Any], None]|None on_update=None, bool none_on_template_error=False)
float|None native_visibility(self)
None async_added_to_hass(self)
float|None native_dew_point(self)
float|None cloud_coverage(self)
float|None native_wind_gust_speed(self)
_attr_native_precipitation_unit
_attr_native_wind_speed_unit
_attr_native_temperature_unit
float|None native_wind_speed(self)
float|str|None wind_bearing(self)
float|None native_temperature(self)
list[Forecast] async_forecast_twice_daily(self)
_attr_native_visibility_unit
float|None humidity(self)
WeatherExtraStoredData|None async_get_last_weather_data(self)
None __init__(self, HomeAssistant hass, TriggerUpdateCoordinator coordinator, ConfigType config)
list[Forecast] async_forecast_hourly(self)
WeatherExtraStoredData extra_restore_state_data(self)
float|None native_pressure(self)
float|None native_apparent_temperature(self)
_attr_native_pressure_unit
list[Forecast] async_forecast_daily(self)
list[Forecast] async_forecast_twice_daily(self)
None _async_setup_templates(self)
_apparent_temperature_template
float|None native_wind_gust_speed(self)
float|None native_apparent_temperature(self)
None _update_forecast(self, Literal["daily", "hourly", "twice_daily"] forecast_type, list[Forecast]|TemplateError result)
None __init__(self, HomeAssistant hass, ConfigType config, str|None unique_id)
_attr_native_visibility_unit
_attr_native_pressure_unit
float|None cloud_coverage(self)
_attr_native_wind_speed_unit
float|None native_temperature(self)
list[Forecast] async_forecast_hourly(self)
float|None native_pressure(self)
_attr_native_precipitation_unit
_attr_native_temperature_unit
_forecast_hourly_template
float|None native_wind_speed(self)
_forecast_twice_daily_template
list[Forecast]|None _validate_forecast(self, Literal["daily", "hourly", "twice_daily"] forecast_type, Any result)
float|str|None wind_bearing(self)
str|None attribution(self)
float|None native_visibility(self)
list[Forecast] async_forecast_daily(self)
float|None native_dew_point(self)
_wind_gust_speed_template
float|None humidity(self)
None async_update_listeners(self, Iterable[Literal["daily", "hourly", "twice_daily"]]|None forecast_types)
State|None async_get_last_state(self)
ExtraStoredData|None async_get_last_extra_data(self)
bool add(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
dict[str, Any] rewrite_common_legacy_to_modern_conf(HomeAssistant hass, dict[str, Any] entity_cfg, dict[str, str]|None extra_legacy_fields=None)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
str async_generate_entity_id(str entity_id_format, str|None name, Iterable[str]|None current_ids=None, HomeAssistant|None hass=None)