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,
CodeFormat,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_CODE,
CONF_DEVICE_ID,
CONF_NAME,
CONF_UNIQUE_ID,
CONF_VALUE_TEMPLATE,
@ -34,12 +36,14 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import selector
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.script import Script
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import slugify
from .const import DOMAIN
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(
hass: HomeAssistant, config: dict[str, Any]
@ -128,6 +151,27 @@ async def _async_create_entities(
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(
hass: HomeAssistant,
config: ConfigType,

View File

@ -39,6 +39,18 @@ from homeassistant.helpers.schema_config_entry_flow import (
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 .const import CONF_PRESS, CONF_TURN_OFF, CONF_TURN_ON, DOMAIN
from .number import (
@ -68,6 +80,30 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
if flow_type == "config":
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:
schema |= _SCHEMA_STATE
if flow_type == "config":
@ -265,6 +301,7 @@ def validate_user_input(
TEMPLATE_TYPES = [
"alarm_control_panel",
"binary_sensor",
"button",
"image",
@ -276,6 +313,10 @@ TEMPLATE_TYPES = [
CONFIG_FLOW = {
"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(
config_schema(Platform.BINARY_SENSOR),
preview="template",
@ -313,6 +354,10 @@ CONFIG_FLOW = {
OPTIONS_FLOW = {
"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(
options_schema(Platform.BINARY_SENSOR),
preview="template",

View File

@ -1,6 +1,26 @@
{
"config": {
"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": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
@ -111,6 +131,25 @@
},
"options": {
"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": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
@ -200,6 +239,13 @@
}
},
"selector": {
"alarm_control_panel_code_format": {
"options": {
"no_code": "No code format",
"number": "Number",
"text": "Text"
}
},
"binary_sensor_device_class": {
"options": {
"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."""
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.const import (
ATTR_DOMAIN,
@ -23,7 +25,7 @@ from homeassistant.const import (
from homeassistant.core import Event, HomeAssistant, State, callback
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"
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"
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(
"config",

View File

@ -29,6 +29,16 @@ from tests.typing import WebSocketGenerator
"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",
{
@ -270,6 +280,12 @@ async def test_config_flow(
"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",
{"state": "{{ states('select.one') }}"},
@ -476,6 +492,16 @@ def get_suggested(schema, key):
},
"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",
{"state": "{{ states('select.one') }}"},
@ -1244,6 +1270,12 @@ async def test_option_flow_sensor_preview_config_entry_removed(
"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",
{"state": "{{ states('select.one') }}"},