mirror of
https://github.com/home-assistant/core.git
synced 2025-12-26 22:18:40 +00:00
Modernize template weather platform and add config flow (#156399)
This commit is contained in:
@@ -31,6 +31,7 @@ from homeassistant.const import (
|
||||
CONF_VALUE_TEMPLATE,
|
||||
CONF_VERIFY_SSL,
|
||||
Platform,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import section
|
||||
@@ -132,6 +133,15 @@ from .vacuum import (
|
||||
SERVICE_STOP,
|
||||
async_create_preview_vacuum,
|
||||
)
|
||||
from .weather import (
|
||||
CONF_CONDITION,
|
||||
CONF_FORECAST_DAILY,
|
||||
CONF_FORECAST_HOURLY,
|
||||
CONF_HUMIDITY,
|
||||
CONF_TEMPERATURE as CONF_WEATHER_TEMPERATURE,
|
||||
CONF_TEMPERATURE_UNIT,
|
||||
async_create_preview_weather,
|
||||
)
|
||||
|
||||
_SCHEMA_STATE: dict[vol.Marker, Any] = {
|
||||
vol.Required(CONF_STATE): selector.TemplateSelector(),
|
||||
@@ -394,6 +404,22 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
|
||||
vol.Optional(SERVICE_LOCATE): selector.ActionSelector(),
|
||||
}
|
||||
|
||||
if domain == Platform.WEATHER:
|
||||
schema |= {
|
||||
vol.Required(CONF_CONDITION): selector.TemplateSelector(),
|
||||
vol.Required(CONF_HUMIDITY): selector.TemplateSelector(),
|
||||
vol.Required(CONF_WEATHER_TEMPERATURE): selector.TemplateSelector(),
|
||||
vol.Optional(CONF_TEMPERATURE_UNIT): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=[cls.value for cls in UnitOfTemperature],
|
||||
mode=selector.SelectSelectorMode.DROPDOWN,
|
||||
sort=True,
|
||||
),
|
||||
),
|
||||
vol.Optional(CONF_FORECAST_DAILY): selector.TemplateSelector(),
|
||||
vol.Optional(CONF_FORECAST_HOURLY): selector.TemplateSelector(),
|
||||
}
|
||||
|
||||
schema |= {
|
||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
||||
vol.Optional(CONF_ADVANCED_OPTIONS): section(
|
||||
@@ -414,6 +440,15 @@ options_schema = partial(generate_schema, flow_type="options")
|
||||
config_schema = partial(generate_schema, flow_type="config")
|
||||
|
||||
|
||||
async def _get_forecast_description_place_holders(
|
||||
handler: SchemaCommonFlowHandler,
|
||||
) -> dict[str, str]:
|
||||
return {
|
||||
"daily_link": "https://www.home-assistant.io/integrations/template/#daily-weather-forecast",
|
||||
"hourly_link": "https://www.home-assistant.io/integrations/template/#hourly-weather-forecast",
|
||||
}
|
||||
|
||||
|
||||
async def choose_options_step(options: dict[str, Any]) -> str:
|
||||
"""Return next step_id for options flow according to template_type."""
|
||||
return cast(str, options["template_type"])
|
||||
@@ -511,6 +546,7 @@ TEMPLATE_TYPES = [
|
||||
Platform.SWITCH,
|
||||
Platform.UPDATE,
|
||||
Platform.VACUUM,
|
||||
Platform.WEATHER,
|
||||
]
|
||||
|
||||
CONFIG_FLOW = {
|
||||
@@ -589,6 +625,12 @@ CONFIG_FLOW = {
|
||||
preview="template",
|
||||
validate_user_input=validate_user_input(Platform.VACUUM),
|
||||
),
|
||||
Platform.WEATHER: SchemaFlowFormStep(
|
||||
config_schema(Platform.WEATHER),
|
||||
preview="template",
|
||||
validate_user_input=validate_user_input(Platform.WEATHER),
|
||||
description_placeholders=_get_forecast_description_place_holders,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -668,6 +710,12 @@ OPTIONS_FLOW = {
|
||||
preview="template",
|
||||
validate_user_input=validate_user_input(Platform.VACUUM),
|
||||
),
|
||||
Platform.WEATHER: SchemaFlowFormStep(
|
||||
options_schema(Platform.WEATHER),
|
||||
preview="template",
|
||||
validate_user_input=validate_user_input(Platform.WEATHER),
|
||||
description_placeholders=_get_forecast_description_place_holders,
|
||||
),
|
||||
}
|
||||
|
||||
CREATE_PREVIEW_ENTITY: dict[
|
||||
@@ -687,6 +735,7 @@ CREATE_PREVIEW_ENTITY: dict[
|
||||
Platform.SWITCH: async_create_preview_switch,
|
||||
Platform.UPDATE: async_create_preview_update,
|
||||
Platform.VACUUM: async_create_preview_vacuum,
|
||||
Platform.WEATHER: async_create_preview_weather,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -463,7 +463,8 @@
|
||||
"sensor": "[%key:component::sensor::title%]",
|
||||
"switch": "[%key:component::switch::title%]",
|
||||
"update": "[%key:component::update::title%]",
|
||||
"vacuum": "[%key:component::vacuum::title%]"
|
||||
"vacuum": "[%key:component::vacuum::title%]",
|
||||
"weather": "[%key:component::weather::title%]"
|
||||
},
|
||||
"title": "Template helper"
|
||||
},
|
||||
@@ -507,6 +508,36 @@
|
||||
}
|
||||
},
|
||||
"title": "Template vacuum"
|
||||
},
|
||||
"weather": {
|
||||
"data": {
|
||||
"condition": "Condition",
|
||||
"device_id": "[%key:common::config_flow::data::device%]",
|
||||
"forecast_daily": "Forecast daily",
|
||||
"forecast_hourly": "Forecast hourly",
|
||||
"humidity": "Humidity",
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"temperature": "Temperature",
|
||||
"temperature_unit": "Temperature unit"
|
||||
},
|
||||
"data_description": {
|
||||
"condition": "Defines a template to get the current weather condition",
|
||||
"device_id": "[%key:component::template::common::device_id_description%]",
|
||||
"forecast_daily": "Defines a template to get the [daily forecast data]({daily_link})",
|
||||
"forecast_hourly": "Defines a template to get the [hourly forecast data]({hourly_link})",
|
||||
"humidity": "Defines a template to get the current humidity",
|
||||
"temperature": "Defines a template to get the current temperature",
|
||||
"temperature_unit": "The temperature unit"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"data": {
|
||||
"availability": "[%key:component::template::common::availability%]"
|
||||
},
|
||||
"name": "[%key:component::template::common::advanced_options%]"
|
||||
}
|
||||
},
|
||||
"title": "Template weather"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -995,6 +1026,36 @@
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::vacuum::title%]"
|
||||
},
|
||||
"weather": {
|
||||
"data": {
|
||||
"condition": "[%key:component::template::config::step::weather::data::condition%]",
|
||||
"device_id": "[%key:common::config_flow::data::device%]",
|
||||
"forecast_daily": "[%key:component::template::config::step::weather::data::forecast_daily%]",
|
||||
"forecast_hourly": "[%key:component::template::config::step::weather::data::forecast_hourly%]",
|
||||
"humidity": "[%key:component::template::config::step::weather::data::humidity%]",
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"temperature": "[%key:component::template::config::step::weather::data::temperature%]",
|
||||
"temperature_unit": "[%key:component::template::config::step::weather::data::temperature_unit%]"
|
||||
},
|
||||
"data_description": {
|
||||
"condition": "[%key:component::template::config::step::weather::data_description::condition%]",
|
||||
"device_id": "[%key:component::template::common::device_id_description%]",
|
||||
"forecast_daily": "[%key:component::template::config::step::weather::data_description::forecast_daily%]",
|
||||
"forecast_hourly": "[%key:component::template::config::step::weather::data_description::forecast_hourly%]",
|
||||
"humidity": "[%key:component::template::config::step::weather::data_description::humidity%]",
|
||||
"temperature": "[%key:component::template::config::step::weather::data_description::temperature%]",
|
||||
"temperature_unit": "[%key:component::template::config::step::weather::data_description::temperature_unit%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"data": {
|
||||
"availability": "[%key:component::template::common::availability%]"
|
||||
},
|
||||
"name": "[%key:component::template::common::advanced_options%]"
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::weather::title%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,29 @@
|
||||
'last_wind_speed': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_setup_config_entry
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'assumed_state': True,
|
||||
'attribution': 'Powered by Home Assistant',
|
||||
'friendly_name': 'My template',
|
||||
'humidity': 50,
|
||||
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
|
||||
'pressure_unit': <UnitOfPressure.HPA: 'hPa'>,
|
||||
'supported_features': 0,
|
||||
'temperature': 20.0,
|
||||
'temperature_unit': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
'visibility_unit': <UnitOfLength.KILOMETERS: 'km'>,
|
||||
'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'weather.my_template',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_trigger_weather_services[config0-1-template-get_forecasts]
|
||||
dict({
|
||||
'weather.test': dict({
|
||||
|
||||
@@ -270,6 +270,16 @@ BINARY_SENSOR_OPTIONS = {
|
||||
{"start": []},
|
||||
{},
|
||||
),
|
||||
(
|
||||
"weather",
|
||||
{"condition": "{{ states('weather.one') }}"},
|
||||
"sunny",
|
||||
{"one": "sunny", "two": "cloudy"},
|
||||
{},
|
||||
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
|
||||
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
|
||||
{},
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
|
||||
@@ -463,6 +473,12 @@ async def test_config_flow(
|
||||
{"start": []},
|
||||
{"start": []},
|
||||
),
|
||||
(
|
||||
"weather",
|
||||
{"condition": "{{ states('weather.one') }}"},
|
||||
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
|
||||
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_config_flow_device(
|
||||
@@ -752,6 +768,16 @@ async def test_config_flow_device(
|
||||
{"start": []},
|
||||
"state",
|
||||
),
|
||||
(
|
||||
"weather",
|
||||
{"condition": "{{ states('weather.one') }}"},
|
||||
{"condition": "{{ states('weather.two') }}"},
|
||||
["sunny", "cloudy"],
|
||||
{"one": "sunny", "two": "cloudy"},
|
||||
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
|
||||
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
|
||||
"condition",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
|
||||
@@ -1601,6 +1627,12 @@ async def test_option_flow_sensor_preview_config_entry_removed(
|
||||
{"start": []},
|
||||
{"start": []},
|
||||
),
|
||||
(
|
||||
"weather",
|
||||
{"condition": "{{ states('weather.one') }}"},
|
||||
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
|
||||
{"temperature": "{{ 20 }}", "humidity": "{{ 50 }}"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_options_flow_change_device(
|
||||
|
||||
@@ -37,13 +37,15 @@ 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 .conftest import ConfigurationStyle
|
||||
from .conftest import ConfigurationStyle, async_get_flow_preview_state
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
assert_setup_component,
|
||||
async_mock_restore_state_shutdown_restart,
|
||||
mock_restore_cache_with_extra_data,
|
||||
)
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
ATTR_FORECAST = "forecast"
|
||||
|
||||
@@ -122,6 +124,27 @@ async def setup_weather(
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def setup_weather_single_attribute(
|
||||
hass: HomeAssistant,
|
||||
count: int,
|
||||
style: ConfigurationStyle,
|
||||
attribute: str,
|
||||
attribute_template: str,
|
||||
weather_config: dict[str, Any],
|
||||
) -> None:
|
||||
"""Do setup of weather integration."""
|
||||
extra = {attribute: attribute_template}
|
||||
if style == ConfigurationStyle.MODERN:
|
||||
await async_setup_modern_format(
|
||||
hass, count, {"name": TEST_OBJECT_ID, **weather_config, **extra}
|
||||
)
|
||||
if style == ConfigurationStyle.TRIGGER:
|
||||
await async_setup_trigger_format(
|
||||
hass, count, {"name": TEST_OBJECT_ID, **weather_config, **extra}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
@@ -1129,3 +1152,58 @@ async def test_templated_optional_config(
|
||||
state = hass.states.get(TEST_WEATHER)
|
||||
|
||||
assert state.attributes[attribute] == expected
|
||||
|
||||
|
||||
async def test_setup_config_entry(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Tests creating a weather from a config entry."""
|
||||
|
||||
hass.states.async_set(
|
||||
"weather.test_state",
|
||||
"sunny",
|
||||
{},
|
||||
)
|
||||
|
||||
template_config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=template.DOMAIN,
|
||||
options={
|
||||
"name": "My template",
|
||||
"condition": "{{ states('sensor.test_sensor') }}",
|
||||
"humidity": "{{ 50 }}",
|
||||
"temperature": "{{ 20 }}",
|
||||
"template_type": WEATHER_DOMAIN,
|
||||
},
|
||||
title="My template",
|
||||
)
|
||||
template_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("weather.my_template")
|
||||
assert state is not None
|
||||
assert state == snapshot
|
||||
|
||||
|
||||
async def test_flow_preview(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test the config flow preview."""
|
||||
|
||||
state = await async_get_flow_preview_state(
|
||||
hass,
|
||||
hass_ws_client,
|
||||
WEATHER_DOMAIN,
|
||||
{
|
||||
"name": "My template",
|
||||
"condition": "{{ 'sunny' }}",
|
||||
"humidity": "{{ 50 }}",
|
||||
"temperature": "{{ 20 }}",
|
||||
},
|
||||
)
|
||||
|
||||
assert state["state"] == "sunny"
|
||||
|
||||
Reference in New Issue
Block a user