Add improved scene support to the alarm_control_panel integration (#28269)

* Add improved scene support to the alarm_control_panel integration

* Add service description for alarm_arm_custom_bypass
This commit is contained in:
Santobert 2019-11-01 21:36:18 +01:00 committed by Paulus Schoutsen
parent 1fb377e61e
commit 12f1a8f551
3 changed files with 242 additions and 0 deletions

View file

@ -0,0 +1,84 @@
"""Reproduce an Alarm control panel state."""
import asyncio
import logging
from typing import Iterable, Optional
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_CUSTOM_BYPASS,
SERVICE_ALARM_ARM_HOME,
SERVICE_ALARM_ARM_NIGHT,
SERVICE_ALARM_DISARM,
SERVICE_ALARM_TRIGGER,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED,
)
from homeassistant.core import Context, State
from homeassistant.helpers.typing import HomeAssistantType
from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
VALID_STATES = {
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED,
}
async def _async_reproduce_state(
hass: HomeAssistantType, state: State, context: Optional[Context] = None
) -> None:
"""Reproduce a single state."""
cur_state = hass.states.get(state.entity_id)
if cur_state is None:
_LOGGER.warning("Unable to find entity %s", state.entity_id)
return
if state.state not in VALID_STATES:
_LOGGER.warning(
"Invalid state specified for %s: %s", state.entity_id, state.state
)
return
# Return if we are already at the right state.
if cur_state.state == state.state:
return
service_data = {ATTR_ENTITY_ID: state.entity_id}
if state.state == STATE_ALARM_ARMED_AWAY:
service = SERVICE_ALARM_ARM_AWAY
elif state.state == STATE_ALARM_ARMED_CUSTOM_BYPASS:
service = SERVICE_ALARM_ARM_CUSTOM_BYPASS
elif state.state == STATE_ALARM_ARMED_HOME:
service = SERVICE_ALARM_ARM_HOME
elif state.state == STATE_ALARM_ARMED_NIGHT:
service = SERVICE_ALARM_ARM_NIGHT
elif state.state == STATE_ALARM_DISARMED:
service = SERVICE_ALARM_DISARM
elif state.state == STATE_ALARM_TRIGGERED:
service = SERVICE_ALARM_TRIGGER
await hass.services.async_call(
DOMAIN, service, service_data, context=context, blocking=True
)
async def async_reproduce_states(
hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None
) -> None:
"""Reproduce Alarm control panel states."""
await asyncio.gather(
*(_async_reproduce_state(hass, state, context) for state in states)
)

View file

@ -10,6 +10,16 @@ alarm_disarm:
description: An optional code to disarm the alarm control panel with.
example: 1234
alarm_arm_custom_bypass:
description: Send arm custom bypass command.
fields:
entity_id:
description: Name of alarm control panel to arm custom bypass.
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to arm custom bypass the alarm control panel with.
example: 1234
alarm_arm_home:
description: Send the alarm the command for arm home.
fields:

View file

@ -0,0 +1,148 @@
"""Test reproduce state for Alarm control panel."""
from homeassistant.const import (
SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_CUSTOM_BYPASS,
SERVICE_ALARM_ARM_HOME,
SERVICE_ALARM_ARM_NIGHT,
SERVICE_ALARM_DISARM,
SERVICE_ALARM_TRIGGER,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED,
)
from homeassistant.core import State
from tests.common import async_mock_service
async def test_reproducing_states(hass, caplog):
"""Test reproducing Alarm control panel states."""
hass.states.async_set(
"alarm_control_panel.entity_armed_away", STATE_ALARM_ARMED_AWAY, {}
)
hass.states.async_set(
"alarm_control_panel.entity_armed_custom_bypass",
STATE_ALARM_ARMED_CUSTOM_BYPASS,
{},
)
hass.states.async_set(
"alarm_control_panel.entity_armed_home", STATE_ALARM_ARMED_HOME, {}
)
hass.states.async_set(
"alarm_control_panel.entity_armed_night", STATE_ALARM_ARMED_NIGHT, {}
)
hass.states.async_set(
"alarm_control_panel.entity_disarmed", STATE_ALARM_DISARMED, {}
)
hass.states.async_set(
"alarm_control_panel.entity_triggered", STATE_ALARM_TRIGGERED, {}
)
arm_away_calls = async_mock_service(
hass, "alarm_control_panel", SERVICE_ALARM_ARM_AWAY
)
arm_custom_bypass_calls = async_mock_service(
hass, "alarm_control_panel", SERVICE_ALARM_ARM_CUSTOM_BYPASS
)
arm_home_calls = async_mock_service(
hass, "alarm_control_panel", SERVICE_ALARM_ARM_HOME
)
arm_night_calls = async_mock_service(
hass, "alarm_control_panel", SERVICE_ALARM_ARM_NIGHT
)
disarm_calls = async_mock_service(hass, "alarm_control_panel", SERVICE_ALARM_DISARM)
trigger_calls = async_mock_service(
hass, "alarm_control_panel", SERVICE_ALARM_TRIGGER
)
# These calls should do nothing as entities already in desired state
await hass.helpers.state.async_reproduce_state(
[
State("alarm_control_panel.entity_armed_away", STATE_ALARM_ARMED_AWAY),
State(
"alarm_control_panel.entity_armed_custom_bypass",
STATE_ALARM_ARMED_CUSTOM_BYPASS,
),
State("alarm_control_panel.entity_armed_home", STATE_ALARM_ARMED_HOME),
State("alarm_control_panel.entity_armed_night", STATE_ALARM_ARMED_NIGHT),
State("alarm_control_panel.entity_disarmed", STATE_ALARM_DISARMED),
State("alarm_control_panel.entity_triggered", STATE_ALARM_TRIGGERED),
],
blocking=True,
)
assert len(arm_away_calls) == 0
assert len(arm_custom_bypass_calls) == 0
assert len(arm_home_calls) == 0
assert len(arm_night_calls) == 0
assert len(disarm_calls) == 0
assert len(trigger_calls) == 0
# Test invalid state is handled
await hass.helpers.state.async_reproduce_state(
[State("alarm_control_panel.entity_triggered", "not_supported")], blocking=True
)
assert "not_supported" in caplog.text
assert len(arm_away_calls) == 0
assert len(arm_custom_bypass_calls) == 0
assert len(arm_home_calls) == 0
assert len(arm_night_calls) == 0
assert len(disarm_calls) == 0
assert len(trigger_calls) == 0
# Make sure correct services are called
await hass.helpers.state.async_reproduce_state(
[
State("alarm_control_panel.entity_armed_away", STATE_ALARM_TRIGGERED),
State(
"alarm_control_panel.entity_armed_custom_bypass", STATE_ALARM_ARMED_AWAY
),
State(
"alarm_control_panel.entity_armed_home", STATE_ALARM_ARMED_CUSTOM_BYPASS
),
State("alarm_control_panel.entity_armed_night", STATE_ALARM_ARMED_HOME),
State("alarm_control_panel.entity_disarmed", STATE_ALARM_ARMED_NIGHT),
State("alarm_control_panel.entity_triggered", STATE_ALARM_DISARMED),
# Should not raise
State("alarm_control_panel.non_existing", "on"),
],
blocking=True,
)
assert len(arm_away_calls) == 1
assert arm_away_calls[0].domain == "alarm_control_panel"
assert arm_away_calls[0].data == {
"entity_id": "alarm_control_panel.entity_armed_custom_bypass"
}
assert len(arm_custom_bypass_calls) == 1
assert arm_custom_bypass_calls[0].domain == "alarm_control_panel"
assert arm_custom_bypass_calls[0].data == {
"entity_id": "alarm_control_panel.entity_armed_home"
}
assert len(arm_home_calls) == 1
assert arm_home_calls[0].domain == "alarm_control_panel"
assert arm_home_calls[0].data == {
"entity_id": "alarm_control_panel.entity_armed_night"
}
assert len(arm_night_calls) == 1
assert arm_night_calls[0].domain == "alarm_control_panel"
assert arm_night_calls[0].data == {
"entity_id": "alarm_control_panel.entity_disarmed"
}
assert len(disarm_calls) == 1
assert disarm_calls[0].domain == "alarm_control_panel"
assert disarm_calls[0].data == {"entity_id": "alarm_control_panel.entity_triggered"}
assert len(trigger_calls) == 1
assert trigger_calls[0].domain == "alarm_control_panel"
assert trigger_calls[0].data == {
"entity_id": "alarm_control_panel.entity_armed_away"
}