diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py index 4fc3e2ad0b8..c0ebc9d3134 100644 --- a/homeassistant/components/deconz/alarm_control_panel.py +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -5,13 +5,19 @@ 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_IN_ALARM, AncillaryControl, ) -import voluptuous as vol from homeassistant.components.alarm_control_panel import ( DOMAIN, + FORMAT_NUMBER, SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, SUPPORT_ALARM_ARM_NIGHT, @@ -21,40 +27,39 @@ from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_ARMING, STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, + STATE_ALARM_TRIGGERED, ) 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_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_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, ANCILLARY_CONTROL_ARMED_STAY: STATE_ALARM_ARMED_HOME, + ANCILLARY_CONTROL_ARMING_AWAY: STATE_ALARM_ARMING, + ANCILLARY_CONTROL_ARMING_NIGHT: STATE_ALARM_ARMING, + ANCILLARY_CONTROL_ARMING_STAY: STATE_ALARM_ARMING, ANCILLARY_CONTROL_DISARMED: STATE_ALARM_DISARMED, + ANCILLARY_CONTROL_ENTRY_DELAY: STATE_ALARM_PENDING, + ANCILLARY_CONTROL_EXIT_DELAY: STATE_ALARM_PENDING, + ANCILLARY_CONTROL_IN_ALARM: STATE_ALARM_TRIGGERED, } +def get_alarm_system_for_unique_id(gateway, unique_id: str): + """Retrieve alarm system unique ID is registered to.""" + for alarm_system in gateway.api.alarm_systems.values(): + if unique_id in alarm_system.devices: + return alarm_system + + async def async_setup_entry(hass, config_entry, async_add_entities) -> None: """Set up the deCONZ alarm control panel devices. @@ -63,8 +68,6 @@ 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.async_get_current_platform() - @callback def async_add_alarm_control_panel(sensors=gateway.api.sensors.values()) -> None: """Add alarm control panel devices from deCONZ.""" @@ -75,15 +78,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None: if ( sensor.type in AncillaryControl.ZHATYPE and sensor.uniqueid not in gateway.entities[DOMAIN] + and get_alarm_system_for_unique_id(gateway, sensor.uniqueid) ): + 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( @@ -102,7 +102,7 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): TYPE = DOMAIN - _attr_code_arm_required = False + _attr_code_format = FORMAT_NUMBER _attr_supported_features = ( SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_NIGHT ) @@ -110,16 +110,12 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): def __init__(self, device, gateway) -> None: """Set up alarm control panel device.""" super().__init__(device, gateway) - self._service_to_device_panel_command = { - 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, - } + self.alarm_system = get_alarm_system_for_unique_id(gateway, device.uniqueid) @callback def async_update_callback(self, force_update: bool = False) -> None: """Update the control panels state.""" - keys = {"armed", "reachable"} + keys = {"panel", "reachable"} if force_update or ( self._device.changed_keys.intersection(keys) and self._device.state in DECONZ_TO_ALARM_STATE @@ -133,20 +129,16 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): async def async_alarm_arm_away(self, code: None = None) -> None: """Send arm away command.""" - await self._device.arm_away() + await self.alarm_system.arm_away(code) async def async_alarm_arm_home(self, code: None = None) -> None: """Send arm home command.""" - await self._device.arm_stay() + await self.alarm_system.arm_stay(code) async def async_alarm_arm_night(self, code: None = None) -> None: """Send arm night command.""" - await self._device.arm_night() + await self.alarm_system.arm_night(code) 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]() + await self.alarm_system.disarm(code) diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index c5336825878..2fa9ec87fe3 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -1,25 +1,20 @@ """Representation of a deCONZ remote or keypad.""" from pydeconz.sensor import ( - ANCILLARY_CONTROL_ARMED_AWAY, - ANCILLARY_CONTROL_ARMED_NIGHT, - ANCILLARY_CONTROL_ARMED_STAY, - ANCILLARY_CONTROL_DISARMED, + ANCILLARY_CONTROL_EMERGENCY, + ANCILLARY_CONTROL_FIRE, + ANCILLARY_CONTROL_INVALID_CODE, + ANCILLARY_CONTROL_PANIC, AncillaryControl, Switch, ) from homeassistant.const import ( - CONF_CODE, CONF_DEVICE_ID, CONF_EVENT, CONF_ID, CONF_UNIQUE_ID, CONF_XY, - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_DISARMED, ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -31,11 +26,11 @@ from .deconz_device import DeconzBase CONF_DECONZ_EVENT = "deconz_event" CONF_DECONZ_ALARM_EVENT = "deconz_alarm_event" -DECONZ_TO_ALARM_STATE = { - ANCILLARY_CONTROL_ARMED_AWAY: STATE_ALARM_ARMED_AWAY, - ANCILLARY_CONTROL_ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT, - ANCILLARY_CONTROL_ARMED_STAY: STATE_ALARM_ARMED_HOME, - ANCILLARY_CONTROL_DISARMED: STATE_ALARM_DISARMED, +SUPPORTED_DECONZ_ALARM_EVENTS = { + ANCILLARY_CONTROL_EMERGENCY, + ANCILLARY_CONTROL_FIRE, + ANCILLARY_CONTROL_INVALID_CODE, + ANCILLARY_CONTROL_PANIC, } @@ -155,31 +150,23 @@ class DeconzEvent(DeconzBase): class DeconzAlarmEvent(DeconzEvent): - """Alarm control panel companion event when user inputs a code.""" + """Alarm control panel companion event when user interacts with a keypad.""" @callback def async_update_callback(self, force_update=False): - """Fire the event if reason is that state is updated.""" + """Fire the event if reason is new action is updated.""" if ( self.gateway.ignore_state_updates or "action" not in self._device.changed_keys + or self._device.action not in SUPPORTED_DECONZ_ALARM_EVENTS ): return - try: - state, code, _area = self._device.action.split(",") - except (AttributeError, ValueError): - return - - if state not in DECONZ_TO_ALARM_STATE: - return - data = { CONF_ID: self.event_id, CONF_UNIQUE_ID: self.serial, CONF_DEVICE_ID: self.device_id, - CONF_EVENT: DECONZ_TO_ALARM_STATE[state], - CONF_CODE: code, + CONF_EVENT: self._device.action, } self.gateway.hass.bus.async_fire(CONF_DECONZ_ALARM_EVENT, data) diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 0ae9e8c98b0..69deac8b5e0 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", "requirements": [ - "pydeconz==82" + "pydeconz==83" ], "ssdp": [ { diff --git a/homeassistant/components/deconz/services.yaml b/homeassistant/components/deconz/services.yaml index fbaf47b009c..9084728a216 100644 --- a/homeassistant/components/deconz/services.yaml +++ b/homeassistant/components/deconz/services.yaml @@ -63,25 +63,3 @@ remove_orphaned_entries: example: "00212EFFFF012345" selector: text: - -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: >- - - "entry_delay": make panel beep until panel is disarmed. Beep interval is long. - - "exit_delay": make panel beep until panel is set to armed state. Beep interval is short. - - "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: - - "entry_delay" - - "exit_delay" - - "not_ready_to_arm" diff --git a/requirements_all.txt b/requirements_all.txt index 81b94ac5f43..e79382fef33 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1400,7 +1400,7 @@ pydaikin==2.4.4 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==82 +pydeconz==83 # homeassistant.components.delijn pydelijn==0.6.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 86c2921598b..9704fdc168a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -797,7 +797,7 @@ pycoolmasternet-async==0.1.2 pydaikin==2.4.4 # homeassistant.components.deconz -pydeconz==82 +pydeconz==83 # homeassistant.components.dexcom pydexcom==0.2.0 diff --git a/tests/components/deconz/test_alarm_control_panel.py b/tests/components/deconz/test_alarm_control_panel.py index b8de1f10d85..5bafdc2fbb6 100644 --- a/tests/components/deconz/test_alarm_control_panel.py +++ b/tests/components/deconz/test_alarm_control_panel.py @@ -6,24 +6,21 @@ 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, + ANCILLARY_CONTROL_IN_ALARM, + ANCILLARY_CONTROL_NOT_READY, ) 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_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_CODE, ATTR_ENTITY_ID, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_HOME, @@ -32,7 +29,10 @@ from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_ARMING, STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, + STATE_ALARM_TRIGGERED, STATE_UNAVAILABLE, ) @@ -52,38 +52,68 @@ async def test_no_sensors(hass, aioclient_mock): async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of alarm control panel entities.""" data = { + "alarmsystems": { + "0": { + "name": "default", + "config": { + "armmode": "armed_away", + "configured": True, + "disarmed_entry_delay": 0, + "disarmed_exit_delay": 0, + "armed_away_entry_delay": 120, + "armed_away_exit_delay": 120, + "armed_away_trigger_duration": 120, + "armed_stay_entry_delay": 120, + "armed_stay_exit_delay": 120, + "armed_stay_trigger_duration": 120, + "armed_night_entry_delay": 120, + "armed_night_exit_delay": 120, + "armed_night_trigger_duration": 120, + }, + "state": {"armstate": "armed_away", "seconds_remaining": 0}, + "devices": { + "00:00:00:00:00:00:00:00-00": {}, + "00:15:8d:00:02:af:95:f9-01-0101": { + "armmask": "AN", + "trigger": "state/vibration", + }, + }, + } + }, "sensors": { "0": { "config": { - "armed": "disarmed", - "enrolled": 0, + "battery": 95, + "enrolled": 1, "on": True, - "panel": "disarmed", "pending": [], "reachable": True, }, "ep": 1, - "etag": "3c4008d74035dfaa1f0bb30d24468b12", - "lastseen": "2021-04-02T13:07Z", - "manufacturername": "Universal Electronics Inc", - "modelid": "URC4450BC0-X-R", + "etag": "5aaa1c6bae8501f59929539c6e8f44d6", + "lastseen": "2021-07-25T18:07Z", + "manufacturername": "lk", + "modelid": "ZB-KeypadGeneric-D0002", "name": "Keypad", "state": { - "action": "armed_away,1111,55", - "lastupdated": "2021-04-02T13:08:18.937", + "action": "armed_stay", + "lastupdated": "2021-07-25T18:02:51.172", "lowbattery": False, - "tampered": True, + "panel": "exit_delay", + "seconds_remaining": 55, + "tampered": False, }, + "swversion": "3.13", "type": "ZHAAncillaryControl", - "uniqueid": "00:0d:6f:00:13:4f:61:39-01-0501", + "uniqueid": "00:00:00:00:00:00:00:00-00", } - } + }, } with patch.dict(DECONZ_WEB_REQUEST, data): config_entry = await setup_deconz_integration(hass, aioclient_mock) - assert len(hass.states.async_all()) == 2 - assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_DISARMED + assert len(hass.states.async_all()) == 3 + assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_PENDING # Event signals alarm control panel armed away @@ -92,7 +122,7 @@ async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket): "e": "changed", "r": "sensors", "id": "0", - "config": {"armed": ANCILLARY_CONTROL_ARMED_AWAY}, + "state": {"panel": ANCILLARY_CONTROL_ARMED_AWAY}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() @@ -106,7 +136,7 @@ async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket): "e": "changed", "r": "sensors", "id": "0", - "config": {"armed": ANCILLARY_CONTROL_ARMED_NIGHT}, + "state": {"panel": ANCILLARY_CONTROL_ARMED_NIGHT}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() @@ -122,29 +152,13 @@ async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket): "e": "changed", "r": "sensors", "id": "0", - "config": {"armed": ANCILLARY_CONTROL_ARMED_STAY}, + "state": {"panel": ANCILLARY_CONTROL_ARMED_STAY}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_ARMED_HOME - # Event signals alarm control panel armed night - - event_changed_sensor = { - "t": "event", - "e": "changed", - "r": "sensors", - "id": "0", - "config": {"armed": ANCILLARY_CONTROL_ARMED_NIGHT}, - } - await mock_deconz_websocket(data=event_changed_sensor) - await hass.async_block_till_done() - - assert ( - hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_ARMED_NIGHT - ) - # Event signals alarm control panel disarmed event_changed_sensor = { @@ -152,116 +166,139 @@ async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket): "e": "changed", "r": "sensors", "id": "0", - "config": {"armed": ANCILLARY_CONTROL_DISARMED}, + "state": {"panel": ANCILLARY_CONTROL_DISARMED}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_DISARMED + # Event signals alarm control panel arming + + for arming_event in { + ANCILLARY_CONTROL_ARMING_AWAY, + ANCILLARY_CONTROL_ARMING_NIGHT, + ANCILLARY_CONTROL_ARMING_STAY, + }: + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "0", + "state": {"panel": arming_event}, + } + await mock_deconz_websocket(data=event_changed_sensor) + await hass.async_block_till_done() + + assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_ARMING + + # Event signals alarm control panel pending + + for pending_event in {ANCILLARY_CONTROL_ENTRY_DELAY, ANCILLARY_CONTROL_EXIT_DELAY}: + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "0", + "state": {"panel": pending_event}, + } + await mock_deconz_websocket(data=event_changed_sensor) + await hass.async_block_till_done() + + assert ( + hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_PENDING + ) + + # Event signals alarm control panel triggered + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "0", + "state": {"panel": ANCILLARY_CONTROL_IN_ALARM}, + } + await mock_deconz_websocket(data=event_changed_sensor) + await hass.async_block_till_done() + + assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_TRIGGERED + + # Event signals alarm control panel unknown state keeps previous state + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "0", + "state": {"panel": ANCILLARY_CONTROL_NOT_READY}, + } + await mock_deconz_websocket(data=event_changed_sensor) + await hass.async_block_till_done() + + assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_TRIGGERED + # Verify service calls - mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/0/config") - # Service set alarm to away mode + mock_deconz_put_request( + aioclient_mock, config_entry.data, "/alarmsystems/0/arm_away" + ) + await hass.services.async_call( ALARM_CONTROL_PANEL_DOMAIN, SERVICE_ALARM_ARM_AWAY, - {ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, + {ATTR_ENTITY_ID: "alarm_control_panel.keypad", ATTR_CODE: "1234"}, blocking=True, ) - assert aioclient_mock.mock_calls[1][2] == { - "armed": ANCILLARY_CONTROL_ARMED_AWAY, - "panel": ANCILLARY_CONTROL_ARMED_AWAY, - } + assert aioclient_mock.mock_calls[1][2] == {"code0": "1234"} # Service set alarm to home mode + mock_deconz_put_request( + aioclient_mock, config_entry.data, "/alarmsystems/0/arm_stay" + ) + await hass.services.async_call( ALARM_CONTROL_PANEL_DOMAIN, SERVICE_ALARM_ARM_HOME, - {ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, + {ATTR_ENTITY_ID: "alarm_control_panel.keypad", ATTR_CODE: "2345"}, blocking=True, ) - assert aioclient_mock.mock_calls[2][2] == { - "armed": ANCILLARY_CONTROL_ARMED_STAY, - "panel": ANCILLARY_CONTROL_ARMED_STAY, - } + assert aioclient_mock.mock_calls[2][2] == {"code0": "2345"} # Service set alarm to night mode + mock_deconz_put_request( + aioclient_mock, config_entry.data, "/alarmsystems/0/arm_night" + ) + await hass.services.async_call( ALARM_CONTROL_PANEL_DOMAIN, SERVICE_ALARM_ARM_NIGHT, - {ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, + {ATTR_ENTITY_ID: "alarm_control_panel.keypad", ATTR_CODE: "3456"}, blocking=True, ) - assert aioclient_mock.mock_calls[3][2] == { - "armed": ANCILLARY_CONTROL_ARMED_NIGHT, - "panel": ANCILLARY_CONTROL_ARMED_NIGHT, - } + assert aioclient_mock.mock_calls[3][2] == {"code0": "3456"} # Service set alarm to disarmed + mock_deconz_put_request(aioclient_mock, config_entry.data, "/alarmsystems/0/disarm") + await hass.services.async_call( ALARM_CONTROL_PANEL_DOMAIN, SERVICE_ALARM_DISARM, - {ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, + {ATTR_ENTITY_ID: "alarm_control_panel.keypad", ATTR_CODE: "4567"}, blocking=True, ) - assert aioclient_mock.mock_calls[4][2] == { - "armed": ANCILLARY_CONTROL_DISARMED, - "panel": ANCILLARY_CONTROL_DISARMED, - } - - # Verify entity service calls - - # 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[5][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[6][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[7][2] == { - "panel": ANCILLARY_CONTROL_NOT_READY_TO_ARM - } + assert aioclient_mock.mock_calls[4][2] == {"code0": "4567"} await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(states) == 2 + assert len(states) == 3 for state in states: assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 418545f11cf..ca76631728e 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -2,13 +2,20 @@ from unittest.mock import patch +from pydeconz.sensor import ( + ANCILLARY_CONTROL_ARMED_AWAY, + ANCILLARY_CONTROL_EMERGENCY, + ANCILLARY_CONTROL_FIRE, + ANCILLARY_CONTROL_INVALID_CODE, + ANCILLARY_CONTROL_PANIC, +) + from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.deconz_event import ( CONF_DECONZ_ALARM_EVENT, CONF_DECONZ_EVENT, ) from homeassistant.const import ( - CONF_CODE, CONF_DEVICE_ID, CONF_EVENT, CONF_ID, @@ -200,39 +207,69 @@ async def test_deconz_events(hass, aioclient_mock, mock_deconz_websocket): async def test_deconz_alarm_events(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of deconz alarm events.""" data = { + "alarmsystems": { + "0": { + "name": "default", + "config": { + "armmode": "armed_away", + "configured": True, + "disarmed_entry_delay": 0, + "disarmed_exit_delay": 0, + "armed_away_entry_delay": 120, + "armed_away_exit_delay": 120, + "armed_away_trigger_duration": 120, + "armed_stay_entry_delay": 120, + "armed_stay_exit_delay": 120, + "armed_stay_trigger_duration": 120, + "armed_night_entry_delay": 120, + "armed_night_exit_delay": 120, + "armed_night_trigger_duration": 120, + }, + "state": {"armstate": "armed_away", "seconds_remaining": 0}, + "devices": { + "00:00:00:00:00:00:00:01-00": {}, + "00:15:8d:00:02:af:95:f9-01-0101": { + "armmask": "AN", + "trigger": "state/vibration", + }, + }, + } + }, "sensors": { "1": { "config": { - "armed": "disarmed", - "enrolled": 0, + "battery": 95, + "enrolled": 1, "on": True, - "panel": "disarmed", "pending": [], "reachable": True, }, "ep": 1, - "etag": "3c4008d74035dfaa1f0bb30d24468b12", - "lastseen": "2021-04-02T13:07Z", - "manufacturername": "Universal Electronics Inc", - "modelid": "URC4450BC0-X-R", + "etag": "5aaa1c6bae8501f59929539c6e8f44d6", + "lastseen": "2021-07-25T18:07Z", + "manufacturername": "lk", + "modelid": "ZB-KeypadGeneric-D0002", "name": "Keypad", "state": { - "action": "armed_away,1111,55", - "lastupdated": "2021-04-02T13:08:18.937", + "action": "invalid_code", + "lastupdated": "2021-07-25T18:02:51.172", "lowbattery": False, - "tampered": True, + "panel": "exit_delay", + "seconds_remaining": 55, + "tampered": False, }, + "swversion": "3.13", "type": "ZHAAncillaryControl", - "uniqueid": "00:00:00:00:00:00:00:01-01-0501", + "uniqueid": "00:00:00:00:00:00:00:01-00", } - } + }, } with patch.dict(DECONZ_WEB_REQUEST, data): config_entry = await setup_deconz_integration(hass, aioclient_mock) device_registry = await hass.helpers.device_registry.async_get_registry() - assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_all()) == 3 # 1 alarm control device + 2 additional devices for deconz service and host assert ( len(async_entries_for_config_entry(device_registry, config_entry.entry_id)) == 3 @@ -240,14 +277,14 @@ async def test_deconz_alarm_events(hass, aioclient_mock, mock_deconz_websocket): captured_events = async_capture_events(hass, CONF_DECONZ_ALARM_EVENT) - # Armed away event + # Emergency event event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", - "state": {"action": "armed_away,1234,1"}, + "state": {"action": ANCILLARY_CONTROL_EMERGENCY}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() @@ -261,86 +298,113 @@ async def test_deconz_alarm_events(hass, aioclient_mock, mock_deconz_websocket): CONF_ID: "keypad", CONF_UNIQUE_ID: "00:00:00:00:00:00:00:01", CONF_DEVICE_ID: device.id, - CONF_EVENT: "armed_away", - CONF_CODE: "1234", + CONF_EVENT: ANCILLARY_CONTROL_EMERGENCY, } - # Unsupported events - - # Bad action string; string is None + # Fire event event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", - "state": {"action": None}, + "state": {"action": ANCILLARY_CONTROL_FIRE}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(captured_events) == 1 + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:01")} + ) - # Bad action string; empty string + assert len(captured_events) == 2 + assert captured_events[1].data == { + CONF_ID: "keypad", + CONF_UNIQUE_ID: "00:00:00:00:00:00:00:01", + CONF_DEVICE_ID: device.id, + CONF_EVENT: ANCILLARY_CONTROL_FIRE, + } + + # Invalid code event event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", - "state": {"action": ""}, + "state": {"action": ANCILLARY_CONTROL_INVALID_CODE}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(captured_events) == 1 + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:01")} + ) - # Bad action string; too few "," + assert len(captured_events) == 3 + assert captured_events[2].data == { + CONF_ID: "keypad", + CONF_UNIQUE_ID: "00:00:00:00:00:00:00:01", + CONF_DEVICE_ID: device.id, + CONF_EVENT: ANCILLARY_CONTROL_INVALID_CODE, + } + + # Panic event event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", - "state": {"action": "armed_away,1234"}, + "state": {"action": ANCILLARY_CONTROL_PANIC}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(captured_events) == 1 + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:01")} + ) - # Bad action string; unsupported command + assert len(captured_events) == 4 + assert captured_events[3].data == { + CONF_ID: "keypad", + CONF_UNIQUE_ID: "00:00:00:00:00:00:00:01", + CONF_DEVICE_ID: device.id, + CONF_EVENT: ANCILLARY_CONTROL_PANIC, + } + + # Only care for changes to specific action events event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", - "state": {"action": "unsupported,1234,1"}, + "state": {"action": ANCILLARY_CONTROL_ARMED_AWAY}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(captured_events) == 1 + assert len(captured_events) == 4 - # Only care for changes to action + # Only care for action events event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", - "config": {"panel": "armed_away"}, + "state": {"panel": ANCILLARY_CONTROL_ARMED_AWAY}, } await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(captured_events) == 1 + assert len(captured_events) == 4 await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_all()) == 3 for state in states: assert state.state == STATE_UNAVAILABLE