From c1dc20f81a9d1c2cac3981daaaf35db4eba5eba9 Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sat, 17 Apr 2021 17:15:28 +0200 Subject: [PATCH 01/14] First draft implementing config flow for velux --- .coveragerc | 5 +- homeassistant/components/velux/__init__.py | 43 ++++++++------- homeassistant/components/velux/config_flow.py | 52 +++++++++++++++++++ homeassistant/components/velux/const.py | 3 ++ homeassistant/components/velux/cover.py | 12 ++++- homeassistant/components/velux/manifest.json | 3 +- homeassistant/components/velux/scene.py | 11 +++- homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 3 ++ tests/components/velux/__init__.py | 1 + tests/components/velux/test_config_flow.py | 39 ++++++++++++++ 11 files changed, 149 insertions(+), 24 deletions(-) create mode 100644 homeassistant/components/velux/config_flow.py create mode 100644 homeassistant/components/velux/const.py create mode 100644 tests/components/velux/__init__.py create mode 100644 tests/components/velux/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 40daa9ce2307c5..aa7fd218375f29 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1085,7 +1085,10 @@ omit = homeassistant/components/velbus/light.py homeassistant/components/velbus/sensor.py homeassistant/components/velbus/switch.py - homeassistant/components/velux/* + homeassistant/components/velux/__init__.py + homeassistant/components/velux/const.py + homeassistant/components/velux/cover.py + homeassistant/components/velux/scene.py homeassistant/components/venstar/climate.py homeassistant/components/verisure/__init__.py homeassistant/components/verisure/alarm_control_panel.py diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py index 5c1d8bfd37060a..25d9d445d9aec7 100644 --- a/homeassistant/components/velux/__init__.py +++ b/homeassistant/components/velux/__init__.py @@ -2,31 +2,38 @@ import logging from pyvlx import PyVLX, PyVLXException -import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP -from homeassistant.helpers import discovery -import homeassistant.helpers.config_validation as cv +from homeassistant.core import HomeAssistant DOMAIN = "velux" DATA_VELUX = "data_velux" PLATFORMS = ["cover", "scene"] _LOGGER = logging.getLogger(__name__) -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - {vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PASSWORD): cv.string} - ) - }, - extra=vol.ALLOW_EXTRA, -) + +async def async_setup(hass: HomeAssistant, config: dict): + """Component setup, run import config flow for each entry in config.""" + conf = config.get(DOMAIN) + if conf is None: + return True + + if DOMAIN in config: + for entry in config[DOMAIN]: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=entry + ) + ) + + return True -async def async_setup(hass, config): - """Set up the velux component.""" +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): + """Set up Velux using config flow.""" try: - hass.data[DATA_VELUX] = VeluxModule(hass, config[DOMAIN]) + hass.data[DATA_VELUX] = VeluxModule(hass, config_entry) hass.data[DATA_VELUX].setup() await hass.data[DATA_VELUX].async_start() @@ -36,7 +43,7 @@ async def async_setup(hass, config): for platform in PLATFORMS: hass.async_create_task( - discovery.async_load_platform(hass, platform, DOMAIN, {}, config) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -44,7 +51,7 @@ async def async_setup(hass, config): class VeluxModule: """Abstraction for velux component.""" - def __init__(self, hass, domain_config): + def __init__(self, hass: HomeAssistant, domain_config): """Initialize for velux component.""" self.pyvlx = None self._hass = hass @@ -62,8 +69,8 @@ async def async_reboot_gateway(service_call): await self.pyvlx.reboot_gateway() self._hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) - host = self._domain_config.get(CONF_HOST) - password = self._domain_config.get(CONF_PASSWORD) + host = self._domain_config.data[CONF_HOST] + password = self._domain_config.data[CONF_PASSWORD] self.pyvlx = PyVLX(host=host, password=password) self._hass.services.async_register( diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py new file mode 100644 index 00000000000000..8e804a224cbfab --- /dev/null +++ b/homeassistant/components/velux/config_flow.py @@ -0,0 +1,52 @@ +"""Config flow for velux integration.""" +import logging + +from pyvlx import PyVLX, PyVLXException +import voluptuous as vol + +from homeassistant import config_entries, exceptions +from homeassistant.const import CONF_HOST, CONF_PASSWORD + +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema( + {vol.Required(CONF_HOST): str, vol.Required(CONF_PASSWORD): str} +) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for youless.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + if user_input is not None: + try: + host = user_input[CONF_HOST] + password = user_input[CONF_PASSWORD] + + pyvlx = PyVLX(host=host, password=password) + await pyvlx.connect() + + await pyvlx.disconnect() + + return self.async_create_entry( + title=host, + data=user_input, + ) + except PyVLXException: + _LOGGER.exception("Cannot connect to KLF 200 gateway") + errors["base"] = "invalid_auth" + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/velux/const.py b/homeassistant/components/velux/const.py new file mode 100644 index 00000000000000..b73fa4f97113e9 --- /dev/null +++ b/homeassistant/components/velux/const.py @@ -0,0 +1,3 @@ +"""Const for Velux.""" + +DOMAIN = "velux" diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 187c0d3617860d..6c222660c91de2 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -1,4 +1,6 @@ """Support for Velux covers.""" +from typing import Callable + from pyvlx import OpeningDevice, Position from pyvlx.opening_device import Awning, Blind, GarageDoor, Gate, RollerShutter, Window @@ -21,12 +23,18 @@ SUPPORT_STOP_TILT, CoverEntity, ) -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import Entity from . import DATA_VELUX -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list[Entity], bool], None], +) -> None: """Set up cover(s) for Velux platform.""" entities = [] for node in hass.data[DATA_VELUX].pyvlx.nodes: diff --git a/homeassistant/components/velux/manifest.json b/homeassistant/components/velux/manifest.json index 43be9b424a8464..85c14ede098abe 100644 --- a/homeassistant/components/velux/manifest.json +++ b/homeassistant/components/velux/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/velux", "requirements": ["pyvlx==0.2.18"], "codeowners": ["@Julius2342"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "config_flow": true } diff --git a/homeassistant/components/velux/scene.py b/homeassistant/components/velux/scene.py index 96ff0558fff791..ed79ea204b37e3 100644 --- a/homeassistant/components/velux/scene.py +++ b/homeassistant/components/velux/scene.py @@ -1,12 +1,19 @@ """Support for VELUX scenes.""" -from typing import Any +from typing import Any, Callable from homeassistant.components.scene import Scene +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import _LOGGER, DATA_VELUX -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list[Entity], bool], None], +) -> None: """Set up the scenes for Velux platform.""" entities = [VeluxScene(scene) for scene in hass.data[DATA_VELUX].pyvlx.scenes] async_add_entities(entities) diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 151b95a8f20309..68a6d1c0a93f90 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -256,6 +256,7 @@ "upcloud", "upnp", "velbus", + "velux", "vera", "verisure", "vesync", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0419b9b3c5f97c..ddddd39f592db0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1018,6 +1018,9 @@ pyvesync==1.3.1 # homeassistant.components.vizio pyvizio==0.1.57 +# homeassistant.components.velux +pyvlx==0.2.18 + # homeassistant.components.volumio pyvolumio==0.1.3 diff --git a/tests/components/velux/__init__.py b/tests/components/velux/__init__.py new file mode 100644 index 00000000000000..00477bf4e67198 --- /dev/null +++ b/tests/components/velux/__init__.py @@ -0,0 +1 @@ +"""Tests for the Velux Component.""" diff --git a/tests/components/velux/test_config_flow.py b/tests/components/velux/test_config_flow.py new file mode 100644 index 00000000000000..615fb1990b886f --- /dev/null +++ b/tests/components/velux/test_config_flow.py @@ -0,0 +1,39 @@ +"""Test the Velux config flow.""" +from unittest.mock import patch + +from pyvlx import PyVLXException + +from homeassistant import config_entries, setup +from homeassistant.components.velux import DOMAIN +from homeassistant.const import CONF_HOST, CONF_PASSWORD + +CONFIG = {DOMAIN: {CONF_HOST: "192.168.0.20", CONF_PASSWORD: "password"}} + + +async def test_form(hass): + """Test we get the form.""" + return + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + +async def test_gateway_connect_exception(hass): + """Test a error message is displayed when connection to KLF gateway fails.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "pyvlx.PyVLX.connect", + side_effect=PyVLXException("Login to KLF 200 failed, check credentials"), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG[DOMAIN] + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} From 45fc39cca165af314f30e7fb8b08e1428e83e931 Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sat, 17 Apr 2021 18:18:02 +0200 Subject: [PATCH 02/14] Rename config flow, add string and translations --- homeassistant/components/velux/config_flow.py | 2 +- homeassistant/components/velux/strings.json | 16 ++++++++++++++++ .../components/velux/translations/en.json | 16 ++++++++++++++++ .../components/velux/translations/nl.json | 16 ++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/velux/strings.json create mode 100644 homeassistant/components/velux/translations/en.json create mode 100644 homeassistant/components/velux/translations/nl.json diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index 8e804a224cbfab..7cd26060094fa4 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -16,7 +16,7 @@ ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class VeluxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for youless.""" VERSION = 1 diff --git a/homeassistant/components/velux/strings.json b/homeassistant/components/velux/strings.json new file mode 100644 index 00000000000000..590e85a819ca05 --- /dev/null +++ b/homeassistant/components/velux/strings.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "user": { + "title": "Setup your Velux KLF 200", + "data": { + "host": "[%key:common::config_flow::data::host%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velux/translations/en.json b/homeassistant/components/velux/translations/en.json new file mode 100644 index 00000000000000..cb2799206fdea1 --- /dev/null +++ b/homeassistant/components/velux/translations/en.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_auth": "Authenticatie ongeldig" + }, + "step": { + "user": { + "data": { + "host": "Host/IP", + "password": "Wachtwoord" + }, + "title": "Instellen Velux KLF 200" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velux/translations/nl.json b/homeassistant/components/velux/translations/nl.json new file mode 100644 index 00000000000000..4a906f39fc1699 --- /dev/null +++ b/homeassistant/components/velux/translations/nl.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_auth": "Invalid authentication" + }, + "step": { + "user": { + "data": { + "host": "Host/IP", + "password": "Password" + }, + "title": "Setup your Velux KLF 200" + } + } + } +} \ No newline at end of file From 0cf4ece3af7c97ed3b57207175f36874b196cbca Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sat, 17 Apr 2021 18:54:45 +0200 Subject: [PATCH 03/14] Import configuration from yaml --- homeassistant/components/velux/__init__.py | 15 ++++++--------- homeassistant/components/velux/config_flow.py | 4 ++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py index 25d9d445d9aec7..9e0bba20588cb6 100644 --- a/homeassistant/components/velux/__init__.py +++ b/homeassistant/components/velux/__init__.py @@ -15,17 +15,14 @@ async def async_setup(hass: HomeAssistant, config: dict): """Component setup, run import config flow for each entry in config.""" - conf = config.get(DOMAIN) - if conf is None: + if DOMAIN not in config: return True - if DOMAIN in config: - for entry in config[DOMAIN]: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=entry - ) - ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN] + ) + ) return True diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index 7cd26060094fa4..f90300847e836a 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -47,6 +47,10 @@ async def async_step_user(self, user_input=None): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) + async def async_step_import(self, import_config): + """Import config from configuration.yaml.""" + return await self.async_step_user(import_config) + class CannotConnect(exceptions.HomeAssistantError): """Error to indicate we cannot connect.""" From b0b09dc08c5b00106b87f871c568e0d3678da501 Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sat, 17 Apr 2021 19:32:25 +0200 Subject: [PATCH 04/14] Fix linting --- homeassistant/components/velux/cover.py | 5 +---- homeassistant/components/velux/scene.py | 5 ++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 6c222660c91de2..2941230c1c8129 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -1,6 +1,4 @@ """Support for Velux covers.""" -from typing import Callable - from pyvlx import OpeningDevice, Position from pyvlx.opening_device import Awning, Blind, GarageDoor, Gate, RollerShutter, Window @@ -25,7 +23,6 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.entity import Entity from . import DATA_VELUX @@ -33,7 +30,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[list[Entity], bool], None], + async_add_entities, ) -> None: """Set up cover(s) for Velux platform.""" entities = [] diff --git a/homeassistant/components/velux/scene.py b/homeassistant/components/velux/scene.py index ed79ea204b37e3..ef11a99f4afb88 100644 --- a/homeassistant/components/velux/scene.py +++ b/homeassistant/components/velux/scene.py @@ -1,10 +1,9 @@ """Support for VELUX scenes.""" -from typing import Any, Callable +from typing import Any from homeassistant.components.scene import Scene from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from . import _LOGGER, DATA_VELUX @@ -12,7 +11,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[list[Entity], bool], None], + async_add_entities, ) -> None: """Set up the scenes for Velux platform.""" entities = [VeluxScene(scene) for scene in hass.data[DATA_VELUX].pyvlx.scenes] From cd98275aa5479e16d47f0bfaf51866f26abe14cc Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sat, 17 Apr 2021 19:52:42 +0200 Subject: [PATCH 05/14] Fix config is imported only once for the same host --- homeassistant/components/velux/config_flow.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index f90300847e836a..f942ff7a14b969 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -49,6 +49,11 @@ async def async_step_user(self, user_input=None): async def async_step_import(self, import_config): """Import config from configuration.yaml.""" + entries = self._async_current_entries() + for entry in entries: + if entry.data[CONF_HOST] == import_config[CONF_HOST]: + return self.async_abort(reason="already_configured") + return await self.async_step_user(import_config) From eb3a78e33f3617b812be3eea825df789da41a051 Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sun, 16 May 2021 19:11:19 +0200 Subject: [PATCH 06/14] Update homeassistant/components/velux/config_flow.py Co-authored-by: J. Nick Koston --- homeassistant/components/velux/config_flow.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index f942ff7a14b969..a71c010a5826b1 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -50,9 +50,7 @@ async def async_step_user(self, user_input=None): async def async_step_import(self, import_config): """Import config from configuration.yaml.""" entries = self._async_current_entries() - for entry in entries: - if entry.data[CONF_HOST] == import_config[CONF_HOST]: - return self.async_abort(reason="already_configured") + self._async_abort_entries_match({CONF_HOST: import_config[CONF_HOST]}) return await self.async_step_user(import_config) From 17c7e421de092fbc565645105b785ef404bb3593 Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sun, 16 May 2021 19:16:19 +0200 Subject: [PATCH 07/14] Remove translation files from git --- .../components/velux/translations/en.json | 16 ---------------- .../components/velux/translations/nl.json | 16 ---------------- 2 files changed, 32 deletions(-) delete mode 100644 homeassistant/components/velux/translations/en.json delete mode 100644 homeassistant/components/velux/translations/nl.json diff --git a/homeassistant/components/velux/translations/en.json b/homeassistant/components/velux/translations/en.json deleted file mode 100644 index cb2799206fdea1..00000000000000 --- a/homeassistant/components/velux/translations/en.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "config": { - "error": { - "invalid_auth": "Authenticatie ongeldig" - }, - "step": { - "user": { - "data": { - "host": "Host/IP", - "password": "Wachtwoord" - }, - "title": "Instellen Velux KLF 200" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/velux/translations/nl.json b/homeassistant/components/velux/translations/nl.json deleted file mode 100644 index 4a906f39fc1699..00000000000000 --- a/homeassistant/components/velux/translations/nl.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "config": { - "error": { - "invalid_auth": "Invalid authentication" - }, - "step": { - "user": { - "data": { - "host": "Host/IP", - "password": "Password" - }, - "title": "Setup your Velux KLF 200" - } - } - } -} \ No newline at end of file From e93732744034ccc0e4121586999424bae5441a9a Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sun, 16 May 2021 19:18:55 +0200 Subject: [PATCH 08/14] Fix typo --- homeassistant/components/velux/config_flow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index a71c010a5826b1..8d854387a2a986 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -17,7 +17,7 @@ class VeluxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for youless.""" + """Handle a config flow for velux.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL @@ -49,7 +49,6 @@ async def async_step_user(self, user_input=None): async def async_step_import(self, import_config): """Import config from configuration.yaml.""" - entries = self._async_current_entries() self._async_abort_entries_match({CONF_HOST: import_config[CONF_HOST]}) return await self.async_step_user(import_config) From 702ded2acd2eb6b7feceb060b4568d66c3f759ab Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sun, 16 May 2021 19:32:04 +0200 Subject: [PATCH 09/14] Extend exception handling --- homeassistant/components/velux/config_flow.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index 8d854387a2a986..6e286600ce8f6c 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -39,9 +39,12 @@ async def async_step_user(self, user_input=None): title=host, data=user_input, ) - except PyVLXException: - _LOGGER.exception("Cannot connect to KLF 200 gateway") + except PyVLXException as ex: + _LOGGER.exception("Unable to connect to Velux gateway: %s", ex) errors["base"] = "invalid_auth" + except OSError as ex: + _LOGGER.exception("Unable to connect to Velux gateway: %s", ex) + errors["base"] = "cannot_connect" return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, errors=errors From 9be4604c497562237d89983f1b866035494e92c5 Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sun, 16 May 2021 19:34:10 +0200 Subject: [PATCH 10/14] Extend exception handling --- homeassistant/components/velux/config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index 6e286600ce8f6c..90f9c10b557e09 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -42,9 +42,9 @@ async def async_step_user(self, user_input=None): except PyVLXException as ex: _LOGGER.exception("Unable to connect to Velux gateway: %s", ex) errors["base"] = "invalid_auth" - except OSError as ex: + except Exception as ex: # pylint: disable=broad-except _LOGGER.exception("Unable to connect to Velux gateway: %s", ex) - errors["base"] = "cannot_connect" + errors["base"] = "unknown" return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, errors=errors From 0c5a66d8ec77dcfe50fc1ccdd30ac08f69948696 Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sun, 16 May 2021 19:37:13 +0200 Subject: [PATCH 11/14] Remove unused declaration --- homeassistant/components/velux/config_flow.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index 90f9c10b557e09..b8932635da1016 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -4,7 +4,7 @@ from pyvlx import PyVLX, PyVLXException import voluptuous as vol -from homeassistant import config_entries, exceptions +from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_PASSWORD from .const import DOMAIN # pylint:disable=unused-import @@ -55,7 +55,3 @@ async def async_step_import(self, import_config): self._async_abort_entries_match({CONF_HOST: import_config[CONF_HOST]}) return await self.async_step_user(import_config) - - -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" From b5f294a6c8b7d0197dd3437584dae2d74989a0b9 Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sun, 16 May 2021 20:18:03 +0200 Subject: [PATCH 12/14] Remove unnessary constant. Register a veluxModule per config entry in the global hass object, so we can have multiple instances --- homeassistant/components/velux/__init__.py | 13 ++++++++----- homeassistant/components/velux/config_flow.py | 2 +- homeassistant/components/velux/const.py | 1 + homeassistant/components/velux/cover.py | 5 +++-- homeassistant/components/velux/scene.py | 6 ++++-- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py index 9e0bba20588cb6..93b4b3bbdd2763 100644 --- a/homeassistant/components/velux/__init__.py +++ b/homeassistant/components/velux/__init__.py @@ -7,8 +7,8 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant -DOMAIN = "velux" -DATA_VELUX = "data_velux" +from .const import CONFIG_KEY_MODULE, DOMAIN + PLATFORMS = ["cover", "scene"] _LOGGER = logging.getLogger(__name__) @@ -30,9 +30,12 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Set up Velux using config flow.""" try: - hass.data[DATA_VELUX] = VeluxModule(hass, config_entry) - hass.data[DATA_VELUX].setup() - await hass.data[DATA_VELUX].async_start() + veluxModule = VeluxModule(hass, config_entry) + veluxModule.setup() + await veluxModule.async_start() + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][config_entry.entry_id] = {CONFIG_KEY_MODULE: veluxModule} except PyVLXException as ex: _LOGGER.exception("Can't connect to velux interface: %s", ex) diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index b8932635da1016..e7d024820d013d 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -7,7 +7,7 @@ from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_PASSWORD -from .const import DOMAIN # pylint:disable=unused-import +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/velux/const.py b/homeassistant/components/velux/const.py index b73fa4f97113e9..34bc032621b48c 100644 --- a/homeassistant/components/velux/const.py +++ b/homeassistant/components/velux/const.py @@ -1,3 +1,4 @@ """Const for Velux.""" DOMAIN = "velux" +CONFIG_KEY_MODULE = "velux_module" diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 2941230c1c8129..977040e0983f43 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -24,7 +24,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from . import DATA_VELUX +from .const import CONFIG_KEY_MODULE, DOMAIN async def async_setup_entry( @@ -34,7 +34,8 @@ async def async_setup_entry( ) -> None: """Set up cover(s) for Velux platform.""" entities = [] - for node in hass.data[DATA_VELUX].pyvlx.nodes: + veluxModule = hass.data[DOMAIN][entry.entry_id][CONFIG_KEY_MODULE] + for node in veluxModule.pyvlx.nodes: if isinstance(node, OpeningDevice): entities.append(VeluxCover(node)) async_add_entities(entities) diff --git a/homeassistant/components/velux/scene.py b/homeassistant/components/velux/scene.py index ef11a99f4afb88..3bf65cfc049d1b 100644 --- a/homeassistant/components/velux/scene.py +++ b/homeassistant/components/velux/scene.py @@ -5,7 +5,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from . import _LOGGER, DATA_VELUX +from . import _LOGGER +from .const import CONFIG_KEY_MODULE, DOMAIN async def async_setup_entry( @@ -14,7 +15,8 @@ async def async_setup_entry( async_add_entities, ) -> None: """Set up the scenes for Velux platform.""" - entities = [VeluxScene(scene) for scene in hass.data[DATA_VELUX].pyvlx.scenes] + veluxModule = hass.data[DOMAIN][entry.entry_id][CONFIG_KEY_MODULE] + entities = [VeluxScene(scene) for scene in veluxModule.pyvlx.scenes] async_add_entities(entities) From 8e041d5cf5e77d619b98678194d170118d2cfee3 Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sun, 16 May 2021 20:26:36 +0200 Subject: [PATCH 13/14] Re-add config schema --- homeassistant/components/velux/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py index 93b4b3bbdd2763..f1b7959d38ce0d 100644 --- a/homeassistant/components/velux/__init__.py +++ b/homeassistant/components/velux/__init__.py @@ -2,16 +2,27 @@ import logging from pyvlx import PyVLX, PyVLXException +import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant +import homeassistant.helpers.config_validation as cv from .const import CONFIG_KEY_MODULE, DOMAIN PLATFORMS = ["cover", "scene"] _LOGGER = logging.getLogger(__name__) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PASSWORD): cv.string} + ) + }, + extra=vol.ALLOW_EXTRA, +) + async def async_setup(hass: HomeAssistant, config: dict): """Component setup, run import config flow for each entry in config.""" From 6f87d8e057d0c264ee8b2b84a9f4e4cddcf4b76b Mon Sep 17 00:00:00 2001 From: Bram Gerritsen Date: Sun, 16 May 2021 20:42:37 +0200 Subject: [PATCH 14/14] Prevent setting up integration for same host twice --- homeassistant/components/velux/config_flow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index e7d024820d013d..990b3c195544fd 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -30,6 +30,9 @@ async def async_step_user(self, user_input=None): host = user_input[CONF_HOST] password = user_input[CONF_PASSWORD] + await self.async_set_unique_id(host) + self._abort_if_unique_id_configured() + pyvlx = PyVLX(host=host, password=password) await pyvlx.connect()