Update alarm control panel and deconz alarm event to reflect the finalized implementation in deCONZ (#54936)

* Update alarm control panel and deconz alarm event to reflect the new implementation in deCONZ

* Bump dependency to v83
This commit is contained in:
Robert Svensson 2021-08-23 20:56:45 +02:00 committed by GitHub
parent 4d452dbccf
commit 6ad0e0220a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 294 additions and 236 deletions

View File

@ -5,13 +5,19 @@ from pydeconz.sensor import (
ANCILLARY_CONTROL_ARMED_AWAY, ANCILLARY_CONTROL_ARMED_AWAY,
ANCILLARY_CONTROL_ARMED_NIGHT, ANCILLARY_CONTROL_ARMED_NIGHT,
ANCILLARY_CONTROL_ARMED_STAY, ANCILLARY_CONTROL_ARMED_STAY,
ANCILLARY_CONTROL_ARMING_AWAY,
ANCILLARY_CONTROL_ARMING_NIGHT,
ANCILLARY_CONTROL_ARMING_STAY,
ANCILLARY_CONTROL_DISARMED, ANCILLARY_CONTROL_DISARMED,
ANCILLARY_CONTROL_ENTRY_DELAY,
ANCILLARY_CONTROL_EXIT_DELAY,
ANCILLARY_CONTROL_IN_ALARM,
AncillaryControl, AncillaryControl,
) )
import voluptuous as vol
from homeassistant.components.alarm_control_panel import ( from homeassistant.components.alarm_control_panel import (
DOMAIN, DOMAIN,
FORMAT_NUMBER,
SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME, SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT, SUPPORT_ALARM_ARM_NIGHT,
@ -21,40 +27,39 @@ from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED,
) )
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import NEW_SENSOR from .const import NEW_SENSOR
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry 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 = { DECONZ_TO_ALARM_STATE = {
ANCILLARY_CONTROL_ARMED_AWAY: STATE_ALARM_ARMED_AWAY, ANCILLARY_CONTROL_ARMED_AWAY: STATE_ALARM_ARMED_AWAY,
ANCILLARY_CONTROL_ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT, ANCILLARY_CONTROL_ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT,
ANCILLARY_CONTROL_ARMED_STAY: STATE_ALARM_ARMED_HOME, 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_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: async def async_setup_entry(hass, config_entry, async_add_entities) -> None:
"""Set up the deCONZ alarm control panel devices. """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 = get_gateway_from_config_entry(hass, config_entry)
gateway.entities[DOMAIN] = set() gateway.entities[DOMAIN] = set()
platform = entity_platform.async_get_current_platform()
@callback @callback
def async_add_alarm_control_panel(sensors=gateway.api.sensors.values()) -> None: def async_add_alarm_control_panel(sensors=gateway.api.sensors.values()) -> None:
"""Add alarm control panel devices from deCONZ.""" """Add alarm control panel devices from deCONZ."""
@ -75,15 +78,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None:
if ( if (
sensor.type in AncillaryControl.ZHATYPE sensor.type in AncillaryControl.ZHATYPE
and sensor.uniqueid not in gateway.entities[DOMAIN] and sensor.uniqueid not in gateway.entities[DOMAIN]
and get_alarm_system_for_unique_id(gateway, sensor.uniqueid)
): ):
entities.append(DeconzAlarmControlPanel(sensor, gateway)) entities.append(DeconzAlarmControlPanel(sensor, gateway))
if entities: if entities:
platform.async_register_entity_service(
SERVICE_ALARM_PANEL_STATE,
SERVICE_ALARM_PANEL_STATE_SCHEMA,
"async_set_panel_state",
)
async_add_entities(entities) async_add_entities(entities)
config_entry.async_on_unload( config_entry.async_on_unload(
@ -102,7 +102,7 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity):
TYPE = DOMAIN TYPE = DOMAIN
_attr_code_arm_required = False _attr_code_format = FORMAT_NUMBER
_attr_supported_features = ( _attr_supported_features = (
SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_NIGHT 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: def __init__(self, device, gateway) -> None:
"""Set up alarm control panel device.""" """Set up alarm control panel device."""
super().__init__(device, gateway) super().__init__(device, gateway)
self._service_to_device_panel_command = { self.alarm_system = get_alarm_system_for_unique_id(gateway, device.uniqueid)
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,
}
@callback @callback
def async_update_callback(self, force_update: bool = False) -> None: def async_update_callback(self, force_update: bool = False) -> None:
"""Update the control panels state.""" """Update the control panels state."""
keys = {"armed", "reachable"} keys = {"panel", "reachable"}
if force_update or ( if force_update or (
self._device.changed_keys.intersection(keys) self._device.changed_keys.intersection(keys)
and self._device.state in DECONZ_TO_ALARM_STATE 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: async def async_alarm_arm_away(self, code: None = None) -> None:
"""Send arm away command.""" """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: async def async_alarm_arm_home(self, code: None = None) -> None:
"""Send arm home command.""" """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: async def async_alarm_arm_night(self, code: None = None) -> None:
"""Send arm night command.""" """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: async def async_alarm_disarm(self, code: None = None) -> None:
"""Send disarm command.""" """Send disarm command."""
await self._device.disarm() await self.alarm_system.disarm(code)
async def async_set_panel_state(self, panel_state: str) -> None:
"""Send panel_state command."""
await self._service_to_device_panel_command[panel_state]()

View File

@ -1,25 +1,20 @@
"""Representation of a deCONZ remote or keypad.""" """Representation of a deCONZ remote or keypad."""
from pydeconz.sensor import ( from pydeconz.sensor import (
ANCILLARY_CONTROL_ARMED_AWAY, ANCILLARY_CONTROL_EMERGENCY,
ANCILLARY_CONTROL_ARMED_NIGHT, ANCILLARY_CONTROL_FIRE,
ANCILLARY_CONTROL_ARMED_STAY, ANCILLARY_CONTROL_INVALID_CODE,
ANCILLARY_CONTROL_DISARMED, ANCILLARY_CONTROL_PANIC,
AncillaryControl, AncillaryControl,
Switch, Switch,
) )
from homeassistant.const import ( from homeassistant.const import (
CONF_CODE,
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_EVENT, CONF_EVENT,
CONF_ID, CONF_ID,
CONF_UNIQUE_ID, CONF_UNIQUE_ID,
CONF_XY, CONF_XY,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED,
) )
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -31,11 +26,11 @@ from .deconz_device import DeconzBase
CONF_DECONZ_EVENT = "deconz_event" CONF_DECONZ_EVENT = "deconz_event"
CONF_DECONZ_ALARM_EVENT = "deconz_alarm_event" CONF_DECONZ_ALARM_EVENT = "deconz_alarm_event"
DECONZ_TO_ALARM_STATE = { SUPPORTED_DECONZ_ALARM_EVENTS = {
ANCILLARY_CONTROL_ARMED_AWAY: STATE_ALARM_ARMED_AWAY, ANCILLARY_CONTROL_EMERGENCY,
ANCILLARY_CONTROL_ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT, ANCILLARY_CONTROL_FIRE,
ANCILLARY_CONTROL_ARMED_STAY: STATE_ALARM_ARMED_HOME, ANCILLARY_CONTROL_INVALID_CODE,
ANCILLARY_CONTROL_DISARMED: STATE_ALARM_DISARMED, ANCILLARY_CONTROL_PANIC,
} }
@ -155,31 +150,23 @@ class DeconzEvent(DeconzBase):
class DeconzAlarmEvent(DeconzEvent): 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 @callback
def async_update_callback(self, force_update=False): 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 ( if (
self.gateway.ignore_state_updates self.gateway.ignore_state_updates
or "action" not in self._device.changed_keys or "action" not in self._device.changed_keys
or self._device.action not in SUPPORTED_DECONZ_ALARM_EVENTS
): ):
return return
try:
state, code, _area = self._device.action.split(",")
except (AttributeError, ValueError):
return
if state not in DECONZ_TO_ALARM_STATE:
return
data = { data = {
CONF_ID: self.event_id, CONF_ID: self.event_id,
CONF_UNIQUE_ID: self.serial, CONF_UNIQUE_ID: self.serial,
CONF_DEVICE_ID: self.device_id, CONF_DEVICE_ID: self.device_id,
CONF_EVENT: DECONZ_TO_ALARM_STATE[state], CONF_EVENT: self._device.action,
CONF_CODE: code,
} }
self.gateway.hass.bus.async_fire(CONF_DECONZ_ALARM_EVENT, data) self.gateway.hass.bus.async_fire(CONF_DECONZ_ALARM_EVENT, data)

View File

@ -4,7 +4,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/deconz", "documentation": "https://www.home-assistant.io/integrations/deconz",
"requirements": [ "requirements": [
"pydeconz==82" "pydeconz==83"
], ],
"ssdp": [ "ssdp": [
{ {

View File

@ -63,25 +63,3 @@ remove_orphaned_entries:
example: "00212EFFFF012345" example: "00212EFFFF012345"
selector: selector:
text: 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"

View File

@ -1400,7 +1400,7 @@ pydaikin==2.4.4
pydanfossair==0.1.0 pydanfossair==0.1.0
# homeassistant.components.deconz # homeassistant.components.deconz
pydeconz==82 pydeconz==83
# homeassistant.components.delijn # homeassistant.components.delijn
pydelijn==0.6.1 pydelijn==0.6.1

View File

@ -797,7 +797,7 @@ pycoolmasternet-async==0.1.2
pydaikin==2.4.4 pydaikin==2.4.4
# homeassistant.components.deconz # homeassistant.components.deconz
pydeconz==82 pydeconz==83
# homeassistant.components.dexcom # homeassistant.components.dexcom
pydexcom==0.2.0 pydexcom==0.2.0

View File

@ -6,24 +6,21 @@ from pydeconz.sensor import (
ANCILLARY_CONTROL_ARMED_AWAY, ANCILLARY_CONTROL_ARMED_AWAY,
ANCILLARY_CONTROL_ARMED_NIGHT, ANCILLARY_CONTROL_ARMED_NIGHT,
ANCILLARY_CONTROL_ARMED_STAY, ANCILLARY_CONTROL_ARMED_STAY,
ANCILLARY_CONTROL_ARMING_AWAY,
ANCILLARY_CONTROL_ARMING_NIGHT,
ANCILLARY_CONTROL_ARMING_STAY,
ANCILLARY_CONTROL_DISARMED, ANCILLARY_CONTROL_DISARMED,
ANCILLARY_CONTROL_ENTRY_DELAY, ANCILLARY_CONTROL_ENTRY_DELAY,
ANCILLARY_CONTROL_EXIT_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 ( from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, 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 ( from homeassistant.const import (
ATTR_CODE,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_HOME,
@ -32,7 +29,10 @@ from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED,
STATE_UNAVAILABLE, 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): async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket):
"""Test successful creation of alarm control panel entities.""" """Test successful creation of alarm control panel entities."""
data = { 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": { "sensors": {
"0": { "0": {
"config": { "config": {
"armed": "disarmed", "battery": 95,
"enrolled": 0, "enrolled": 1,
"on": True, "on": True,
"panel": "disarmed",
"pending": [], "pending": [],
"reachable": True, "reachable": True,
}, },
"ep": 1, "ep": 1,
"etag": "3c4008d74035dfaa1f0bb30d24468b12", "etag": "5aaa1c6bae8501f59929539c6e8f44d6",
"lastseen": "2021-04-02T13:07Z", "lastseen": "2021-07-25T18:07Z",
"manufacturername": "Universal Electronics Inc", "manufacturername": "lk",
"modelid": "URC4450BC0-X-R", "modelid": "ZB-KeypadGeneric-D0002",
"name": "Keypad", "name": "Keypad",
"state": { "state": {
"action": "armed_away,1111,55", "action": "armed_stay",
"lastupdated": "2021-04-02T13:08:18.937", "lastupdated": "2021-07-25T18:02:51.172",
"lowbattery": False, "lowbattery": False,
"tampered": True, "panel": "exit_delay",
"seconds_remaining": 55,
"tampered": False,
}, },
"swversion": "3.13",
"type": "ZHAAncillaryControl", "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): with patch.dict(DECONZ_WEB_REQUEST, data):
config_entry = await setup_deconz_integration(hass, aioclient_mock) config_entry = await setup_deconz_integration(hass, aioclient_mock)
assert len(hass.states.async_all()) == 2 assert len(hass.states.async_all()) == 3
assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_DISARMED assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_PENDING
# Event signals alarm control panel armed away # 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", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "0", "id": "0",
"config": {"armed": ANCILLARY_CONTROL_ARMED_AWAY}, "state": {"panel": ANCILLARY_CONTROL_ARMED_AWAY},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -106,7 +136,7 @@ async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket):
"e": "changed", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "0", "id": "0",
"config": {"armed": ANCILLARY_CONTROL_ARMED_NIGHT}, "state": {"panel": ANCILLARY_CONTROL_ARMED_NIGHT},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -122,29 +152,13 @@ async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket):
"e": "changed", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "0", "id": "0",
"config": {"armed": ANCILLARY_CONTROL_ARMED_STAY}, "state": {"panel": ANCILLARY_CONTROL_ARMED_STAY},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_ARMED_HOME 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 signals alarm control panel disarmed
event_changed_sensor = { event_changed_sensor = {
@ -152,116 +166,139 @@ async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket):
"e": "changed", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "0", "id": "0",
"config": {"armed": ANCILLARY_CONTROL_DISARMED}, "state": {"panel": ANCILLARY_CONTROL_DISARMED},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_DISARMED 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 # Verify service calls
mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/0/config")
# Service set alarm to away mode # Service set alarm to away mode
mock_deconz_put_request(
aioclient_mock, config_entry.data, "/alarmsystems/0/arm_away"
)
await hass.services.async_call( await hass.services.async_call(
ALARM_CONTROL_PANEL_DOMAIN, ALARM_CONTROL_PANEL_DOMAIN,
SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_AWAY,
{ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, {ATTR_ENTITY_ID: "alarm_control_panel.keypad", ATTR_CODE: "1234"},
blocking=True, blocking=True,
) )
assert aioclient_mock.mock_calls[1][2] == { assert aioclient_mock.mock_calls[1][2] == {"code0": "1234"}
"armed": ANCILLARY_CONTROL_ARMED_AWAY,
"panel": ANCILLARY_CONTROL_ARMED_AWAY,
}
# Service set alarm to home mode # Service set alarm to home mode
mock_deconz_put_request(
aioclient_mock, config_entry.data, "/alarmsystems/0/arm_stay"
)
await hass.services.async_call( await hass.services.async_call(
ALARM_CONTROL_PANEL_DOMAIN, ALARM_CONTROL_PANEL_DOMAIN,
SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_HOME,
{ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, {ATTR_ENTITY_ID: "alarm_control_panel.keypad", ATTR_CODE: "2345"},
blocking=True, blocking=True,
) )
assert aioclient_mock.mock_calls[2][2] == { assert aioclient_mock.mock_calls[2][2] == {"code0": "2345"}
"armed": ANCILLARY_CONTROL_ARMED_STAY,
"panel": ANCILLARY_CONTROL_ARMED_STAY,
}
# Service set alarm to night mode # Service set alarm to night mode
mock_deconz_put_request(
aioclient_mock, config_entry.data, "/alarmsystems/0/arm_night"
)
await hass.services.async_call( await hass.services.async_call(
ALARM_CONTROL_PANEL_DOMAIN, ALARM_CONTROL_PANEL_DOMAIN,
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_NIGHT,
{ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, {ATTR_ENTITY_ID: "alarm_control_panel.keypad", ATTR_CODE: "3456"},
blocking=True, blocking=True,
) )
assert aioclient_mock.mock_calls[3][2] == { assert aioclient_mock.mock_calls[3][2] == {"code0": "3456"}
"armed": ANCILLARY_CONTROL_ARMED_NIGHT,
"panel": ANCILLARY_CONTROL_ARMED_NIGHT,
}
# Service set alarm to disarmed # Service set alarm to disarmed
mock_deconz_put_request(aioclient_mock, config_entry.data, "/alarmsystems/0/disarm")
await hass.services.async_call( await hass.services.async_call(
ALARM_CONTROL_PANEL_DOMAIN, ALARM_CONTROL_PANEL_DOMAIN,
SERVICE_ALARM_DISARM, SERVICE_ALARM_DISARM,
{ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, {ATTR_ENTITY_ID: "alarm_control_panel.keypad", ATTR_CODE: "4567"},
blocking=True, blocking=True,
) )
assert aioclient_mock.mock_calls[4][2] == { assert aioclient_mock.mock_calls[4][2] == {"code0": "4567"}
"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
}
await hass.config_entries.async_unload(config_entry.entry_id) await hass.config_entries.async_unload(config_entry.entry_id)
states = hass.states.async_all() states = hass.states.async_all()
assert len(states) == 2 assert len(states) == 3
for state in states: for state in states:
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE

View File

@ -2,13 +2,20 @@
from unittest.mock import patch 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.const import DOMAIN as DECONZ_DOMAIN
from homeassistant.components.deconz.deconz_event import ( from homeassistant.components.deconz.deconz_event import (
CONF_DECONZ_ALARM_EVENT, CONF_DECONZ_ALARM_EVENT,
CONF_DECONZ_EVENT, CONF_DECONZ_EVENT,
) )
from homeassistant.const import ( from homeassistant.const import (
CONF_CODE,
CONF_DEVICE_ID, CONF_DEVICE_ID,
CONF_EVENT, CONF_EVENT,
CONF_ID, 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): async def test_deconz_alarm_events(hass, aioclient_mock, mock_deconz_websocket):
"""Test successful creation of deconz alarm events.""" """Test successful creation of deconz alarm events."""
data = { 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": { "sensors": {
"1": { "1": {
"config": { "config": {
"armed": "disarmed", "battery": 95,
"enrolled": 0, "enrolled": 1,
"on": True, "on": True,
"panel": "disarmed",
"pending": [], "pending": [],
"reachable": True, "reachable": True,
}, },
"ep": 1, "ep": 1,
"etag": "3c4008d74035dfaa1f0bb30d24468b12", "etag": "5aaa1c6bae8501f59929539c6e8f44d6",
"lastseen": "2021-04-02T13:07Z", "lastseen": "2021-07-25T18:07Z",
"manufacturername": "Universal Electronics Inc", "manufacturername": "lk",
"modelid": "URC4450BC0-X-R", "modelid": "ZB-KeypadGeneric-D0002",
"name": "Keypad", "name": "Keypad",
"state": { "state": {
"action": "armed_away,1111,55", "action": "invalid_code",
"lastupdated": "2021-04-02T13:08:18.937", "lastupdated": "2021-07-25T18:02:51.172",
"lowbattery": False, "lowbattery": False,
"tampered": True, "panel": "exit_delay",
"seconds_remaining": 55,
"tampered": False,
}, },
"swversion": "3.13",
"type": "ZHAAncillaryControl", "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): with patch.dict(DECONZ_WEB_REQUEST, data):
config_entry = await setup_deconz_integration(hass, aioclient_mock) config_entry = await setup_deconz_integration(hass, aioclient_mock)
device_registry = await hass.helpers.device_registry.async_get_registry() 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 # 1 alarm control device + 2 additional devices for deconz service and host
assert ( assert (
len(async_entries_for_config_entry(device_registry, config_entry.entry_id)) == 3 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) captured_events = async_capture_events(hass, CONF_DECONZ_ALARM_EVENT)
# Armed away event # Emergency event
event_changed_sensor = { event_changed_sensor = {
"t": "event", "t": "event",
"e": "changed", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "1", "id": "1",
"state": {"action": "armed_away,1234,1"}, "state": {"action": ANCILLARY_CONTROL_EMERGENCY},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() 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_ID: "keypad",
CONF_UNIQUE_ID: "00:00:00:00:00:00:00:01", CONF_UNIQUE_ID: "00:00:00:00:00:00:00:01",
CONF_DEVICE_ID: device.id, CONF_DEVICE_ID: device.id,
CONF_EVENT: "armed_away", CONF_EVENT: ANCILLARY_CONTROL_EMERGENCY,
CONF_CODE: "1234",
} }
# Unsupported events # Fire event
# Bad action string; string is None
event_changed_sensor = { event_changed_sensor = {
"t": "event", "t": "event",
"e": "changed", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "1", "id": "1",
"state": {"action": None}, "state": {"action": ANCILLARY_CONTROL_FIRE},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() 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 = { event_changed_sensor = {
"t": "event", "t": "event",
"e": "changed", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "1", "id": "1",
"state": {"action": ""}, "state": {"action": ANCILLARY_CONTROL_INVALID_CODE},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() 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 = { event_changed_sensor = {
"t": "event", "t": "event",
"e": "changed", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "1", "id": "1",
"state": {"action": "armed_away,1234"}, "state": {"action": ANCILLARY_CONTROL_PANIC},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() 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 = { event_changed_sensor = {
"t": "event", "t": "event",
"e": "changed", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "1", "id": "1",
"state": {"action": "unsupported,1234,1"}, "state": {"action": ANCILLARY_CONTROL_ARMED_AWAY},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() 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 = { event_changed_sensor = {
"t": "event", "t": "event",
"e": "changed", "e": "changed",
"r": "sensors", "r": "sensors",
"id": "1", "id": "1",
"config": {"panel": "armed_away"}, "state": {"panel": ANCILLARY_CONTROL_ARMED_AWAY},
} }
await mock_deconz_websocket(data=event_changed_sensor) await mock_deconz_websocket(data=event_changed_sensor)
await hass.async_block_till_done() 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) await hass.config_entries.async_unload(config_entry.entry_id)
states = hass.states.async_all() states = hass.states.async_all()
assert len(hass.states.async_all()) == 2 assert len(hass.states.async_all()) == 3
for state in states: for state in states:
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE