mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 00:37:13 +00:00
Add trigger weather template (#100824)
* Add trigger weather template * Add tests * mod dataclass * Remove legacy forecast * Fix test failure * sorting * add hourly test * Add tests * Add and fix tests * Improve tests --------- Co-authored-by: Erik <erik@montnemery.com>
This commit is contained in:
parent
fd53e116bb
commit
43954d660b
@ -9,6 +9,7 @@ from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN
|
|||||||
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
|
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
|
||||||
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
|
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
|
||||||
from homeassistant.config import async_log_exception, config_without_domain
|
from homeassistant.config import async_log_exception, config_without_domain
|
||||||
from homeassistant.const import CONF_BINARY_SENSORS, CONF_SENSORS, CONF_UNIQUE_ID
|
from homeassistant.const import CONF_BINARY_SENSORS, CONF_SENSORS, CONF_UNIQUE_ID
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
@ -21,6 +22,7 @@ from . import (
|
|||||||
number as number_platform,
|
number as number_platform,
|
||||||
select as select_platform,
|
select as select_platform,
|
||||||
sensor as sensor_platform,
|
sensor as sensor_platform,
|
||||||
|
weather as weather_platform,
|
||||||
)
|
)
|
||||||
from .const import CONF_ACTION, CONF_TRIGGER, DOMAIN
|
from .const import CONF_ACTION, CONF_TRIGGER, DOMAIN
|
||||||
|
|
||||||
@ -55,6 +57,9 @@ CONFIG_SECTION_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(IMAGE_DOMAIN): vol.All(
|
vol.Optional(IMAGE_DOMAIN): vol.All(
|
||||||
cv.ensure_list, [image_platform.IMAGE_SCHEMA]
|
cv.ensure_list, [image_platform.IMAGE_SCHEMA]
|
||||||
),
|
),
|
||||||
|
vol.Optional(WEATHER_DOMAIN): vol.All(
|
||||||
|
cv.ensure_list, [weather_platform.WEATHER_SCHEMA]
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
"""Template platform that aggregates meteorological data."""
|
"""Template platform that aggregates meteorological data."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal, Self
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -22,18 +23,27 @@ from homeassistant.components.weather import (
|
|||||||
ATTR_CONDITION_SUNNY,
|
ATTR_CONDITION_SUNNY,
|
||||||
ATTR_CONDITION_WINDY,
|
ATTR_CONDITION_WINDY,
|
||||||
ATTR_CONDITION_WINDY_VARIANT,
|
ATTR_CONDITION_WINDY_VARIANT,
|
||||||
|
DOMAIN as WEATHER_DOMAIN,
|
||||||
ENTITY_ID_FORMAT,
|
ENTITY_ID_FORMAT,
|
||||||
Forecast,
|
Forecast,
|
||||||
WeatherEntity,
|
WeatherEntity,
|
||||||
WeatherEntityFeature,
|
WeatherEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_NAME, CONF_TEMPERATURE_UNIT, CONF_UNIQUE_ID
|
from homeassistant.const import (
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_TEMPERATURE_UNIT,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
|
from homeassistant.helpers import template
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
|
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
|
||||||
from homeassistant.helpers.entity import async_generate_entity_id
|
from homeassistant.helpers.entity import async_generate_entity_id
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
from homeassistant.util.unit_conversion import (
|
from homeassistant.util.unit_conversion import (
|
||||||
DistanceConverter,
|
DistanceConverter,
|
||||||
@ -42,7 +52,9 @@ from homeassistant.util.unit_conversion import (
|
|||||||
TemperatureConverter,
|
TemperatureConverter,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .coordinator import TriggerUpdateCoordinator
|
||||||
from .template_entity import TemplateEntity, rewrite_common_legacy_to_modern_conf
|
from .template_entity import TemplateEntity, rewrite_common_legacy_to_modern_conf
|
||||||
|
from .trigger_entity import TriggerEntity
|
||||||
|
|
||||||
CHECK_FORECAST_KEYS = (
|
CHECK_FORECAST_KEYS = (
|
||||||
set().union(Forecast.__annotations__.keys())
|
set().union(Forecast.__annotations__.keys())
|
||||||
@ -92,40 +104,38 @@ CONF_CLOUD_COVERAGE_TEMPLATE = "cloud_coverage_template"
|
|||||||
CONF_DEW_POINT_TEMPLATE = "dew_point_template"
|
CONF_DEW_POINT_TEMPLATE = "dew_point_template"
|
||||||
CONF_APPARENT_TEMPERATURE_TEMPLATE = "apparent_temperature_template"
|
CONF_APPARENT_TEMPERATURE_TEMPLATE = "apparent_temperature_template"
|
||||||
|
|
||||||
|
WEATHER_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_NAME): cv.template,
|
||||||
|
vol.Required(CONF_CONDITION_TEMPLATE): cv.template,
|
||||||
|
vol.Required(CONF_TEMPERATURE_TEMPLATE): cv.template,
|
||||||
|
vol.Required(CONF_HUMIDITY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_ATTRIBUTION_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_PRESSURE_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_WIND_SPEED_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_OZONE_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_FORECAST_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_FORECAST_DAILY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_FORECAST_HOURLY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_FORECAST_TWICE_DAILY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
|
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(TemperatureConverter.VALID_UNITS),
|
||||||
|
vol.Optional(CONF_PRESSURE_UNIT): vol.In(PressureConverter.VALID_UNITS),
|
||||||
|
vol.Optional(CONF_WIND_SPEED_UNIT): vol.In(SpeedConverter.VALID_UNITS),
|
||||||
|
vol.Optional(CONF_VISIBILITY_UNIT): vol.In(DistanceConverter.VALID_UNITS),
|
||||||
|
vol.Optional(CONF_PRECIPITATION_UNIT): vol.In(DistanceConverter.VALID_UNITS),
|
||||||
|
vol.Optional(CONF_WIND_GUST_SPEED_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_CLOUD_COVERAGE_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_DEW_POINT_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_APPARENT_TEMPERATURE_TEMPLATE): cv.template,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = vol.All(
|
PLATFORM_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_FORECAST_TEMPLATE),
|
cv.deprecated(CONF_FORECAST_TEMPLATE),
|
||||||
PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA.extend(WEATHER_SCHEMA.schema),
|
||||||
{
|
|
||||||
vol.Required(CONF_NAME): cv.string,
|
|
||||||
vol.Required(CONF_CONDITION_TEMPLATE): cv.template,
|
|
||||||
vol.Required(CONF_TEMPERATURE_TEMPLATE): cv.template,
|
|
||||||
vol.Required(CONF_HUMIDITY_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_ATTRIBUTION_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_PRESSURE_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_WIND_SPEED_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_OZONE_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_FORECAST_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_FORECAST_DAILY_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_FORECAST_HOURLY_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_FORECAST_TWICE_DAILY_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
|
||||||
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(
|
|
||||||
TemperatureConverter.VALID_UNITS
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_PRESSURE_UNIT): vol.In(PressureConverter.VALID_UNITS),
|
|
||||||
vol.Optional(CONF_WIND_SPEED_UNIT): vol.In(SpeedConverter.VALID_UNITS),
|
|
||||||
vol.Optional(CONF_VISIBILITY_UNIT): vol.In(DistanceConverter.VALID_UNITS),
|
|
||||||
vol.Optional(CONF_PRECIPITATION_UNIT): vol.In(
|
|
||||||
DistanceConverter.VALID_UNITS
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_WIND_GUST_SPEED_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_CLOUD_COVERAGE_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_DEW_POINT_TEMPLATE): cv.template,
|
|
||||||
vol.Optional(CONF_APPARENT_TEMPERATURE_TEMPLATE): cv.template,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -136,6 +146,12 @@ async def async_setup_platform(
|
|||||||
discovery_info: DiscoveryInfoType | None = None,
|
discovery_info: DiscoveryInfoType | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Template weather."""
|
"""Set up the Template weather."""
|
||||||
|
if discovery_info and "coordinator" in discovery_info:
|
||||||
|
async_add_entities(
|
||||||
|
TriggerWeatherEntity(hass, discovery_info["coordinator"], config)
|
||||||
|
for config in discovery_info["entities"]
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
config = rewrite_common_legacy_to_modern_conf(config)
|
config = rewrite_common_legacy_to_modern_conf(config)
|
||||||
unique_id = config.get(CONF_UNIQUE_ID)
|
unique_id = config.get(CONF_UNIQUE_ID)
|
||||||
@ -452,3 +468,248 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class WeatherExtraStoredData(ExtraStoredData):
|
||||||
|
"""Object to hold extra stored data."""
|
||||||
|
|
||||||
|
last_apparent_temperature: float | None
|
||||||
|
last_cloud_coverage: int | None
|
||||||
|
last_dew_point: float | None
|
||||||
|
last_humidity: float | None
|
||||||
|
last_ozone: float | None
|
||||||
|
last_pressure: float | None
|
||||||
|
last_temperature: float | None
|
||||||
|
last_visibility: float | None
|
||||||
|
last_wind_bearing: float | str | None
|
||||||
|
last_wind_gust_speed: float | None
|
||||||
|
last_wind_speed: float | None
|
||||||
|
|
||||||
|
def as_dict(self) -> dict[str, Any]:
|
||||||
|
"""Return a dict representation of the event data."""
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, restored: dict[str, Any]) -> Self | None:
|
||||||
|
"""Initialize a stored event state from a dict."""
|
||||||
|
try:
|
||||||
|
return cls(
|
||||||
|
last_apparent_temperature=restored["last_apparent_temperature"],
|
||||||
|
last_cloud_coverage=restored["last_cloud_coverage"],
|
||||||
|
last_dew_point=restored["last_dew_point"],
|
||||||
|
last_humidity=restored["last_humidity"],
|
||||||
|
last_ozone=restored["last_ozone"],
|
||||||
|
last_pressure=restored["last_pressure"],
|
||||||
|
last_temperature=restored["last_temperature"],
|
||||||
|
last_visibility=restored["last_visibility"],
|
||||||
|
last_wind_bearing=restored["last_wind_bearing"],
|
||||||
|
last_wind_gust_speed=restored["last_wind_gust_speed"],
|
||||||
|
last_wind_speed=restored["last_wind_speed"],
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class TriggerWeatherEntity(TriggerEntity, WeatherEntity, RestoreEntity):
|
||||||
|
"""Sensor entity based on trigger data."""
|
||||||
|
|
||||||
|
domain = WEATHER_DOMAIN
|
||||||
|
extra_template_keys = (
|
||||||
|
CONF_CONDITION_TEMPLATE,
|
||||||
|
CONF_TEMPERATURE_TEMPLATE,
|
||||||
|
CONF_HUMIDITY_TEMPLATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
coordinator: TriggerUpdateCoordinator,
|
||||||
|
config: ConfigType,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(hass, coordinator, config)
|
||||||
|
self._attr_native_precipitation_unit = config.get(CONF_PRECIPITATION_UNIT)
|
||||||
|
self._attr_native_pressure_unit = config.get(CONF_PRESSURE_UNIT)
|
||||||
|
self._attr_native_temperature_unit = config.get(CONF_TEMPERATURE_UNIT)
|
||||||
|
self._attr_native_visibility_unit = config.get(CONF_VISIBILITY_UNIT)
|
||||||
|
self._attr_native_wind_speed_unit = config.get(CONF_WIND_SPEED_UNIT)
|
||||||
|
|
||||||
|
self._attr_supported_features = 0
|
||||||
|
if config.get(CONF_FORECAST_DAILY_TEMPLATE):
|
||||||
|
self._attr_supported_features |= WeatherEntityFeature.FORECAST_DAILY
|
||||||
|
if config.get(CONF_FORECAST_HOURLY_TEMPLATE):
|
||||||
|
self._attr_supported_features |= WeatherEntityFeature.FORECAST_HOURLY
|
||||||
|
if config.get(CONF_FORECAST_TWICE_DAILY_TEMPLATE):
|
||||||
|
self._attr_supported_features |= WeatherEntityFeature.FORECAST_TWICE_DAILY
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
CONF_APPARENT_TEMPERATURE_TEMPLATE,
|
||||||
|
CONF_CLOUD_COVERAGE_TEMPLATE,
|
||||||
|
CONF_DEW_POINT_TEMPLATE,
|
||||||
|
CONF_FORECAST_DAILY_TEMPLATE,
|
||||||
|
CONF_FORECAST_HOURLY_TEMPLATE,
|
||||||
|
CONF_FORECAST_TWICE_DAILY_TEMPLATE,
|
||||||
|
CONF_OZONE_TEMPLATE,
|
||||||
|
CONF_PRESSURE_TEMPLATE,
|
||||||
|
CONF_VISIBILITY_TEMPLATE,
|
||||||
|
CONF_WIND_BEARING_TEMPLATE,
|
||||||
|
CONF_WIND_GUST_SPEED_TEMPLATE,
|
||||||
|
CONF_WIND_SPEED_TEMPLATE,
|
||||||
|
):
|
||||||
|
if isinstance(config.get(key), template.Template):
|
||||||
|
self._to_render_simple.append(key)
|
||||||
|
self._parse_result.add(key)
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Restore last state."""
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
if (
|
||||||
|
(state := await self.async_get_last_state())
|
||||||
|
and state.state is not None
|
||||||
|
and state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
|
||||||
|
and (weather_data := await self.async_get_last_weather_data())
|
||||||
|
):
|
||||||
|
self._rendered[
|
||||||
|
CONF_APPARENT_TEMPERATURE_TEMPLATE
|
||||||
|
] = weather_data.last_apparent_temperature
|
||||||
|
self._rendered[
|
||||||
|
CONF_CLOUD_COVERAGE_TEMPLATE
|
||||||
|
] = weather_data.last_cloud_coverage
|
||||||
|
self._rendered[CONF_CONDITION_TEMPLATE] = state.state
|
||||||
|
self._rendered[CONF_DEW_POINT_TEMPLATE] = weather_data.last_dew_point
|
||||||
|
self._rendered[CONF_HUMIDITY_TEMPLATE] = weather_data.last_humidity
|
||||||
|
self._rendered[CONF_OZONE_TEMPLATE] = weather_data.last_ozone
|
||||||
|
self._rendered[CONF_PRESSURE_TEMPLATE] = weather_data.last_pressure
|
||||||
|
self._rendered[CONF_TEMPERATURE_TEMPLATE] = weather_data.last_temperature
|
||||||
|
self._rendered[CONF_VISIBILITY_TEMPLATE] = weather_data.last_visibility
|
||||||
|
self._rendered[CONF_WIND_BEARING_TEMPLATE] = weather_data.last_wind_bearing
|
||||||
|
self._rendered[
|
||||||
|
CONF_WIND_GUST_SPEED_TEMPLATE
|
||||||
|
] = weather_data.last_wind_gust_speed
|
||||||
|
self._rendered[CONF_WIND_SPEED_TEMPLATE] = weather_data.last_wind_speed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def condition(self) -> str | None:
|
||||||
|
"""Return the current condition."""
|
||||||
|
return self._rendered.get(CONF_CONDITION_TEMPLATE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_temperature(self) -> float | None:
|
||||||
|
"""Return the temperature."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_TEMPERATURE_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def humidity(self) -> float | None:
|
||||||
|
"""Return the humidity."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_HUMIDITY_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_wind_speed(self) -> float | None:
|
||||||
|
"""Return the wind speed."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_WIND_SPEED_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wind_bearing(self) -> float | str | None:
|
||||||
|
"""Return the wind bearing."""
|
||||||
|
return vol.Any(vol.Coerce(float), vol.Coerce(str), None)(
|
||||||
|
self._rendered.get(CONF_WIND_BEARING_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ozone(self) -> float | None:
|
||||||
|
"""Return the ozone level."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_OZONE_TEMPLATE),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_visibility(self) -> float | None:
|
||||||
|
"""Return the visibility."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_VISIBILITY_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_pressure(self) -> float | None:
|
||||||
|
"""Return the air pressure."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_PRESSURE_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_wind_gust_speed(self) -> float | None:
|
||||||
|
"""Return the wind gust speed."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_WIND_GUST_SPEED_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cloud_coverage(self) -> float | None:
|
||||||
|
"""Return the cloud coverage."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_CLOUD_COVERAGE_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_dew_point(self) -> float | None:
|
||||||
|
"""Return the dew point."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_DEW_POINT_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_apparent_temperature(self) -> float | None:
|
||||||
|
"""Return the apparent temperature."""
|
||||||
|
return vol.Any(vol.Coerce(float), None)(
|
||||||
|
self._rendered.get(CONF_APPARENT_TEMPERATURE_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_forecast_daily(self) -> list[Forecast]:
|
||||||
|
"""Return the daily forecast in native units."""
|
||||||
|
return vol.Any(vol.Coerce(list), None)(
|
||||||
|
self._rendered.get(CONF_FORECAST_DAILY_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_forecast_hourly(self) -> list[Forecast]:
|
||||||
|
"""Return the daily forecast in native units."""
|
||||||
|
return vol.Any(vol.Coerce(list), None)(
|
||||||
|
self._rendered.get(CONF_FORECAST_HOURLY_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_forecast_twice_daily(self) -> list[Forecast]:
|
||||||
|
"""Return the daily forecast in native units."""
|
||||||
|
return vol.Any(vol.Coerce(list), None)(
|
||||||
|
self._rendered.get(CONF_FORECAST_TWICE_DAILY_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_restore_state_data(self) -> WeatherExtraStoredData:
|
||||||
|
"""Return weather specific state data to be restored."""
|
||||||
|
return WeatherExtraStoredData(
|
||||||
|
last_apparent_temperature=self._rendered.get(
|
||||||
|
CONF_APPARENT_TEMPERATURE_TEMPLATE
|
||||||
|
),
|
||||||
|
last_cloud_coverage=self._rendered.get(CONF_CLOUD_COVERAGE_TEMPLATE),
|
||||||
|
last_dew_point=self._rendered.get(CONF_DEW_POINT_TEMPLATE),
|
||||||
|
last_humidity=self._rendered.get(CONF_HUMIDITY_TEMPLATE),
|
||||||
|
last_ozone=self._rendered.get(CONF_OZONE_TEMPLATE),
|
||||||
|
last_pressure=self._rendered.get(CONF_PRESSURE_TEMPLATE),
|
||||||
|
last_temperature=self._rendered.get(CONF_TEMPERATURE_TEMPLATE),
|
||||||
|
last_visibility=self._rendered.get(CONF_VISIBILITY_TEMPLATE),
|
||||||
|
last_wind_bearing=self._rendered.get(CONF_WIND_BEARING_TEMPLATE),
|
||||||
|
last_wind_gust_speed=self._rendered.get(CONF_WIND_GUST_SPEED_TEMPLATE),
|
||||||
|
last_wind_speed=self._rendered.get(CONF_WIND_SPEED_TEMPLATE),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_get_last_weather_data(self) -> WeatherExtraStoredData | None:
|
||||||
|
"""Restore weather specific state data."""
|
||||||
|
if (restored_last_extra_data := await self.async_get_last_extra_data()) is None:
|
||||||
|
return None
|
||||||
|
return WeatherExtraStoredData.from_dict(restored_last_extra_data.as_dict())
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""The tests for the Template Weather platform."""
|
"""The tests for the Template Weather platform."""
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.weather import (
|
from homeassistant.components.weather import (
|
||||||
@ -18,8 +20,18 @@ from homeassistant.components.weather import (
|
|||||||
SERVICE_GET_FORECAST,
|
SERVICE_GET_FORECAST,
|
||||||
Forecast,
|
Forecast,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION
|
from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import Context, HomeAssistant, State
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
|
from tests.common import (
|
||||||
|
assert_setup_component,
|
||||||
|
async_mock_restore_state_shutdown_restart,
|
||||||
|
mock_restore_cache_with_extra_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
|
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
|
||||||
@ -493,3 +505,457 @@ async def test_forecast_format_error(
|
|||||||
return_response=True,
|
return_response=True,
|
||||||
)
|
)
|
||||||
assert "Forecast in list is not a dict, see Weather documentation" in caplog.text
|
assert "Forecast in list is not a dict, see Weather documentation" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
SAVED_EXTRA_DATA = {
|
||||||
|
"last_apparent_temperature": None,
|
||||||
|
"last_cloud_coverage": None,
|
||||||
|
"last_dew_point": None,
|
||||||
|
"last_forecast": None,
|
||||||
|
"last_humidity": 10,
|
||||||
|
"last_ozone": None,
|
||||||
|
"last_pressure": None,
|
||||||
|
"last_temperature": 20,
|
||||||
|
"last_visibility": None,
|
||||||
|
"last_wind_bearing": None,
|
||||||
|
"last_wind_gust_speed": None,
|
||||||
|
"last_wind_speed": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
SAVED_EXTRA_DATA_WITH_FUTURE_KEY = {
|
||||||
|
"last_apparent_temperature": None,
|
||||||
|
"last_cloud_coverage": None,
|
||||||
|
"last_dew_point": None,
|
||||||
|
"last_forecast": None,
|
||||||
|
"last_humidity": 10,
|
||||||
|
"last_ozone": None,
|
||||||
|
"last_pressure": None,
|
||||||
|
"last_temperature": 20,
|
||||||
|
"last_visibility": None,
|
||||||
|
"last_wind_bearing": None,
|
||||||
|
"last_wind_gust_speed": None,
|
||||||
|
"last_wind_speed": None,
|
||||||
|
"some_key_added_in_the_future": 123,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"template": {
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"weather": {
|
||||||
|
"name": "test",
|
||||||
|
"condition_template": "{{ trigger.event.data.condition }}",
|
||||||
|
"temperature_template": "{{ trigger.event.data.temperature | float }}",
|
||||||
|
"temperature_unit": "°C",
|
||||||
|
"humidity_template": "{{ trigger.event.data.humidity | float }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("saved_state", "saved_extra_data", "initial_state"),
|
||||||
|
[
|
||||||
|
("sunny", SAVED_EXTRA_DATA, "sunny"),
|
||||||
|
("sunny", SAVED_EXTRA_DATA_WITH_FUTURE_KEY, "sunny"),
|
||||||
|
(STATE_UNAVAILABLE, SAVED_EXTRA_DATA, STATE_UNKNOWN),
|
||||||
|
(STATE_UNKNOWN, SAVED_EXTRA_DATA, STATE_UNKNOWN),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_trigger_entity_restore_state(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
count: int,
|
||||||
|
domain: str,
|
||||||
|
config: dict,
|
||||||
|
saved_state: str,
|
||||||
|
saved_extra_data: dict | None,
|
||||||
|
initial_state: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test restoring trigger template weather."""
|
||||||
|
|
||||||
|
restored_attributes = { # These should be ignored
|
||||||
|
"temperature": -10,
|
||||||
|
"humidity": 50,
|
||||||
|
}
|
||||||
|
|
||||||
|
fake_state = State(
|
||||||
|
"weather.test",
|
||||||
|
saved_state,
|
||||||
|
restored_attributes,
|
||||||
|
)
|
||||||
|
mock_restore_cache_with_extra_data(hass, ((fake_state, saved_extra_data),))
|
||||||
|
with assert_setup_component(count, domain):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
domain,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("weather.test")
|
||||||
|
assert state.state == initial_state
|
||||||
|
|
||||||
|
hass.bus.async_fire(
|
||||||
|
"test_event", {"condition": "cloudy", "temperature": 15, "humidity": 25}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("weather.test")
|
||||||
|
|
||||||
|
state = hass.states.get("weather.test")
|
||||||
|
assert state.state == "cloudy"
|
||||||
|
assert state.attributes["temperature"] == 15.0
|
||||||
|
assert state.attributes["humidity"] == 25.0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"template": [
|
||||||
|
{
|
||||||
|
"unique_id": "listening-test-event",
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"my_variable": "{{ trigger.event.data.temperature + 1 }}"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"weather": [
|
||||||
|
{
|
||||||
|
"name": "Hello Name",
|
||||||
|
"condition_template": "sunny",
|
||||||
|
"temperature_unit": "°C",
|
||||||
|
"humidity_template": "{{ 20 }}",
|
||||||
|
"temperature_template": "{{ my_variable + 1 }}",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_trigger_action(
|
||||||
|
hass: HomeAssistant, start_ha, entity_registry: er.EntityRegistry
|
||||||
|
) -> None:
|
||||||
|
"""Test trigger entity with an action works."""
|
||||||
|
state = hass.states.get("weather.hello_name")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
context = Context()
|
||||||
|
hass.bus.async_fire("test_event", {"temperature": 1}, context=context)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("weather.hello_name")
|
||||||
|
assert state.state == "sunny"
|
||||||
|
assert state.attributes["temperature"] == 3.0
|
||||||
|
assert state.context is context
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"template": [
|
||||||
|
{
|
||||||
|
"unique_id": "listening-test-event",
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"action": [
|
||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"my_variable": "{{ trigger.event.data.information + 1 }}",
|
||||||
|
"var_forecast_daily": "{{ trigger.event.data.forecast_daily }}",
|
||||||
|
"var_forecast_hourly": "{{ trigger.event.data.forecast_hourly }}",
|
||||||
|
"var_forecast_twice_daily": "{{ trigger.event.data.forecast_twice_daily }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"weather": [
|
||||||
|
{
|
||||||
|
"name": "Test",
|
||||||
|
"condition_template": "sunny",
|
||||||
|
"precipitation_unit": "mm",
|
||||||
|
"pressure_unit": "hPa",
|
||||||
|
"visibility_unit": "km",
|
||||||
|
"wind_speed_unit": "km/h",
|
||||||
|
"temperature_unit": "°C",
|
||||||
|
"temperature_template": "{{ my_variable + 1 }}",
|
||||||
|
"humidity_template": "{{ my_variable + 1 }}",
|
||||||
|
"wind_speed_template": "{{ my_variable + 1 }}",
|
||||||
|
"wind_bearing_template": "{{ my_variable + 1 }}",
|
||||||
|
"ozone_template": "{{ my_variable + 1 }}",
|
||||||
|
"visibility_template": "{{ my_variable + 1 }}",
|
||||||
|
"pressure_template": "{{ my_variable + 1 }}",
|
||||||
|
"wind_gust_speed_template": "{{ my_variable + 1 }}",
|
||||||
|
"cloud_coverage_template": "{{ my_variable + 1 }}",
|
||||||
|
"dew_point_template": "{{ my_variable + 1 }}",
|
||||||
|
"apparent_temperature_template": "{{ my_variable + 1 }}",
|
||||||
|
"forecast_template": "{{ var_forecast_daily }}",
|
||||||
|
"forecast_daily_template": "{{ var_forecast_daily }}",
|
||||||
|
"forecast_hourly_template": "{{ var_forecast_hourly }}",
|
||||||
|
"forecast_twice_daily_template": "{{ var_forecast_twice_daily }}",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_trigger_weather_services(
|
||||||
|
hass: HomeAssistant, start_ha, entity_registry: er.EntityRegistry
|
||||||
|
) -> None:
|
||||||
|
"""Test trigger weather entity with services."""
|
||||||
|
state = hass.states.get("weather.test")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
context = Context()
|
||||||
|
now = dt_util.now().isoformat()
|
||||||
|
hass.bus.async_fire(
|
||||||
|
"test_event",
|
||||||
|
{
|
||||||
|
"information": 1,
|
||||||
|
"forecast_daily": [
|
||||||
|
{
|
||||||
|
"datetime": now,
|
||||||
|
"condition": "sunny",
|
||||||
|
"precipitation": 20,
|
||||||
|
"temperature": 20,
|
||||||
|
"templow": 15,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"forecast_hourly": [
|
||||||
|
{
|
||||||
|
"datetime": now,
|
||||||
|
"condition": "sunny",
|
||||||
|
"precipitation": 20,
|
||||||
|
"temperature": 20,
|
||||||
|
"templow": 15,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"forecast_twice_daily": [
|
||||||
|
{
|
||||||
|
"datetime": now,
|
||||||
|
"condition": "sunny",
|
||||||
|
"precipitation": 20,
|
||||||
|
"temperature": 20,
|
||||||
|
"templow": 15,
|
||||||
|
"is_daytime": True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("weather.test")
|
||||||
|
assert state.state == "sunny"
|
||||||
|
assert state.attributes["temperature"] == 3.0
|
||||||
|
assert state.attributes["humidity"] == 3.0
|
||||||
|
assert state.attributes["wind_speed"] == 3.0
|
||||||
|
assert state.attributes["wind_bearing"] == 3.0
|
||||||
|
assert state.attributes["ozone"] == 3.0
|
||||||
|
assert state.attributes["visibility"] == 3.0
|
||||||
|
assert state.attributes["pressure"] == 3.0
|
||||||
|
assert state.attributes["wind_gust_speed"] == 3.0
|
||||||
|
assert state.attributes["cloud_coverage"] == 3.0
|
||||||
|
assert state.attributes["dew_point"] == 3.0
|
||||||
|
assert state.attributes["apparent_temperature"] == 3.0
|
||||||
|
assert state.context is context
|
||||||
|
|
||||||
|
response = await hass.services.async_call(
|
||||||
|
WEATHER_DOMAIN,
|
||||||
|
SERVICE_GET_FORECAST,
|
||||||
|
{
|
||||||
|
"entity_id": state.entity_id,
|
||||||
|
"type": "daily",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
assert response == {
|
||||||
|
"forecast": [
|
||||||
|
{
|
||||||
|
"datetime": now,
|
||||||
|
"condition": "sunny",
|
||||||
|
"precipitation": 20.0,
|
||||||
|
"temperature": 20.0,
|
||||||
|
"templow": 15.0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await hass.services.async_call(
|
||||||
|
WEATHER_DOMAIN,
|
||||||
|
SERVICE_GET_FORECAST,
|
||||||
|
{
|
||||||
|
"entity_id": state.entity_id,
|
||||||
|
"type": "hourly",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
assert response == {
|
||||||
|
"forecast": [
|
||||||
|
{
|
||||||
|
"datetime": now,
|
||||||
|
"condition": "sunny",
|
||||||
|
"precipitation": 20.0,
|
||||||
|
"temperature": 20.0,
|
||||||
|
"templow": 15.0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await hass.services.async_call(
|
||||||
|
WEATHER_DOMAIN,
|
||||||
|
SERVICE_GET_FORECAST,
|
||||||
|
{
|
||||||
|
"entity_id": state.entity_id,
|
||||||
|
"type": "twice_daily",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
assert response == {
|
||||||
|
"forecast": [
|
||||||
|
{
|
||||||
|
"datetime": now,
|
||||||
|
"condition": "sunny",
|
||||||
|
"precipitation": 20.0,
|
||||||
|
"temperature": 20.0,
|
||||||
|
"templow": 15.0,
|
||||||
|
"is_daytime": True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_restore_weather_save_state(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_storage: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Test Restore saved state for Weather trigger template."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"template",
|
||||||
|
{
|
||||||
|
"template": {
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"weather": {
|
||||||
|
"name": "test",
|
||||||
|
"condition_template": "{{ trigger.event.data.condition }}",
|
||||||
|
"temperature_template": "{{ trigger.event.data.temperature | float }}",
|
||||||
|
"temperature_unit": "°C",
|
||||||
|
"humidity_template": "{{ trigger.event.data.humidity | float }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
hass.bus.async_fire(
|
||||||
|
"test_event", {"condition": "cloudy", "temperature": 15, "humidity": 25}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
entity = hass.states.get("weather.test")
|
||||||
|
|
||||||
|
# Trigger saving state
|
||||||
|
await async_mock_restore_state_shutdown_restart(hass)
|
||||||
|
|
||||||
|
assert len(hass_storage[RESTORE_STATE_KEY]["data"]) == 1
|
||||||
|
state = hass_storage[RESTORE_STATE_KEY]["data"][0]["state"]
|
||||||
|
assert state["entity_id"] == entity.entity_id
|
||||||
|
extra_data = hass_storage[RESTORE_STATE_KEY]["data"][0]["extra_data"]
|
||||||
|
assert extra_data == {
|
||||||
|
"last_apparent_temperature": None,
|
||||||
|
"last_cloud_coverage": None,
|
||||||
|
"last_dew_point": None,
|
||||||
|
"last_humidity": "25.0",
|
||||||
|
"last_ozone": None,
|
||||||
|
"last_pressure": None,
|
||||||
|
"last_temperature": "15.0",
|
||||||
|
"last_visibility": None,
|
||||||
|
"last_wind_bearing": None,
|
||||||
|
"last_wind_gust_speed": None,
|
||||||
|
"last_wind_speed": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SAVED_ATTRIBUTES_1 = {
|
||||||
|
"humidity": 20,
|
||||||
|
"temperature": 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
SAVED_EXTRA_DATA_MISSING_KEY = {
|
||||||
|
"last_cloud_coverage": None,
|
||||||
|
"last_dew_point": None,
|
||||||
|
"last_humidity": 20,
|
||||||
|
"last_ozone": None,
|
||||||
|
"last_pressure": None,
|
||||||
|
"last_temperature": 20,
|
||||||
|
"last_visibility": None,
|
||||||
|
"last_wind_bearing": None,
|
||||||
|
"last_wind_gust_speed": None,
|
||||||
|
"last_wind_speed": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("saved_attributes", "saved_extra_data"),
|
||||||
|
[
|
||||||
|
(SAVED_ATTRIBUTES_1, SAVED_EXTRA_DATA_MISSING_KEY),
|
||||||
|
(SAVED_ATTRIBUTES_1, None),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_trigger_entity_restore_state_fail(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
saved_attributes: dict,
|
||||||
|
saved_extra_data: dict | None,
|
||||||
|
) -> None:
|
||||||
|
"""Test restoring trigger template weather fails due to missing attribute."""
|
||||||
|
|
||||||
|
saved_state = State(
|
||||||
|
"weather.test",
|
||||||
|
None,
|
||||||
|
saved_attributes,
|
||||||
|
)
|
||||||
|
mock_restore_cache_with_extra_data(hass, ((saved_state, saved_extra_data),))
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"template",
|
||||||
|
{
|
||||||
|
"template": {
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"weather": {
|
||||||
|
"name": "test",
|
||||||
|
"condition_template": "{{ trigger.event.data.condition }}",
|
||||||
|
"temperature_template": "{{ trigger.event.data.temperature | float }}",
|
||||||
|
"temperature_unit": "°C",
|
||||||
|
"humidity_template": "{{ trigger.event.data.humidity | float }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("weather.test")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
assert state.attributes.get("temperature") is None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user