diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py new file mode 100644 index 00000000000..4592a8014fc --- /dev/null +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -0,0 +1,133 @@ +"""Support for deCONZ alarm control panel devices.""" +from __future__ import annotations + +from pydeconz.sensor import ( + ANCILLARY_CONTROL_ARMED_AWAY, + ANCILLARY_CONTROL_ARMED_NIGHT, + ANCILLARY_CONTROL_ARMED_STAY, + ANCILLARY_CONTROL_DISARMED, + AncillaryControl, +) + +from homeassistant.components.alarm_control_panel import ( + DOMAIN, + SUPPORT_ALARM_ARM_AWAY, + SUPPORT_ALARM_ARM_HOME, + SUPPORT_ALARM_ARM_NIGHT, + AlarmControlPanelEntity, +) +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, + STATE_UNKNOWN, +) +from homeassistant.core import callback +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 + +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, +} + + +async def async_setup_entry(hass, config_entry, async_add_entities) -> None: + """Set up the deCONZ alarm control panel devices. + + Alarm control panels are based on the same device class as sensors in deCONZ. + """ + gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() + + @callback + def async_add_alarm_control_panel(sensors=gateway.api.sensors.values()) -> None: + """Add alarm control panel devices from deCONZ.""" + entities = [] + + for sensor in sensors: + + if ( + sensor.type in AncillaryControl.ZHATYPE + and sensor.uniqueid not in gateway.entities[DOMAIN] + ): + entities.append(DeconzAlarmControlPanel(sensor, gateway)) + + if entities: + async_add_entities(entities) + + gateway.listeners.append( + async_dispatcher_connect( + hass, + gateway.async_signal_new_device(NEW_SENSOR), + async_add_alarm_control_panel, + ) + ) + + async_add_alarm_control_panel() + + +class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): + """Representation of a deCONZ alarm control panel.""" + + TYPE = DOMAIN + + def __init__(self, device, gateway) -> None: + """Set up alarm control panel device.""" + super().__init__(device, gateway) + + self._features = SUPPORT_ALARM_ARM_AWAY + self._features |= SUPPORT_ALARM_ARM_HOME + self._features |= SUPPORT_ALARM_ARM_NIGHT + + @property + def supported_features(self) -> int: + """Return the list of supported features.""" + return self._features + + @property + def code_arm_required(self) -> bool: + """Code is not required for arm actions.""" + return False + + @property + def code_format(self) -> None: + """Code is not supported.""" + return None + + @callback + def async_update_callback(self, force_update: bool = False) -> None: + """Update the control panels state.""" + keys = {"armed", "reachable"} + if force_update or ( + self._device.changed_keys.intersection(keys) + and self._device.state in DECONZ_TO_ALARM_STATE + ): + super().async_update_callback(force_update=force_update) + + @property + def state(self) -> str: + """Return the state of the control panel.""" + return DECONZ_TO_ALARM_STATE.get(self._device.state, STATE_UNKNOWN) + + async def async_alarm_arm_away(self, code: None = None) -> None: + """Send arm away command.""" + await self._device.arm_away() + + async def async_alarm_arm_home(self, code: None = None) -> None: + """Send arm home command.""" + await self._device.arm_stay() + + async def async_alarm_arm_night(self, code: None = None) -> None: + """Send arm night command.""" + await self._device.arm_night() + + async def async_alarm_disarm(self, code: None = None) -> None: + """Send disarm command.""" + await self._device.disarm() diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index 5ed1def66c2..fb4e497587d 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -1,6 +1,9 @@ """Constants for the deCONZ component.""" import logging +from homeassistant.components.alarm_control_panel import ( + DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, +) from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN from homeassistant.components.cover import DOMAIN as COVER_DOMAIN @@ -29,6 +32,7 @@ CONF_ALLOW_NEW_DEVICES = "allow_new_devices" CONF_MASTER_GATEWAY = "master" PLATFORMS = [ + ALARM_CONTROL_PANEL_DOMAIN, BINARY_SENSOR_DOMAIN, CLIMATE_DOMAIN, COVER_DOMAIN, diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index 706850477d8..da80e2e6bf2 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -1,7 +1,26 @@ -"""Representation of a deCONZ remote.""" -from pydeconz.sensor import Switch +"""Representation of a deCONZ remote or keypad.""" -from homeassistant.const import CONF_DEVICE_ID, CONF_EVENT, CONF_ID, CONF_UNIQUE_ID +from pydeconz.sensor import ( + ANCILLARY_CONTROL_ARMED_AWAY, + ANCILLARY_CONTROL_ARMED_NIGHT, + ANCILLARY_CONTROL_ARMED_STAY, + ANCILLARY_CONTROL_DISARMED, + AncillaryControl, + Switch, +) + +from homeassistant.const import ( + CONF_CODE, + CONF_DEVICE_ID, + CONF_EVENT, + CONF_ID, + CONF_UNIQUE_ID, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, + STATE_UNKNOWN, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util import slugify @@ -10,6 +29,14 @@ from .const import CONF_ANGLE, CONF_GESTURE, CONF_XY, LOGGER, NEW_SENSOR 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, +} async def async_setup_events(gateway) -> None: @@ -23,12 +50,18 @@ async def async_setup_events(gateway) -> None: if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): continue - if sensor.type not in Switch.ZHATYPE or sensor.uniqueid in { - event.unique_id for event in gateway.events - }: + if ( + sensor.type not in Switch.ZHATYPE + AncillaryControl.ZHATYPE + or sensor.uniqueid in {event.unique_id for event in gateway.events} + ): continue - new_event = DeconzEvent(sensor, gateway) + if sensor.type in Switch.ZHATYPE: + new_event = DeconzEvent(sensor, gateway) + + elif sensor.type in AncillaryControl.ZHATYPE: + new_event = DeconzAlarmEvent(sensor, gateway) + gateway.hass.async_create_task(new_event.async_update_device_registry()) gateway.events.append(new_event) @@ -119,3 +152,29 @@ class DeconzEvent(DeconzBase): config_entry_id=self.gateway.config_entry.entry_id, **self.device_info ) self.device_id = entry.id + + +class DeconzAlarmEvent(DeconzEvent): + """Alarm control panel companion event when user inputs a code.""" + + @callback + def async_update_callback(self, force_update=False): + """Fire the event if reason is that state is updated.""" + if ( + self.gateway.ignore_state_updates + or "action" not in self._device.changed_keys + or self._device.action == "" + ): + return + + state, code, _area = self._device.action.split(",") + + data = { + CONF_ID: self.event_id, + CONF_UNIQUE_ID: self.serial, + CONF_DEVICE_ID: self.device_id, + CONF_EVENT: DECONZ_TO_ALARM_STATE.get(state, STATE_UNKNOWN), + CONF_CODE: code, + } + + 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 97dbc9a4854..c4dfd0d4dfc 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==78"], + "requirements": ["pydeconz==79"], "ssdp": [ { "manufacturer": "Royal Philips Electronics" diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index a38b7cb20aa..311dd9be82c 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -1,5 +1,6 @@ """Support for deCONZ sensors.""" from pydeconz.sensor import ( + AncillaryControl, Battery, Consumption, Daylight, @@ -104,7 +105,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if ( not sensor.BINARY and sensor.type - not in Battery.ZHATYPE + not in AncillaryControl.ZHATYPE + + Battery.ZHATYPE + DoorLock.ZHATYPE + Switch.ZHATYPE + Thermostat.ZHATYPE diff --git a/requirements_all.txt b/requirements_all.txt index cae71d75295..7ceb300cc23 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1343,7 +1343,7 @@ pydaikin==2.4.1 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==78 +pydeconz==79 # homeassistant.components.delijn pydelijn==0.6.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e2841afab76..64da38880f8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -726,7 +726,7 @@ pycoolmasternet-async==0.1.2 pydaikin==2.4.1 # homeassistant.components.deconz -pydeconz==78 +pydeconz==79 # 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 new file mode 100644 index 00000000000..b0425d5701a --- /dev/null +++ b/tests/components/deconz/test_alarm_control_panel.py @@ -0,0 +1,216 @@ +"""deCONZ alarm control panel platform tests.""" + +from unittest.mock import patch + +from pydeconz.sensor import ( + ANCILLARY_CONTROL_ARMED_AWAY, + ANCILLARY_CONTROL_ARMED_NIGHT, + ANCILLARY_CONTROL_ARMED_STAY, + ANCILLARY_CONTROL_DISARMED, +) + +from homeassistant.components.alarm_control_panel import ( + DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_ALARM_ARM_AWAY, + SERVICE_ALARM_ARM_HOME, + SERVICE_ALARM_ARM_NIGHT, + SERVICE_ALARM_DISARM, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, + STATE_UNAVAILABLE, +) + +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + setup_deconz_integration, +) + + +async def test_no_sensors(hass, aioclient_mock): + """Test that no sensors in deconz results in no climate entities.""" + await setup_deconz_integration(hass, aioclient_mock) + assert len(hass.states.async_all()) == 0 + + +async def test_alarm_control_panel(hass, aioclient_mock, mock_deconz_websocket): + """Test successful creation of alarm control panel entities.""" + data = { + "sensors": { + "0": { + "config": { + "armed": "disarmed", + "enrolled": 0, + "on": True, + "panel": "disarmed", + "pending": [], + "reachable": True, + }, + "ep": 1, + "etag": "3c4008d74035dfaa1f0bb30d24468b12", + "lastseen": "2021-04-02T13:07Z", + "manufacturername": "Universal Electronics Inc", + "modelid": "URC4450BC0-X-R", + "name": "Keypad", + "state": { + "action": "armed_away,1111,55", + "lastupdated": "2021-04-02T13:08:18.937", + "lowbattery": False, + "tampered": True, + }, + "type": "ZHAAncillaryControl", + "uniqueid": "00:0d:6f:00:13:4f:61:39-01-0501", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 1 + assert hass.states.get("alarm_control_panel.keypad").state == STATE_ALARM_DISARMED + + # Event signals alarm control panel armed away + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "0", + "config": {"armed": ANCILLARY_CONTROL_ARMED_AWAY}, + } + 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_AWAY + + # 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 armed home + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "0", + "config": {"armed": 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 = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "0", + "config": {"armed": 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 + + # Verify service calls + + mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/0/config") + + # Service set alarm to away mode + + await hass.services.async_call( + ALARM_CONTROL_PANEL_DOMAIN, + SERVICE_ALARM_ARM_AWAY, + {ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == { + "armed": ANCILLARY_CONTROL_ARMED_AWAY, + "panel": ANCILLARY_CONTROL_ARMED_AWAY, + } + + # Service set alarm to home mode + + await hass.services.async_call( + ALARM_CONTROL_PANEL_DOMAIN, + SERVICE_ALARM_ARM_HOME, + {ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == { + "armed": ANCILLARY_CONTROL_ARMED_STAY, + "panel": ANCILLARY_CONTROL_ARMED_STAY, + } + + # Service set alarm to night mode + + await hass.services.async_call( + ALARM_CONTROL_PANEL_DOMAIN, + SERVICE_ALARM_ARM_NIGHT, + {ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == { + "armed": ANCILLARY_CONTROL_ARMED_NIGHT, + "panel": ANCILLARY_CONTROL_ARMED_NIGHT, + } + + # Service set alarm to disarmed + + await hass.services.async_call( + ALARM_CONTROL_PANEL_DOMAIN, + SERVICE_ALARM_DISARM, + {ATTR_ENTITY_ID: "alarm_control_panel.keypad"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == { + "armed": ANCILLARY_CONTROL_DISARMED, + "panel": ANCILLARY_CONTROL_DISARMED, + } + + await hass.config_entries.async_unload(config_entry.entry_id) + + states = hass.states.async_all() + assert len(states) == 1 + for state in states: + assert state.state == STATE_UNAVAILABLE + + await hass.config_entries.async_remove(config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index fc7544f3918..ed488e05459 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -3,8 +3,18 @@ from unittest.mock import patch from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN -from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT -from homeassistant.const import STATE_UNAVAILABLE +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, + CONF_UNIQUE_ID, + STATE_UNAVAILABLE, +) from homeassistant.helpers.device_registry import async_entries_for_config_entry from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -161,6 +171,20 @@ async def test_deconz_events(hass, aioclient_mock, mock_deconz_websocket): "device_id": device.id, } + # Unsupported event + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "name": "other name", + } + await mock_deconz_websocket(data=event_changed_sensor) + await hass.async_block_till_done() + + assert len(captured_events) == 4 + await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() @@ -173,6 +197,100 @@ async def test_deconz_events(hass, aioclient_mock, mock_deconz_websocket): assert len(hass.states.async_all()) == 0 +async def test_deconz_alarm_events(hass, aioclient_mock, mock_deconz_websocket): + """Test successful creation of deconz alarm events.""" + data = { + "sensors": { + "1": { + "config": { + "armed": "disarmed", + "enrolled": 0, + "on": True, + "panel": "disarmed", + "pending": [], + "reachable": True, + }, + "ep": 1, + "etag": "3c4008d74035dfaa1f0bb30d24468b12", + "lastseen": "2021-04-02T13:07Z", + "manufacturername": "Universal Electronics Inc", + "modelid": "URC4450BC0-X-R", + "name": "Keypad", + "state": { + "action": "armed_away,1111,55", + "lastupdated": "2021-04-02T13:08:18.937", + "lowbattery": False, + "tampered": True, + }, + "type": "ZHAAncillaryControl", + "uniqueid": "00:00:00:00:00:00:00:01-01-0501", + } + } + } + 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()) == 1 + # 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 + ) + + captured_events = async_capture_events(hass, CONF_DECONZ_ALARM_EVENT) + + # Armed away event + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "state": {"action": "armed_away,1234,1"}, + } + await mock_deconz_websocket(data=event_changed_sensor) + await hass.async_block_till_done() + + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:01")} + ) + + assert len(captured_events) == 1 + assert captured_events[0].data == { + 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", + } + + # Unsupported event + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "config": {"panel": "armed_away"}, + } + await mock_deconz_websocket(data=event_changed_sensor) + await hass.async_block_till_done() + + assert len(captured_events) == 1 + + await hass.config_entries.async_unload(config_entry.entry_id) + + states = hass.states.async_all() + assert len(hass.states.async_all()) == 1 + for state in states: + assert state.state == STATE_UNAVAILABLE + + await hass.config_entries.async_remove(config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + + async def test_deconz_events_bad_unique_id(hass, aioclient_mock, mock_deconz_websocket): """Verify no devices are created if unique id is bad or missing.""" data = { diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 603644f47e3..afd4e55499d 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -7,6 +7,9 @@ import pydeconz from pydeconz.websocket import STATE_RETRYING, STATE_RUNNING import pytest +from homeassistant.components.alarm_control_panel import ( + DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, +) from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN from homeassistant.components.cover import DOMAIN as COVER_DOMAIN @@ -147,17 +150,21 @@ async def test_gateway_setup(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 assert forward_entry_setup.mock_calls[0][1] == ( + config_entry, + ALARM_CONTROL_PANEL_DOMAIN, + ) + assert forward_entry_setup.mock_calls[1][1] == ( config_entry, BINARY_SENSOR_DOMAIN, ) - assert forward_entry_setup.mock_calls[1][1] == (config_entry, CLIMATE_DOMAIN) - assert forward_entry_setup.mock_calls[2][1] == (config_entry, COVER_DOMAIN) - assert forward_entry_setup.mock_calls[3][1] == (config_entry, FAN_DOMAIN) - assert forward_entry_setup.mock_calls[4][1] == (config_entry, LIGHT_DOMAIN) - assert forward_entry_setup.mock_calls[5][1] == (config_entry, LOCK_DOMAIN) - assert forward_entry_setup.mock_calls[6][1] == (config_entry, SCENE_DOMAIN) - assert forward_entry_setup.mock_calls[7][1] == (config_entry, SENSOR_DOMAIN) - assert forward_entry_setup.mock_calls[8][1] == (config_entry, SWITCH_DOMAIN) + assert forward_entry_setup.mock_calls[2][1] == (config_entry, CLIMATE_DOMAIN) + assert forward_entry_setup.mock_calls[3][1] == (config_entry, COVER_DOMAIN) + assert forward_entry_setup.mock_calls[4][1] == (config_entry, FAN_DOMAIN) + assert forward_entry_setup.mock_calls[5][1] == (config_entry, LIGHT_DOMAIN) + assert forward_entry_setup.mock_calls[6][1] == (config_entry, LOCK_DOMAIN) + assert forward_entry_setup.mock_calls[7][1] == (config_entry, SCENE_DOMAIN) + assert forward_entry_setup.mock_calls[8][1] == (config_entry, SENSOR_DOMAIN) + assert forward_entry_setup.mock_calls[9][1] == (config_entry, SWITCH_DOMAIN) async def test_gateway_retry(hass):