From 61e2ce5faf2a1015bb94af0a62e4ad127a9065ac Mon Sep 17 00:00:00 2001 From: Kit Klein <33464407+kit-klein@users.noreply.github.com> Date: Tue, 25 Feb 2020 07:55:06 -0500 Subject: [PATCH] Dedup and clarify imported konnected config flows (#32138) * dedup config flows * use default (imported) options until user goes thru options flow * address pr feedback * correct key used to distinguish pro model --- .../konnected/.translations/en.json | 7 +- .../components/konnected/config_flow.py | 55 +- homeassistant/components/konnected/const.py | 1 + homeassistant/components/konnected/panel.py | 5 +- .../components/konnected/strings.json | 6 +- .../components/konnected/test_config_flow.py | 103 +++- tests/components/konnected/test_panel.py | 482 ++++++++++++------ 7 files changed, 478 insertions(+), 181 deletions(-) diff --git a/homeassistant/components/konnected/.translations/en.json b/homeassistant/components/konnected/.translations/en.json index cb6d2d24ff1..9d642a43603 100644 --- a/homeassistant/components/konnected/.translations/en.json +++ b/homeassistant/components/konnected/.translations/en.json @@ -11,9 +11,13 @@ }, "step": { "confirm": { - "description": "Model: {model}\nHost: {host}\nPort: {port}\n\nYou can configure the IO and panel behavior in the Konnected Alarm Panel settings.", + "description": "Model: {model}\nID: {id}\nHost: {host}\nPort: {port}\n\nYou can configure the IO and panel behavior in the Konnected Alarm Panel settings.", "title": "Konnected Device Ready" }, + "import_confirm": { + "description": "A Konnected Alarm Panel with ID {id} has been discovered in configuration.yaml. This flow will allow you to import it into a config entry.", + "title": "Import Konnected Device" + }, "user": { "data": { "host": "Konnected device IP address", @@ -29,6 +33,7 @@ "abort": { "not_konn_panel": "Not a recognized Konnected.io device" }, + "error": {}, "step": { "options_binary": { "data": { diff --git a/homeassistant/components/konnected/config_flow.py b/homeassistant/components/konnected/config_flow.py index b6e0c00c465..cb9004c9efe 100644 --- a/homeassistant/components/konnected/config_flow.py +++ b/homeassistant/components/konnected/config_flow.py @@ -32,6 +32,7 @@ from homeassistant.helpers import config_validation as cv from .const import ( CONF_ACTIVATION, CONF_BLINK, + CONF_DEFAULT_OPTIONS, CONF_DISCOVERY, CONF_INVERSE, CONF_MODEL, @@ -138,7 +139,6 @@ OPTIONS_SCHEMA = vol.Schema( extra=vol.REMOVE_EXTRA, ) -CONF_DEFAULT_OPTIONS = "default_options" CONFIG_ENTRY_SCHEMA = vol.Schema( { vol.Required(CONF_ID): cv.matches_regex("[0-9a-f]{12}"), @@ -158,6 +158,9 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + # class variable to store/share discovered host information + discovered_hosts = {} + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 def __init__(self): @@ -178,7 +181,7 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): except (CannotConnect, KeyError): raise CannotConnect else: - self.data[CONF_MODEL] = status.get("name", KONN_MODEL) + self.data[CONF_MODEL] = status.get("model", KONN_MODEL) self.data[CONF_ACCESS_TOKEN] = "".join( random.choices(f"{string.ascii_uppercase}{string.digits}", k=20) ) @@ -196,6 +199,7 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # config schema ensures we have port if we have host if device_config.get(CONF_HOST): + # automatically connect if we have host info return await self.async_step_user( user_input={ CONF_HOST: device_config[CONF_HOST], @@ -205,6 +209,28 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # if we have no host info wait for it or abort if previously configured self._abort_if_unique_id_configured() + return await self.async_step_import_confirm() + + async def async_step_import_confirm(self, user_input=None): + """Confirm the user wants to import the config entry.""" + if user_input is None: + return self.async_show_form( + step_id="import_confirm", + description_placeholders={"id": self.unique_id}, + ) + + # if we have ssdp discovered applicable host info use it + if KonnectedFlowHandler.discovered_hosts.get(self.unique_id): + return await self.async_step_user( + user_input={ + CONF_HOST: KonnectedFlowHandler.discovered_hosts[self.unique_id][ + CONF_HOST + ], + CONF_PORT: KonnectedFlowHandler.discovered_hosts[self.unique_id][ + CONF_PORT + ], + } + ) return await self.async_step_user() async def async_step_ssdp(self, discovery_info): @@ -265,7 +291,13 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "cannot_connect" else: self.data[CONF_ID] = status["mac"].replace(":", "") - self.data[CONF_MODEL] = status.get("name", KONN_MODEL) + self.data[CONF_MODEL] = status.get("model", KONN_MODEL) + + # save off our discovered host info + KonnectedFlowHandler.discovered_hosts[self.data[CONF_ID]] = { + CONF_HOST: self.data[CONF_HOST], + CONF_PORT: self.data[CONF_PORT], + } return await self.async_step_confirm() return self.async_show_form( @@ -290,23 +322,14 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): the connection. """ if user_input is None: - # update an existing config entry if host info changes - entry = await self.async_set_unique_id( - self.data[CONF_ID], raise_on_progress=False - ) - if entry and ( - entry.data[CONF_HOST] != self.data[CONF_HOST] - or entry.data[CONF_PORT] != self.data[CONF_PORT] - ): - entry_data = copy.deepcopy(entry.data) - entry_data.update(self.data) - self.hass.config_entries.async_update_entry(entry, data=entry_data) - - self._abort_if_unique_id_configured() + # abort and update an existing config entry if host info changes + await self.async_set_unique_id(self.data[CONF_ID]) + self._abort_if_unique_id_configured(updates=self.data) return self.async_show_form( step_id="confirm", description_placeholders={ "model": KONN_PANEL_MODEL_NAMES[self.data[CONF_MODEL]], + "id": self.unique_id, "host": self.data[CONF_HOST], "port": self.data[CONF_PORT], }, diff --git a/homeassistant/components/konnected/const.py b/homeassistant/components/konnected/const.py index d8777a5611e..d6819dcf71f 100644 --- a/homeassistant/components/konnected/const.py +++ b/homeassistant/components/konnected/const.py @@ -4,6 +4,7 @@ DOMAIN = "konnected" CONF_ACTIVATION = "activation" CONF_API_HOST = "api_host" +CONF_DEFAULT_OPTIONS = "default_options" CONF_MOMENTARY = "momentary" CONF_PAUSE = "pause" CONF_POLL_INTERVAL = "poll_interval" diff --git a/homeassistant/components/konnected/panel.py b/homeassistant/components/konnected/panel.py index 9f4b39e82bc..2668a382ccc 100644 --- a/homeassistant/components/konnected/panel.py +++ b/homeassistant/components/konnected/panel.py @@ -28,6 +28,7 @@ from .const import ( CONF_ACTIVATION, CONF_API_HOST, CONF_BLINK, + CONF_DEFAULT_OPTIONS, CONF_DHT_SENSORS, CONF_DISCOVERY, CONF_DS18B20_SENSORS, @@ -64,7 +65,9 @@ class AlarmPanel: self.hass = hass self.config_entry = config_entry self.config = config_entry.data - self.options = config_entry.options + self.options = config_entry.options or config_entry.data.get( + CONF_DEFAULT_OPTIONS, {} + ) self.host = self.config.get(CONF_HOST) self.port = self.config.get(CONF_PORT) self.client = None diff --git a/homeassistant/components/konnected/strings.json b/homeassistant/components/konnected/strings.json index 1f27b04d811..4d923238df4 100644 --- a/homeassistant/components/konnected/strings.json +++ b/homeassistant/components/konnected/strings.json @@ -2,6 +2,10 @@ "config": { "title": "Konnected.io", "step": { + "import_confirm": { + "title": "Import Konnected Device", + "description": "A Konnected Alarm Panel with ID {id} has been discovered in configuration.yaml. This flow will allow you to import it into a config entry." + }, "user": { "title": "Discover Konnected Device", "description": "Please enter the host information for your Konnected Panel.", @@ -12,7 +16,7 @@ }, "confirm": { "title": "Konnected Device Ready", - "description": "Model: {model}\nHost: {host}\nPort: {port}\n\nYou can configure the IO and panel behavior in the Konnected Alarm Panel settings." + "description": "Model: {model}\nID: {id}\nHost: {host}\nPort: {port}\n\nYou can configure the IO and panel behavior in the Konnected Alarm Panel settings." } }, "error": { diff --git a/tests/components/konnected/test_config_flow.py b/tests/components/konnected/test_config_flow.py index 8dfead58659..3638f40735b 100644 --- a/tests/components/konnected/test_config_flow.py +++ b/tests/components/konnected/test_config_flow.py @@ -34,7 +34,7 @@ async def test_flow_works(hass, mock_panel): mock_panel.get_status.return_value = { "mac": "11:22:33:44:55:66", - "name": "Konnected", + "model": "Konnected", } result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"port": 1234, "host": "1.2.3.4"} @@ -43,6 +43,7 @@ async def test_flow_works(hass, mock_panel): assert result["step_id"] == "confirm" assert result["description_placeholders"] == { "model": "Konnected Alarm Panel", + "id": "112233445566", "host": "1.2.3.4", "port": 1234, } @@ -70,7 +71,7 @@ async def test_pro_flow_works(hass, mock_panel): mock_panel.get_status.return_value = { "mac": "11:22:33:44:55:66", - "name": "Konnected Pro", + "model": "Konnected Pro", } result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={"port": 1234, "host": "1.2.3.4"} @@ -79,6 +80,7 @@ async def test_pro_flow_works(hass, mock_panel): assert result["step_id"] == "confirm" assert result["description_placeholders"] == { "model": "Konnected Alarm Panel Pro", + "id": "112233445566", "host": "1.2.3.4", "port": 1234, } @@ -100,7 +102,7 @@ async def test_ssdp(hass, mock_panel): """Test a panel being discovered.""" mock_panel.get_status.return_value = { "mac": "11:22:33:44:55:66", - "name": "Konnected", + "model": "Konnected", } result = await hass.config_entries.flow.async_init( @@ -117,6 +119,7 @@ async def test_ssdp(hass, mock_panel): assert result["step_id"] == "confirm" assert result["description_placeholders"] == { "model": "Konnected Alarm Panel", + "id": "112233445566", "host": "1.2.3.4", "port": 1234, } @@ -125,8 +128,8 @@ async def test_ssdp(hass, mock_panel): async def test_import_no_host_user_finish(hass, mock_panel): """Test importing a panel with no host info.""" mock_panel.get_status.return_value = { - "mac": "11:22:33:44:55:66", - "name": "Konnected Pro", + "mac": "aa:bb:cc:dd:ee:ff", + "model": "Konnected Pro", } result = await hass.config_entries.flow.async_init( @@ -159,6 +162,13 @@ async def test_import_no_host_user_finish(hass, mock_panel): }, ) assert result["type"] == "form" + assert result["step_id"] == "import_confirm" + assert result["description_placeholders"]["id"] == "aabbccddeeff" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == "form" assert result["step_id"] == "user" # confirm user is prompted to enter host @@ -169,6 +179,7 @@ async def test_import_no_host_user_finish(hass, mock_panel): assert result["step_id"] == "confirm" assert result["description_placeholders"] == { "model": "Konnected Alarm Panel Pro", + "id": "aabbccddeeff", "host": "1.1.1.1", "port": 1234, } @@ -180,6 +191,78 @@ async def test_import_no_host_user_finish(hass, mock_panel): assert result["type"] == "create_entry" +async def test_import_ssdp_host_user_finish(hass, mock_panel): + """Test importing a panel with no host info which ssdp discovers.""" + mock_panel.get_status.return_value = { + "mac": "11:22:33:44:55:66", + "model": "Konnected Pro", + } + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": "import"}, + data={ + "default_options": { + "blink": True, + "discovery": True, + "io": { + "1": "Disabled", + "10": "Disabled", + "11": "Disabled", + "12": "Disabled", + "2": "Disabled", + "3": "Disabled", + "4": "Disabled", + "5": "Disabled", + "6": "Disabled", + "7": "Disabled", + "8": "Disabled", + "9": "Disabled", + "alarm1": "Disabled", + "alarm2_out2": "Disabled", + "out": "Disabled", + "out1": "Disabled", + }, + }, + "id": "112233445566", + }, + ) + assert result["type"] == "form" + assert result["step_id"] == "import_confirm" + assert result["description_placeholders"]["id"] == "112233445566" + + # discover the panel via ssdp + ssdp_result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": "ssdp"}, + data={ + "ssdp_location": "http://0.0.0.0:1234/Device.xml", + "manufacturer": config_flow.KONN_MANUFACTURER, + "modelName": config_flow.KONN_MODEL_PRO, + }, + ) + assert ssdp_result["type"] == "abort" + assert ssdp_result["reason"] == "already_in_progress" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == "form" + assert result["step_id"] == "confirm" + assert result["description_placeholders"] == { + "model": "Konnected Alarm Panel Pro", + "id": "112233445566", + "host": "0.0.0.0", + "port": 1234, + } + + # final confirmation + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == "create_entry" + + async def test_ssdp_already_configured(hass, mock_panel): """Test if a discovered panel has already been configured.""" MockConfigEntry( @@ -189,7 +272,7 @@ async def test_ssdp_already_configured(hass, mock_panel): ).add_to_hass(hass) mock_panel.get_status.return_value = { "mac": "11:22:33:44:55:66", - "name": "Konnected Pro", + "model": "Konnected Pro", } result = await hass.config_entries.flow.async_init( @@ -265,7 +348,7 @@ async def test_ssdp_host_update(hass, mock_panel): ).add_to_hass(hass) mock_panel.get_status.return_value = { "mac": "11:22:33:44:55:66", - "name": "Konnected Pro", + "model": "Konnected Pro", } result = await hass.config_entries.flow.async_init( @@ -289,7 +372,7 @@ async def test_import_existing_config(hass, mock_panel): """Test importing a host with an existing config file.""" mock_panel.get_status.return_value = { "mac": "11:22:33:44:55:66", - "name": "Konnected Pro", + "model": "Konnected Pro", } result = await hass.config_entries.flow.async_init( @@ -402,7 +485,7 @@ async def test_import_existing_config_entry(hass, mock_panel): mock_panel.get_status.return_value = { "mac": "11:22:33:44:55:66", - "name": "Konnected Pro", + "model": "Konnected Pro", } # utilize a global access token this time @@ -462,7 +545,7 @@ async def test_import_pin_config(hass, mock_panel): """Test importing a host with an existing config file that specifies pin configs.""" mock_panel.get_status.return_value = { "mac": "11:22:33:44:55:66", - "name": "Konnected Pro", + "model": "Konnected Pro", } result = await hass.config_entries.flow.async_init( diff --git a/tests/components/konnected/test_panel.py b/tests/components/konnected/test_panel.py index 0ad384bd35e..f1ae8a4357c 100644 --- a/tests/components/konnected/test_panel.py +++ b/tests/components/konnected/test_panel.py @@ -3,6 +3,7 @@ from asynctest import patch import pytest from homeassistant.components.konnected import config_flow, panel +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -92,9 +93,6 @@ async def test_create_and_setup(hass, mock_panel): options=device_options, ) entry.add_to_hass(hass) - hass.data[panel.DOMAIN] = { - panel.CONF_API_HOST: "192.168.1.1", - } # override get_status to reflect non-pro board mock_panel.get_status.return_value = { @@ -111,19 +109,35 @@ async def test_create_and_setup(hass, mock_panel): "mac": "11:22:33:44:55:66", "settings": {}, } - device = panel.AlarmPanel(hass, entry) - await device.async_save_data() - await device.async_connect() + + # setup the integration and inspect panel behavior + assert ( + await async_setup_component( + hass, + panel.DOMAIN, + { + panel.DOMAIN: { + panel.CONF_ACCESS_TOKEN: "arandomstringvalue", + panel.CONF_API_HOST: "http://192.168.1.1:8123", + } + }, + ) + is True + ) + + # confirm panel instance was created and configured + # hass.data is the only mechanism to get a reference to the created panel instance + device = hass.data[panel.DOMAIN][panel.CONF_DEVICES]["112233445566"]["panel"] await device.update_switch("1", 0) # confirm the correct api is used # pylint: disable=no-member - assert device.client.put_device.call_count == 1 - assert device.client.put_zone.call_count == 0 + assert mock_panel.put_device.call_count == 1 + assert mock_panel.put_zone.call_count == 0 # confirm the settings are sent to the panel # pylint: disable=no-member - assert device.client.put_settings.call_args_list[0][1] == { + assert mock_panel.put_settings.call_args_list[0][1] == { "sensors": [{"pin": "1"}, {"pin": "2"}, {"pin": "5"}], "actuators": [{"trigger": 0, "pin": "8"}, {"trigger": 1, "pin": "9"}], "dht_sensors": [{"poll_interval": 3, "pin": "6"}], @@ -131,67 +145,60 @@ async def test_create_and_setup(hass, mock_panel): "auth_token": "11223344556677889900", "blink": True, "discovery": True, - "endpoint": "192.168.1.1/api/konnected", + "endpoint": "http://192.168.1.1:8123/api/konnected", } # confirm the device settings are saved in hass.data - assert hass.data[panel.DOMAIN][panel.CONF_DEVICES] == { - "112233445566": { - "binary_sensors": { - "1": { - "inverse": False, - "name": "Konnected 445566 Zone 1", - "state": None, - "type": "door", - }, - "2": { - "inverse": True, - "name": "winder", - "state": None, - "type": "window", - }, - "3": { - "inverse": False, - "name": "Konnected 445566 Zone 3", - "state": None, - "type": "door", - }, + assert device.stored_configuration == { + "binary_sensors": { + "1": { + "inverse": False, + "name": "Konnected 445566 Zone 1", + "state": None, + "type": "door", }, - "blink": True, - "panel": device, - "discovery": True, - "host": "1.2.3.4", - "port": 1234, - "sensors": [ - { - "name": "Konnected 445566 Sensor 4", - "poll_interval": 3, - "type": "dht", - "zone": "4", - }, - {"name": "temper", "poll_interval": 3, "type": "ds18b20", "zone": "5"}, - ], - "switches": [ - { - "activation": "low", - "momentary": 50, - "name": "switcher", - "pause": 100, - "repeat": 4, - "state": None, - "zone": "out", - }, - { - "activation": "high", - "momentary": None, - "name": "Konnected 445566 Actuator 6", - "pause": None, - "repeat": None, - "state": None, - "zone": "6", - }, - ], - } + "2": {"inverse": True, "name": "winder", "state": None, "type": "window"}, + "3": { + "inverse": False, + "name": "Konnected 445566 Zone 3", + "state": None, + "type": "door", + }, + }, + "blink": True, + "panel": device, + "discovery": True, + "host": "1.2.3.4", + "port": 1234, + "sensors": [ + { + "name": "Konnected 445566 Sensor 4", + "poll_interval": 3, + "type": "dht", + "zone": "4", + }, + {"name": "temper", "poll_interval": 3, "type": "ds18b20", "zone": "5"}, + ], + "switches": [ + { + "activation": "low", + "momentary": 50, + "name": "switcher", + "pause": 100, + "repeat": 4, + "state": None, + "zone": "out", + }, + { + "activation": "high", + "momentary": None, + "name": "Konnected 445566 Actuator 6", + "pause": None, + "repeat": None, + "state": None, + "zone": "6", + }, + ], } @@ -255,23 +262,35 @@ async def test_create_and_setup_pro(hass, mock_panel): options=device_options, ) entry.add_to_hass(hass) - hass.data[panel.DOMAIN] = { - panel.CONF_API_HOST: "192.168.1.1", - } - device = panel.AlarmPanel(hass, entry) - await device.async_save_data() - await device.async_connect() + # setup the integration and inspect panel behavior + assert ( + await async_setup_component( + hass, + panel.DOMAIN, + { + panel.DOMAIN: { + panel.CONF_ACCESS_TOKEN: "arandomstringvalue", + panel.CONF_API_HOST: "http://192.168.1.1:8123", + } + }, + ) + is True + ) + + # confirm panel instance was created and configured + # hass.data is the only mechanism to get a reference to the created panel instance + device = hass.data[panel.DOMAIN][panel.CONF_DEVICES]["112233445566"]["panel"] await device.update_switch("2", 1) # confirm the correct api is used # pylint: disable=no-member - assert device.client.put_device.call_count == 0 - assert device.client.put_zone.call_count == 1 + assert mock_panel.put_device.call_count == 0 + assert mock_panel.put_zone.call_count == 1 # confirm the settings are sent to the panel # pylint: disable=no-member - assert device.client.put_settings.call_args_list[0][1] == { + assert mock_panel.put_settings.call_args_list[0][1] == { "sensors": [{"zone": "2"}, {"zone": "6"}, {"zone": "10"}], "actuators": [ {"trigger": 1, "zone": "4"}, @@ -287,89 +306,248 @@ async def test_create_and_setup_pro(hass, mock_panel): "auth_token": "11223344556677889900", "blink": True, "discovery": True, - "endpoint": "192.168.1.1/api/konnected", + "endpoint": "http://192.168.1.1:8123/api/konnected", } # confirm the device settings are saved in hass.data - assert hass.data[panel.DOMAIN][panel.CONF_DEVICES] == { - "112233445566": { - "binary_sensors": { - "10": { - "inverse": False, - "name": "Konnected 445566 Zone 10", - "state": None, - "type": "door", - }, - "2": { - "inverse": False, - "name": "Konnected 445566 Zone 2", - "state": None, - "type": "door", - }, - "6": { - "inverse": True, - "name": "winder", - "state": None, - "type": "window", - }, + assert device.stored_configuration == { + "binary_sensors": { + "10": { + "inverse": False, + "name": "Konnected 445566 Zone 10", + "state": None, + "type": "door", }, - "blink": True, - "panel": device, - "discovery": True, + "2": { + "inverse": False, + "name": "Konnected 445566 Zone 2", + "state": None, + "type": "door", + }, + "6": {"inverse": True, "name": "winder", "state": None, "type": "window"}, + }, + "blink": True, + "panel": device, + "discovery": True, + "host": "1.2.3.4", + "port": 1234, + "sensors": [ + { + "name": "Konnected 445566 Sensor 3", + "poll_interval": 3, + "type": "dht", + "zone": "3", + }, + {"name": "temper", "poll_interval": 3, "type": "ds18b20", "zone": "7"}, + { + "name": "Konnected 445566 Sensor 11", + "poll_interval": 5, + "type": "dht", + "zone": "11", + }, + ], + "switches": [ + { + "activation": "high", + "momentary": None, + "name": "Konnected 445566 Actuator 4", + "pause": None, + "repeat": None, + "state": None, + "zone": "4", + }, + { + "activation": "low", + "momentary": 50, + "name": "switcher", + "pause": 100, + "repeat": 4, + "state": None, + "zone": "8", + }, + { + "activation": "high", + "momentary": None, + "name": "Konnected 445566 Actuator out1", + "pause": None, + "repeat": None, + "state": None, + "zone": "out1", + }, + { + "activation": "high", + "momentary": None, + "name": "Konnected 445566 Actuator alarm1", + "pause": None, + "repeat": None, + "state": None, + "zone": "alarm1", + }, + ], + } + + +async def test_default_options(hass, mock_panel): + """Test that we create a Konnected Panel and save the data.""" + device_config = config_flow.CONFIG_ENTRY_SCHEMA( + { "host": "1.2.3.4", "port": 1234, - "sensors": [ + "id": "112233445566", + "model": "Konnected Pro", + "access_token": "11223344556677889900", + "default_options": config_flow.OPTIONS_SCHEMA( { - "name": "Konnected 445566 Sensor 3", - "poll_interval": 3, - "type": "dht", - "zone": "3", - }, - {"name": "temper", "poll_interval": 3, "type": "ds18b20", "zone": "7"}, - { - "name": "Konnected 445566 Sensor 11", - "poll_interval": 5, - "type": "dht", - "zone": "11", - }, - ], - "switches": [ - { - "activation": "high", - "momentary": None, - "name": "Konnected 445566 Actuator 4", - "pause": None, - "repeat": None, - "state": None, - "zone": "4", - }, - { - "activation": "low", - "momentary": 50, - "name": "switcher", - "pause": 100, - "repeat": 4, - "state": None, - "zone": "8", - }, - { - "activation": "high", - "momentary": None, - "name": "Konnected 445566 Actuator out1", - "pause": None, - "repeat": None, - "state": None, - "zone": "out1", - }, - { - "activation": "high", - "momentary": None, - "name": "Konnected 445566 Actuator alarm1", - "pause": None, - "repeat": None, - "state": None, - "zone": "alarm1", - }, - ], + "io": { + "1": "Binary Sensor", + "2": "Binary Sensor", + "3": "Binary Sensor", + "4": "Digital Sensor", + "5": "Digital Sensor", + "6": "Switchable Output", + "out": "Switchable Output", + }, + "binary_sensors": [ + {"zone": "1", "type": "door"}, + { + "zone": "2", + "type": "window", + "name": "winder", + "inverse": True, + }, + {"zone": "3", "type": "door"}, + ], + "sensors": [ + {"zone": "4", "type": "dht"}, + {"zone": "5", "type": "ds18b20", "name": "temper"}, + ], + "switches": [ + { + "zone": "out", + "name": "switcher", + "activation": "low", + "momentary": 50, + "pause": 100, + "repeat": 4, + }, + {"zone": "6"}, + ], + } + ), } + ) + + entry = MockConfigEntry( + domain="konnected", + title="Konnected Alarm Panel", + data=device_config, + options={}, + ) + entry.add_to_hass(hass) + + # override get_status to reflect non-pro board + mock_panel.get_status.return_value = { + "hwVersion": "2.3.0", + "swVersion": "2.3.1", + "heap": 10000, + "uptime": 12222, + "ip": "192.168.1.90", + "port": 9123, + "sensors": [], + "actuators": [], + "dht_sensors": [], + "ds18b20_sensors": [], + "mac": "11:22:33:44:55:66", + "settings": {}, + } + + # setup the integration and inspect panel behavior + assert ( + await async_setup_component( + hass, + panel.DOMAIN, + { + panel.DOMAIN: { + panel.CONF_ACCESS_TOKEN: "arandomstringvalue", + panel.CONF_API_HOST: "http://192.168.1.1:8123", + } + }, + ) + is True + ) + + # confirm panel instance was created and configured. + # hass.data is the only mechanism to get a reference to the created panel instance + device = hass.data[panel.DOMAIN][panel.CONF_DEVICES]["112233445566"]["panel"] + await device.update_switch("1", 0) + + # confirm the correct api is used + # pylint: disable=no-member + assert mock_panel.put_device.call_count == 1 + assert mock_panel.put_zone.call_count == 0 + + # confirm the settings are sent to the panel + # pylint: disable=no-member + assert mock_panel.put_settings.call_args_list[0][1] == { + "sensors": [{"pin": "1"}, {"pin": "2"}, {"pin": "5"}], + "actuators": [{"trigger": 0, "pin": "8"}, {"trigger": 1, "pin": "9"}], + "dht_sensors": [{"poll_interval": 3, "pin": "6"}], + "ds18b20_sensors": [{"pin": "7"}], + "auth_token": "11223344556677889900", + "blink": True, + "discovery": True, + "endpoint": "http://192.168.1.1:8123/api/konnected", + } + + # confirm the device settings are saved in hass.data + assert device.stored_configuration == { + "binary_sensors": { + "1": { + "inverse": False, + "name": "Konnected 445566 Zone 1", + "state": None, + "type": "door", + }, + "2": {"inverse": True, "name": "winder", "state": None, "type": "window"}, + "3": { + "inverse": False, + "name": "Konnected 445566 Zone 3", + "state": None, + "type": "door", + }, + }, + "blink": True, + "panel": device, + "discovery": True, + "host": "1.2.3.4", + "port": 1234, + "sensors": [ + { + "name": "Konnected 445566 Sensor 4", + "poll_interval": 3, + "type": "dht", + "zone": "4", + }, + {"name": "temper", "poll_interval": 3, "type": "ds18b20", "zone": "5"}, + ], + "switches": [ + { + "activation": "low", + "momentary": 50, + "name": "switcher", + "pause": 100, + "repeat": 4, + "state": None, + "zone": "out", + }, + { + "activation": "high", + "momentary": None, + "name": "Konnected 445566 Actuator 6", + "pause": None, + "repeat": None, + "state": None, + "zone": "6", + }, + ], }