Add config flow to template vacuum platform (#149458)

This commit is contained in:
Petro31 2025-07-30 11:04:08 -04:00 committed by GitHub
parent edca3fc0b7
commit d481a694f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 250 additions and 5 deletions

View File

@ -103,6 +103,18 @@ from .select import CONF_OPTIONS, CONF_SELECT_OPTION, async_create_preview_selec
from .sensor import async_create_preview_sensor from .sensor import async_create_preview_sensor
from .switch import async_create_preview_switch from .switch import async_create_preview_switch
from .template_entity import TemplateEntity from .template_entity import TemplateEntity
from .vacuum import (
CONF_FAN_SPEED,
CONF_FAN_SPEED_LIST,
SERVICE_CLEAN_SPOT,
SERVICE_LOCATE,
SERVICE_PAUSE,
SERVICE_RETURN_TO_BASE,
SERVICE_SET_FAN_SPEED,
SERVICE_START,
SERVICE_STOP,
async_create_preview_vacuum,
)
_SCHEMA_STATE: dict[vol.Marker, Any] = { _SCHEMA_STATE: dict[vol.Marker, Any] = {
vol.Required(CONF_STATE): selector.TemplateSelector(), vol.Required(CONF_STATE): selector.TemplateSelector(),
@ -294,6 +306,26 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
vol.Optional(CONF_TURN_OFF): selector.ActionSelector(), vol.Optional(CONF_TURN_OFF): selector.ActionSelector(),
} }
if domain == Platform.VACUUM:
schema |= _SCHEMA_STATE | {
vol.Required(SERVICE_START): selector.ActionSelector(),
vol.Optional(CONF_FAN_SPEED): selector.TemplateSelector(),
vol.Optional(CONF_FAN_SPEED_LIST): selector.SelectSelector(
selector.SelectSelectorConfig(
options=[],
multiple=True,
custom_value=True,
mode=selector.SelectSelectorMode.DROPDOWN,
)
),
vol.Optional(SERVICE_SET_FAN_SPEED): selector.ActionSelector(),
vol.Optional(SERVICE_STOP): selector.ActionSelector(),
vol.Optional(SERVICE_PAUSE): selector.ActionSelector(),
vol.Optional(SERVICE_RETURN_TO_BASE): selector.ActionSelector(),
vol.Optional(SERVICE_CLEAN_SPOT): selector.ActionSelector(),
vol.Optional(SERVICE_LOCATE): selector.ActionSelector(),
}
schema |= { schema |= {
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(), vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
vol.Optional(CONF_ADVANCED_OPTIONS): section( vol.Optional(CONF_ADVANCED_OPTIONS): section(
@ -407,6 +439,7 @@ TEMPLATE_TYPES = [
Platform.SELECT, Platform.SELECT,
Platform.SENSOR, Platform.SENSOR,
Platform.SWITCH, Platform.SWITCH,
Platform.VACUUM,
] ]
CONFIG_FLOW = { CONFIG_FLOW = {
@ -465,6 +498,11 @@ CONFIG_FLOW = {
preview="template", preview="template",
validate_user_input=validate_user_input(Platform.SWITCH), validate_user_input=validate_user_input(Platform.SWITCH),
), ),
Platform.VACUUM: SchemaFlowFormStep(
config_schema(Platform.VACUUM),
preview="template",
validate_user_input=validate_user_input(Platform.VACUUM),
),
} }
@ -524,6 +562,11 @@ OPTIONS_FLOW = {
preview="template", preview="template",
validate_user_input=validate_user_input(Platform.SWITCH), validate_user_input=validate_user_input(Platform.SWITCH),
), ),
Platform.VACUUM: SchemaFlowFormStep(
options_schema(Platform.VACUUM),
preview="template",
validate_user_input=validate_user_input(Platform.VACUUM),
),
} }
CREATE_PREVIEW_ENTITY: dict[ CREATE_PREVIEW_ENTITY: dict[
@ -539,6 +582,7 @@ CREATE_PREVIEW_ENTITY: dict[
Platform.SELECT: async_create_preview_select, Platform.SELECT: async_create_preview_select,
Platform.SENSOR: async_create_preview_sensor, Platform.SENSOR: async_create_preview_sensor,
Platform.SWITCH: async_create_preview_switch, Platform.SWITCH: async_create_preview_switch,
Platform.VACUUM: async_create_preview_vacuum,
} }

View File

@ -268,7 +268,8 @@
"number": "Template a number", "number": "Template a number",
"select": "Template a select", "select": "Template a select",
"sensor": "Template a sensor", "sensor": "Template a sensor",
"switch": "Template a switch" "switch": "Template a switch",
"vacuum": "Template a vacuum"
}, },
"title": "Template helper" "title": "Template helper"
}, },
@ -293,6 +294,34 @@
} }
}, },
"title": "Template switch" "title": "Template switch"
},
"vacuum": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"name": "[%key:common::config_flow::data::name%]",
"state": "[%key:component::template::common::state%]",
"start": "Actions on turn off",
"fan_speed": "Fan speed",
"fan_speeds": "Fan speeds",
"set_fan_speed": "Actions on set fan speed",
"stop": "Actions on stop",
"pause": "Actions on pause",
"return_to_base": "Actions on return to base",
"clean_spot": "Actions on clean spot",
"locate": "Actions on locate"
},
"data_description": {
"device_id": "[%key:component::template::common::device_id_description%]"
},
"sections": {
"advanced_options": {
"name": "[%key:component::template::common::advanced_options%]",
"data": {
"availability": "[%key:component::template::common::availability%]"
}
}
},
"title": "Template vacuum"
} }
} }
}, },
@ -552,6 +581,34 @@
} }
}, },
"title": "[%key:component::template::config::step::switch::title%]" "title": "[%key:component::template::config::step::switch::title%]"
},
"vacuum": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"name": "[%key:common::config_flow::data::name%]",
"state": "[%key:component::template::common::state%]",
"start": "[%key:component::template::config::step::vacuum::data::start%]",
"fan_speed": "[%key:component::template::config::step::vacuum::data::fan_speed%]",
"fan_speeds": "[%key:component::template::config::step::vacuum::data::fan_speeds%]",
"set_fan_speed": "[%key:component::template::config::step::vacuum::data::set_fan_speed%]",
"stop": "[%key:component::template::config::step::vacuum::data::stop%]",
"pause": "[%key:component::template::config::step::vacuum::data::pause%]",
"return_to_base": "[%key:component::template::config::step::vacuum::data::return_to_base%]",
"clean_spot": "[%key:component::template::config::step::vacuum::data::clean_spot%]",
"locate": "[%key:component::template::config::step::vacuum::data::locate%]"
},
"data_description": {
"device_id": "[%key:component::template::common::device_id_description%]"
},
"sections": {
"advanced_options": {
"name": "[%key:component::template::common::advanced_options%]",
"data": {
"availability": "[%key:component::template::common::availability%]"
}
}
},
"title": "Template vacuum"
} }
} }
}, },

View File

@ -22,6 +22,7 @@ from homeassistant.components.vacuum import (
VacuumActivity, VacuumActivity,
VacuumEntityFeature, VacuumEntityFeature,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_ENTITY_ID, CONF_ENTITY_ID,
CONF_FRIENDLY_NAME, CONF_FRIENDLY_NAME,
@ -34,16 +35,24 @@ from homeassistant.const import (
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 config_validation as cv, template from homeassistant.helpers import config_validation as cv, template
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DOMAIN from .const import DOMAIN
from .coordinator import TriggerUpdateCoordinator from .coordinator import TriggerUpdateCoordinator
from .entity import AbstractTemplateEntity from .entity import AbstractTemplateEntity
from .helpers import async_setup_template_platform from .helpers import (
async_setup_template_entry,
async_setup_template_platform,
async_setup_template_preview,
)
from .template_entity import ( from .template_entity import (
TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA_LEGACY, TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA_LEGACY,
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY, TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA, TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
TemplateEntity, TemplateEntity,
make_template_entity_common_modern_attributes_schema, make_template_entity_common_modern_attributes_schema,
@ -125,6 +134,10 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
{vol.Required(CONF_VACUUMS): cv.schema_with_slug_keys(VACUUM_LEGACY_YAML_SCHEMA)} {vol.Required(CONF_VACUUMS): cv.schema_with_slug_keys(VACUUM_LEGACY_YAML_SCHEMA)}
) )
VACUUM_CONFIG_ENTRY_SCHEMA = VACUUM_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
)
async def async_setup_platform( async def async_setup_platform(
hass: HomeAssistant, hass: HomeAssistant,
@ -146,6 +159,35 @@ async def async_setup_platform(
) )
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Initialize config entry."""
await async_setup_template_entry(
hass,
config_entry,
async_add_entities,
TemplateStateVacuumEntity,
VACUUM_CONFIG_ENTRY_SCHEMA,
)
@callback
def async_create_preview_vacuum(
hass: HomeAssistant, name: str, config: dict[str, Any]
) -> TemplateStateVacuumEntity:
"""Create a preview."""
return async_setup_template_preview(
hass,
name,
config,
TemplateStateVacuumEntity,
VACUUM_CONFIG_ENTRY_SCHEMA,
)
class AbstractTemplateVacuum(AbstractTemplateEntity, StateVacuumEntity): class AbstractTemplateVacuum(AbstractTemplateEntity, StateVacuumEntity):
"""Representation of a template vacuum features.""" """Representation of a template vacuum features."""

View File

@ -0,0 +1,15 @@
# serializer version: 1
# name: test_setup_config_entry
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'My template',
'supported_features': <VacuumEntityFeature: 12288>,
}),
'context': <ANY>,
'entity_id': 'vacuum.my_template',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'docked',
})
# ---

View File

@ -229,6 +229,16 @@ BINARY_SENSOR_OPTIONS = {
{}, {},
{}, {},
), ),
(
"vacuum",
{"state": "{{ states('vacuum.one') }}"},
"docked",
{"one": "docked", "two": "cleaning"},
{},
{"start": []},
{"start": []},
{},
),
], ],
) )
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00") @pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
@ -398,6 +408,12 @@ async def test_config_flow(
{"options": "{{ ['off', 'on', 'auto'] }}"}, {"options": "{{ ['off', 'on', 'auto'] }}"},
{"options": "{{ ['off', 'on', 'auto'] }}"}, {"options": "{{ ['off', 'on', 'auto'] }}"},
), ),
(
"vacuum",
{"state": "{{ states('vacuum.one') }}"},
{"start": []},
{"start": []},
),
], ],
) )
async def test_config_flow_device( async def test_config_flow_device(
@ -647,6 +663,16 @@ async def test_config_flow_device(
{}, {},
"value_template", "value_template",
), ),
(
"vacuum",
{"state": "{{ states('vacuum.one') }}"},
{"state": "{{ states('vacuum.two') }}"},
["docked", "cleaning"],
{"one": "docked", "two": "cleaning"},
{"start": []},
{"start": []},
"state",
),
], ],
) )
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00") @pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
@ -1480,6 +1506,12 @@ async def test_option_flow_sensor_preview_config_entry_removed(
{}, {},
{}, {},
), ),
(
"vacuum",
{"state": "{{ states('vacuum.one') }}"},
{"start": []},
{"start": []},
),
], ],
) )
async def test_options_flow_change_device( async def test_options_flow_change_device(

View File

@ -3,6 +3,7 @@
from typing import Any from typing import Any
import pytest import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components import template, vacuum from homeassistant.components import template, vacuum
from homeassistant.components.vacuum import ( from homeassistant.components.vacuum import (
@ -18,10 +19,11 @@ from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_component import async_update_entity from homeassistant.helpers.entity_component import async_update_entity
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .conftest import ConfigurationStyle from .conftest import ConfigurationStyle, async_get_flow_preview_state
from tests.common import assert_setup_component from tests.common import MockConfigEntry, assert_setup_component
from tests.components.vacuum import common from tests.components.vacuum import common
from tests.typing import WebSocketGenerator
TEST_OBJECT_ID = "test_vacuum" TEST_OBJECT_ID = "test_vacuum"
TEST_ENTITY_ID = f"vacuum.{TEST_OBJECT_ID}" TEST_ENTITY_ID = f"vacuum.{TEST_OBJECT_ID}"
@ -1261,3 +1263,56 @@ async def test_optimistic_option(
state = hass.states.get(TEST_ENTITY_ID) state = hass.states.get(TEST_ENTITY_ID)
assert state.state == VacuumActivity.DOCKED assert state.state == VacuumActivity.DOCKED
async def test_setup_config_entry(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
) -> None:
"""Tests creating a vacuum from a config entry."""
hass.states.async_set(
"sensor.test_sensor",
"docked",
{},
)
template_config_entry = MockConfigEntry(
data={},
domain=template.DOMAIN,
options={
"name": "My template",
"state": "{{ states('sensor.test_sensor') }}",
"start": [],
"template_type": vacuum.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("vacuum.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,
vacuum.DOMAIN,
{
"name": "My template",
"state": "{{ 'cleaning' }}",
"start": [],
},
)
assert state["state"] == VacuumActivity.CLEANING