diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py index 7a6ed19bcd6..ce8467c736a 100644 --- a/homeassistant/components/deconz/alarm_control_panel.py +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -8,6 +8,7 @@ from pydeconz.sensor import ( ANCILLARY_CONTROL_DISARMED, AncillaryControl, ) +import voluptuous as vol from homeassistant.components.alarm_control_panel import ( DOMAIN, @@ -24,12 +25,35 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import callback +from homeassistant.helpers import entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import NEW_SENSOR from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry +PANEL_ARMING_AWAY = "arming_away" +PANEL_ARMING_HOME = "arming_home" +PANEL_ARMING_NIGHT = "arming_night" +PANEL_ENTRY_DELAY = "entry_delay" +PANEL_EXIT_DELAY = "exit_delay" +PANEL_NOT_READY_TO_ARM = "not_ready_to_arm" + +SERVICE_ALARM_PANEL_STATE = "alarm_panel_state" +CONF_ALARM_PANEL_STATE = "panel_state" +SERVICE_ALARM_PANEL_STATE_SCHEMA = { + vol.Required(CONF_ALARM_PANEL_STATE): vol.In( + [ + PANEL_ARMING_AWAY, + PANEL_ARMING_HOME, + PANEL_ARMING_NIGHT, + PANEL_ENTRY_DELAY, + PANEL_EXIT_DELAY, + PANEL_NOT_READY_TO_ARM, + ] + ) +} + DECONZ_TO_ALARM_STATE = { ANCILLARY_CONTROL_ARMED_AWAY: STATE_ALARM_ARMED_AWAY, ANCILLARY_CONTROL_ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT, @@ -46,6 +70,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None: gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() + platform = entity_platform.current_platform.get() + @callback def async_add_alarm_control_panel(sensors=gateway.api.sensors.values()) -> None: """Add alarm control panel devices from deCONZ.""" @@ -60,6 +86,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None: entities.append(DeconzAlarmControlPanel(sensor, gateway)) if entities: + platform.async_register_entity_service( + SERVICE_ALARM_PANEL_STATE, + SERVICE_ALARM_PANEL_STATE_SCHEMA, + "async_set_panel_state", + ) async_add_entities(entities) config_entry.async_on_unload( @@ -86,6 +117,15 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): self._features |= SUPPORT_ALARM_ARM_HOME self._features |= SUPPORT_ALARM_ARM_NIGHT + self._service_to_device_panel_command = { + PANEL_ARMING_AWAY: self._device.arming_away, + PANEL_ARMING_HOME: self._device.arming_stay, + PANEL_ARMING_NIGHT: self._device.arming_night, + PANEL_ENTRY_DELAY: self._device.entry_delay, + PANEL_EXIT_DELAY: self._device.exit_delay, + PANEL_NOT_READY_TO_ARM: self._device.not_ready_to_arm, + } + @property def supported_features(self) -> int: """Return the list of supported features.""" @@ -131,3 +171,7 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): async def async_alarm_disarm(self, code: None = None) -> None: """Send disarm command.""" await self._device.disarm() + + async def async_set_panel_state(self, panel_state: str) -> None: + """Send panel_state command.""" + await self._service_to_device_panel_command[panel_state]() diff --git a/homeassistant/components/deconz/services.yaml b/homeassistant/components/deconz/services.yaml index 3bce097f7d3..684e86e223c 100644 --- a/homeassistant/components/deconz/services.yaml +++ b/homeassistant/components/deconz/services.yaml @@ -64,3 +64,29 @@ remove_orphaned_entries: It can be found as part of the integration name. Useful if you run multiple deCONZ integrations. example: "00212EFFFF012345" + +alarm_panel_state: + name: Alarm panel state + description: Put keypad panel in an intermediate state, to help with visual and audible cues to the user. + target: + entity: + integration: deconz + domain: alarm_control_panel + fields: + panel_state: + name: Panel state + description: >- + - "arming_away/home/night": set panel in right visual arming state. + - "entry_delay": make panel beep until panel is disarmed. Beep interval is short. + - "exit_delay": make panel beep until panel is set to armed state. Beep interval is long. + - "not_ready_to_arm": turn on yellow status led on the panel. Indicate not all conditions for arming are met. + required: true + selector: + select: + options: + - "arming_away" + - "arming_home" + - "arming_night" + - "entry_delay" + - "exit_delay" + - "not_ready_to_arm" diff --git a/tests/components/deconz/test_alarm_control_panel.py b/tests/components/deconz/test_alarm_control_panel.py index b0425d5701a..951a15b6580 100644 --- a/tests/components/deconz/test_alarm_control_panel.py +++ b/tests/components/deconz/test_alarm_control_panel.py @@ -6,12 +6,29 @@ from pydeconz.sensor import ( ANCILLARY_CONTROL_ARMED_AWAY, ANCILLARY_CONTROL_ARMED_NIGHT, ANCILLARY_CONTROL_ARMED_STAY, + ANCILLARY_CONTROL_ARMING_AWAY, + ANCILLARY_CONTROL_ARMING_NIGHT, + ANCILLARY_CONTROL_ARMING_STAY, ANCILLARY_CONTROL_DISARMED, + ANCILLARY_CONTROL_ENTRY_DELAY, + ANCILLARY_CONTROL_EXIT_DELAY, + ANCILLARY_CONTROL_NOT_READY_TO_ARM, ) from homeassistant.components.alarm_control_panel import ( DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, ) +from homeassistant.components.deconz.alarm_control_panel import ( + CONF_ALARM_PANEL_STATE, + PANEL_ARMING_AWAY, + PANEL_ARMING_HOME, + PANEL_ARMING_NIGHT, + PANEL_ENTRY_DELAY, + PANEL_EXIT_DELAY, + PANEL_NOT_READY_TO_ARM, + SERVICE_ALARM_PANEL_STATE, +) +from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_ALARM_ARM_AWAY, @@ -204,6 +221,88 @@ async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket): "panel": ANCILLARY_CONTROL_DISARMED, } + # Verify entity service calls + + # Service set panel to arming away + + await hass.services.async_call( + DECONZ_DOMAIN, + SERVICE_ALARM_PANEL_STATE, + { + ATTR_ENTITY_ID: "alarm_control_panel.keypad", + CONF_ALARM_PANEL_STATE: PANEL_ARMING_AWAY, + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[5][2] == {"panel": ANCILLARY_CONTROL_ARMING_AWAY} + + # Service set panel to arming home + + await hass.services.async_call( + DECONZ_DOMAIN, + SERVICE_ALARM_PANEL_STATE, + { + ATTR_ENTITY_ID: "alarm_control_panel.keypad", + CONF_ALARM_PANEL_STATE: PANEL_ARMING_HOME, + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[6][2] == {"panel": ANCILLARY_CONTROL_ARMING_STAY} + + # Service set panel to arming night + + await hass.services.async_call( + DECONZ_DOMAIN, + SERVICE_ALARM_PANEL_STATE, + { + ATTR_ENTITY_ID: "alarm_control_panel.keypad", + CONF_ALARM_PANEL_STATE: PANEL_ARMING_NIGHT, + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[7][2] == {"panel": ANCILLARY_CONTROL_ARMING_NIGHT} + + # Service set panel to entry delay + + await hass.services.async_call( + DECONZ_DOMAIN, + SERVICE_ALARM_PANEL_STATE, + { + ATTR_ENTITY_ID: "alarm_control_panel.keypad", + CONF_ALARM_PANEL_STATE: PANEL_ENTRY_DELAY, + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[8][2] == {"panel": ANCILLARY_CONTROL_ENTRY_DELAY} + + # Service set panel to exit delay + + await hass.services.async_call( + DECONZ_DOMAIN, + SERVICE_ALARM_PANEL_STATE, + { + ATTR_ENTITY_ID: "alarm_control_panel.keypad", + CONF_ALARM_PANEL_STATE: PANEL_EXIT_DELAY, + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[9][2] == {"panel": ANCILLARY_CONTROL_EXIT_DELAY} + + # Service set panel to not ready to arm + + await hass.services.async_call( + DECONZ_DOMAIN, + SERVICE_ALARM_PANEL_STATE, + { + ATTR_ENTITY_ID: "alarm_control_panel.keypad", + CONF_ALARM_PANEL_STATE: PANEL_NOT_READY_TO_ARM, + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[10][2] == { + "panel": ANCILLARY_CONTROL_NOT_READY_TO_ARM + } + await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all()