Add config flow to template alarm_control_panel (#125861)

* Add config flow to template alarm_control_panel

* Remove commented code

* Test import
This commit is contained in:
G Johansson 2024-09-13 22:12:16 +02:00 committed by GitHub
parent cabaf37437
commit 2080b9a87c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 223 additions and 1 deletions

View File

@ -15,8 +15,10 @@ from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntityFeature, AlarmControlPanelEntityFeature,
CodeFormat, CodeFormat,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_CODE, ATTR_CODE,
CONF_DEVICE_ID,
CONF_NAME, CONF_NAME,
CONF_UNIQUE_ID, CONF_UNIQUE_ID,
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
@ -34,12 +36,14 @@ 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 selector
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
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 RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import slugify
from .const import DOMAIN from .const import DOMAIN
from .template_entity import TemplateEntity, rewrite_common_legacy_to_modern_conf from .template_entity import TemplateEntity, rewrite_common_legacy_to_modern_conf
@ -105,6 +109,25 @@ PLATFORM_SCHEMA = ALARM_CONTROL_PANEL_PLATFORM_SCHEMA.extend(
} }
) )
ALARM_CONTROL_PANEL_CONFIG_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): cv.template,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_DISARM_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_ARM_AWAY_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_ARM_CUSTOM_BYPASS_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_ARM_HOME_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_ARM_NIGHT_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_ARM_VACATION_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_TRIGGER_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean,
vol.Optional(CONF_CODE_FORMAT, default=TemplateCodeFormat.number.name): cv.enum(
TemplateCodeFormat
),
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
}
)
async def _async_create_entities( async def _async_create_entities(
hass: HomeAssistant, config: dict[str, Any] hass: HomeAssistant, config: dict[str, Any]
@ -128,6 +151,27 @@ async def _async_create_entities(
return alarm_control_panels return alarm_control_panels
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize config entry."""
_options = dict(config_entry.options)
_options.pop("template_type")
validated_config = ALARM_CONTROL_PANEL_CONFIG_SCHEMA(_options)
async_add_entities(
[
AlarmControlPanelTemplate(
hass,
slugify(_options[CONF_NAME]),
validated_config,
config_entry.entry_id,
)
]
)
async def async_setup_platform( async def async_setup_platform(
hass: HomeAssistant, hass: HomeAssistant,
config: ConfigType, config: ConfigType,

View File

@ -39,6 +39,18 @@ from homeassistant.helpers.schema_config_entry_flow import (
SchemaFlowMenuStep, SchemaFlowMenuStep,
) )
from .alarm_control_panel import (
CONF_ARM_AWAY_ACTION,
CONF_ARM_CUSTOM_BYPASS_ACTION,
CONF_ARM_HOME_ACTION,
CONF_ARM_NIGHT_ACTION,
CONF_ARM_VACATION_ACTION,
CONF_CODE_ARM_REQUIRED,
CONF_CODE_FORMAT,
CONF_DISARM_ACTION,
CONF_TRIGGER_ACTION,
TemplateCodeFormat,
)
from .binary_sensor import async_create_preview_binary_sensor from .binary_sensor import async_create_preview_binary_sensor
from .const import CONF_PRESS, CONF_TURN_OFF, CONF_TURN_ON, DOMAIN from .const import CONF_PRESS, CONF_TURN_OFF, CONF_TURN_ON, DOMAIN
from .number import ( from .number import (
@ -68,6 +80,30 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
if flow_type == "config": if flow_type == "config":
schema = {vol.Required(CONF_NAME): selector.TextSelector()} schema = {vol.Required(CONF_NAME): selector.TextSelector()}
if domain == Platform.ALARM_CONTROL_PANEL:
schema |= {
vol.Optional(CONF_VALUE_TEMPLATE): selector.TemplateSelector(),
vol.Optional(CONF_DISARM_ACTION): selector.ActionSelector(),
vol.Optional(CONF_ARM_AWAY_ACTION): selector.ActionSelector(),
vol.Optional(CONF_ARM_CUSTOM_BYPASS_ACTION): selector.ActionSelector(),
vol.Optional(CONF_ARM_HOME_ACTION): selector.ActionSelector(),
vol.Optional(CONF_ARM_NIGHT_ACTION): selector.ActionSelector(),
vol.Optional(CONF_ARM_VACATION_ACTION): selector.ActionSelector(),
vol.Optional(CONF_TRIGGER_ACTION): selector.ActionSelector(),
vol.Optional(
CONF_CODE_ARM_REQUIRED, default=True
): selector.BooleanSelector(),
vol.Optional(
CONF_CODE_FORMAT, default=TemplateCodeFormat.number.name
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=[e.name for e in TemplateCodeFormat],
mode=selector.SelectSelectorMode.DROPDOWN,
translation_key="alarm_control_panel_code_format",
)
),
}
if domain == Platform.BINARY_SENSOR: if domain == Platform.BINARY_SENSOR:
schema |= _SCHEMA_STATE schema |= _SCHEMA_STATE
if flow_type == "config": if flow_type == "config":
@ -265,6 +301,7 @@ def validate_user_input(
TEMPLATE_TYPES = [ TEMPLATE_TYPES = [
"alarm_control_panel",
"binary_sensor", "binary_sensor",
"button", "button",
"image", "image",
@ -276,6 +313,10 @@ TEMPLATE_TYPES = [
CONFIG_FLOW = { CONFIG_FLOW = {
"user": SchemaFlowMenuStep(TEMPLATE_TYPES), "user": SchemaFlowMenuStep(TEMPLATE_TYPES),
Platform.ALARM_CONTROL_PANEL: SchemaFlowFormStep(
config_schema(Platform.ALARM_CONTROL_PANEL),
validate_user_input=validate_user_input(Platform.ALARM_CONTROL_PANEL),
),
Platform.BINARY_SENSOR: SchemaFlowFormStep( Platform.BINARY_SENSOR: SchemaFlowFormStep(
config_schema(Platform.BINARY_SENSOR), config_schema(Platform.BINARY_SENSOR),
preview="template", preview="template",
@ -313,6 +354,10 @@ CONFIG_FLOW = {
OPTIONS_FLOW = { OPTIONS_FLOW = {
"init": SchemaFlowFormStep(next_step=choose_options_step), "init": SchemaFlowFormStep(next_step=choose_options_step),
Platform.ALARM_CONTROL_PANEL: SchemaFlowFormStep(
options_schema(Platform.ALARM_CONTROL_PANEL),
validate_user_input=validate_user_input(Platform.ALARM_CONTROL_PANEL),
),
Platform.BINARY_SENSOR: SchemaFlowFormStep( Platform.BINARY_SENSOR: SchemaFlowFormStep(
options_schema(Platform.BINARY_SENSOR), options_schema(Platform.BINARY_SENSOR),
preview="template", preview="template",

View File

@ -1,6 +1,26 @@
{ {
"config": { "config": {
"step": { "step": {
"alarm_control_panel": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"value_template": "[%key:component::template::config::step::switch::data::value_template%]",
"name": "[%key:common::config_flow::data::name%]",
"disarm": "Disarm action",
"arm_away": "Arm away action",
"arm_custom_bypass": "Arm custom bypass action",
"arm_home": "Arm home action",
"arm_night": "Arm night action",
"arm_vacation": "Arm vacation action",
"trigger": "Trigger action",
"code_arm_required": "Code arm required",
"code_format": "Code format"
},
"data_description": {
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
},
"title": "Template alarm control panel"
},
"binary_sensor": { "binary_sensor": {
"data": { "data": {
"device_id": "[%key:common::config_flow::data::device%]", "device_id": "[%key:common::config_flow::data::device%]",
@ -111,6 +131,25 @@
}, },
"options": { "options": {
"step": { "step": {
"alarm_control_panel": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"value_template": "[%key:component::template::config::step::switch::data::value_template%]",
"disarm": "[%key:component::template::config::step::alarm_control_panel::data::disarm%]",
"arm_away": "[%key:component::template::config::step::alarm_control_panel::data::arm_away%]",
"arm_custom_bypass": "[%key:component::template::config::step::alarm_control_panel::data::arm_custom_bypass%]",
"arm_home": "[%key:component::template::config::step::alarm_control_panel::data::arm_home%]",
"arm_night": "[%key:component::template::config::step::alarm_control_panel::data::arm_night%]",
"arm_vacation": "[%key:component::template::config::step::alarm_control_panel::data::arm_vacation%]",
"trigger": "[%key:component::template::config::step::alarm_control_panel::data::trigger%]",
"code_arm_required": "[%key:component::template::config::step::alarm_control_panel::data::code_arm_required%]",
"code_format": "[%key:component::template::config::step::alarm_control_panel::data::code_format%]"
},
"data_description": {
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
},
"title": "[%key:component::template::config::step::alarm_control_panel::title%]"
},
"binary_sensor": { "binary_sensor": {
"data": { "data": {
"device_id": "[%key:common::config_flow::data::device%]", "device_id": "[%key:common::config_flow::data::device%]",
@ -200,6 +239,13 @@
} }
}, },
"selector": { "selector": {
"alarm_control_panel_code_format": {
"options": {
"no_code": "No code format",
"number": "Number",
"text": "Text"
}
},
"binary_sensor_device_class": { "binary_sensor_device_class": {
"options": { "options": {
"battery": "[%key:component::binary_sensor::entity_component::battery::name%]", "battery": "[%key:component::binary_sensor::entity_component::battery::name%]",

View File

@ -0,0 +1,18 @@
# serializer version: 1
# name: test_setup_config_entry
StateSnapshot({
'attributes': ReadOnlyDict({
'changed_by': None,
'code_arm_required': True,
'code_format': <CodeFormat.NUMBER: 'number'>,
'friendly_name': 'My template',
'supported_features': <AlarmControlPanelEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'alarm_control_panel.my_template',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'armed_away',
})
# ---

View File

@ -1,7 +1,9 @@
"""The tests for the Template alarm control panel platform.""" """The tests for the Template alarm control panel platform."""
import pytest import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components import template
from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN
from homeassistant.const import ( from homeassistant.const import (
ATTR_DOMAIN, ATTR_DOMAIN,
@ -23,7 +25,7 @@ from homeassistant.const import (
from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from tests.common import assert_setup_component, mock_restore_cache from tests.common import MockConfigEntry, assert_setup_component, mock_restore_cache
TEMPLATE_NAME = "alarm_control_panel.test_template_panel" TEMPLATE_NAME = "alarm_control_panel.test_template_panel"
PANEL_NAME = "alarm_control_panel.test" PANEL_NAME = "alarm_control_panel.test"
@ -130,6 +132,41 @@ async def test_template_state_text(hass: HomeAssistant, start_ha) -> None:
assert state.state == "unknown" assert state.state == "unknown"
async def test_setup_config_entry(
hass: HomeAssistant, snapshot: SnapshotAssertion
) -> None:
"""Test the config flow."""
value_template = "{{ states('alarm_control_panel.one') }}"
hass.states.async_set("alarm_control_panel.one", "armed_away", {})
template_config_entry = MockConfigEntry(
data={},
domain=template.DOMAIN,
options={
"name": "My template",
"value_template": value_template,
"template_type": "alarm_control_panel",
"code_arm_required": True,
"code_format": "number",
},
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("alarm_control_panel.my_template")
assert state is not None
assert state == snapshot
hass.states.async_set("alarm_control_panel.one", "disarmed", {})
await hass.async_block_till_done()
state = hass.states.get("alarm_control_panel.my_template")
assert state.state == STATE_ALARM_DISARMED
@pytest.mark.parametrize(("count", "domain"), [(1, "alarm_control_panel")]) @pytest.mark.parametrize(("count", "domain"), [(1, "alarm_control_panel")])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"config", "config",

View File

@ -29,6 +29,16 @@ from tests.typing import WebSocketGenerator
"extra_attrs", "extra_attrs",
), ),
[ [
(
"alarm_control_panel",
{"value_template": "{{ states('alarm_control_panel.one') }}"},
"armed_away",
{"one": "armed_away", "two": "disarmed"},
{},
{},
{"code_arm_required": True, "code_format": "number"},
{},
),
( (
"binary_sensor", "binary_sensor",
{ {
@ -270,6 +280,12 @@ async def test_config_flow(
"step": 0.1, "step": 0.1,
}, },
), ),
(
"alarm_control_panel",
{"value_template": "{{ states('alarm_control_panel.one') }}"},
{"code_arm_required": True, "code_format": "number"},
{"code_arm_required": True, "code_format": "number"},
),
( (
"select", "select",
{"state": "{{ states('select.one') }}"}, {"state": "{{ states('select.one') }}"},
@ -476,6 +492,16 @@ def get_suggested(schema, key):
}, },
"state", "state",
), ),
(
"alarm_control_panel",
{"value_template": "{{ states('alarm_control_panel.one') }}"},
{"value_template": "{{ states('alarm_control_panel.two') }}"},
["armed_away", "disarmed"],
{"one": "armed_away", "two": "disarmed"},
{"code_arm_required": True, "code_format": "number"},
{"code_arm_required": True, "code_format": "number"},
"value_template",
),
( (
"select", "select",
{"state": "{{ states('select.one') }}"}, {"state": "{{ states('select.one') }}"},
@ -1244,6 +1270,12 @@ async def test_option_flow_sensor_preview_config_entry_removed(
"step": 0.1, "step": 0.1,
}, },
), ),
(
"alarm_control_panel",
{"value_template": "{{ states('alarm_control_panel.one') }}"},
{"code_arm_required": True, "code_format": "number"},
{"code_arm_required": True, "code_format": "number"},
),
( (
"select", "select",
{"state": "{{ states('select.one') }}"}, {"state": "{{ states('select.one') }}"},