Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 30 additions & 12 deletions homeassistant/components/velux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
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
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv

DOMAIN = "velux"
DATA_VELUX = "data_velux"
from .const import CONFIG_KEY_MODULE, DOMAIN

PLATFORMS = ["cover", "scene"]
_LOGGER = logging.getLogger(__name__)

Expand All @@ -23,28 +24,45 @@
)


async def async_setup(hass, config):
"""Set up the velux component."""
async def async_setup(hass: HomeAssistant, config: dict):
"""Component setup, run import config flow for each entry in config."""
if DOMAIN not in config:
return True

hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN]
)
)

return True


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].setup()
await hass.data[DATA_VELUX].async_start()
veluxModule = VeluxModule(hass, config_entry)
veluxModule.setup()
await veluxModule.async_start()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async_start() may fail (see #34844), in which case you should raise ConfigEntryNotReady. That would really resolve #34844, I guess. The issue was closed due to inactivity.


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)
return False

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


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
Expand All @@ -62,8 +80,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(
Expand Down
60 changes: 60 additions & 0 deletions homeassistant/components/velux/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Config flow for velux integration."""
import logging

from pyvlx import PyVLX, PyVLXException
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_PASSWORD

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_HOST): str, vol.Required(CONF_PASSWORD): str}
)


class VeluxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for velux."""

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]

await self.async_set_unique_id(host)
self._abort_if_unique_id_configured()

pyvlx = PyVLX(host=host, password=password)
await pyvlx.connect()

await pyvlx.disconnect()

return self.async_create_entry(
title=host,
data=user_input,
)
except PyVLXException as ex:
_LOGGER.exception("Unable to connect to Velux gateway: %s", ex)
errors["base"] = "invalid_auth"
except Exception as ex: # pylint: disable=broad-except
_LOGGER.exception("Unable to connect to Velux gateway: %s", ex)
errors["base"] = "unknown"

return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)

async def async_step_import(self, import_config):
"""Import config from configuration.yaml."""
self._async_abort_entries_match({CONF_HOST: import_config[CONF_HOST]})

return await self.async_step_user(import_config)
4 changes: 4 additions & 0 deletions homeassistant/components/velux/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""Const for Velux."""

DOMAIN = "velux"
CONFIG_KEY_MODULE = "velux_module"
14 changes: 10 additions & 4 deletions homeassistant/components/velux/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,21 @@
SUPPORT_STOP_TILT,
CoverEntity,
)
from homeassistant.core import callback
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_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities,
) -> 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)
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/velux/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
14 changes: 11 additions & 3 deletions homeassistant/components/velux/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@
from typing import Any

from homeassistant.components.scene import Scene
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_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
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)


Expand Down
16 changes: 16 additions & 0 deletions homeassistant/components/velux/strings.json
Original file line number Diff line number Diff line change
@@ -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%]"
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No newline at end of file.

1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@
"upcloud",
"upnp",
"velbus",
"velux",
"vera",
"verisure",
"vesync",
Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions tests/components/velux/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the Velux Component."""
39 changes: 39 additions & 0 deletions tests/components/velux/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -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"}