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
94 changes: 45 additions & 49 deletions homeassistant/components/velux/__init__.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,66 @@
"""Support for VELUX KLF 200 devices."""
import logging

from pyvlx import PyVLX, PyVLXException
import voluptuous as vol
from pyvlx import PyVLX

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.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady

from .const import DOMAIN, PLATFORMS

DOMAIN = "velux"
DATA_VELUX = "data_velux"
SUPPORTED_DOMAINS = ["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}

async def async_setup(hass, config):
"""Set up the Velux KLF platform via configuration.yaml."""
if DOMAIN in config:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": "import"}, data=config[DOMAIN]
)
)
},
extra=vol.ALLOW_EXTRA,
)
return True


async def async_setup(hass, config):
"""Set up the velux component."""
try:
hass.data[DATA_VELUX] = VeluxModule(hass, config[DOMAIN])
hass.data[DATA_VELUX].setup()
await hass.data[DATA_VELUX].async_start()
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up the Velux KLF platforms via Config Flow."""
_LOGGER.debug("Setting up velux entry: %s", entry.data)
host = entry.data[CONF_HOST]
password = entry.data[CONF_PASSWORD]
gateway = PyVLX(host=host, password=password)

except PyVLXException as ex:
_LOGGER.exception("Can't connect to velux interface: %s", ex)
return False
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = gateway
try:
await gateway.connect()
except (OSError, ConnectionAbortedError) as ex:
_LOGGER.error("Unable to connect to KLF200: %s", str(ex))
raise ConfigEntryNotReady from ex
await gateway.load_nodes()
await gateway.load_scenes()

for component in SUPPORTED_DOMAINS:
for component in PLATFORMS:
hass.async_create_task(
discovery.async_load_platform(hass, component, DOMAIN, {}, config)
hass.config_entries.async_forward_entry_setup(entry, component)
)
return True

async def async_reboot_gateway(service_call):
await gateway.reboot_gateway()

class VeluxModule:
"""Abstraction for velux component."""
hass.services.async_register(DOMAIN, "reboot_gateway", async_reboot_gateway)

def __init__(self, hass, domain_config):
"""Initialize for velux component."""
self.pyvlx = None
self._hass = hass
self._domain_config = domain_config
return True

def setup(self):
"""Velux component setup."""

async def on_hass_stop(event):
"""Close connection when hass stops."""
_LOGGER.debug("Velux interface terminated")
await self.pyvlx.disconnect()
async def async_unload_entry(hass, entry):
"""Unloading the Velux platform."""
gateway = hass.data[DOMAIN][entry.entry_id]
await gateway.reboot_gateway()
await gateway.disconnect()

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)
self.pyvlx = PyVLX(host=host, password=password)
for component in PLATFORMS:
await hass.config_entries.async_forward_entry_unload(entry, component)

async def async_start(self):
"""Start velux component."""
_LOGGER.debug("Velux interface started")
await self.pyvlx.load_scenes()
await self.pyvlx.load_nodes()
return True
91 changes: 91 additions & 0 deletions homeassistant/components/velux/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Velux component config flow."""
# https://developers.home-assistant.io/docs/config_entries_config_flow_handler#defining-your-config-flow
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__)

RESULT_AUTH_FAILED = "connection_failed"
RESULT_SUCCESS = "success"


class VeluxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Velux config flow."""

def __init__(self):
"""Initialize."""
self._velux = None
self._host = None
self._password = None
self._hostname = None
self.bridge = None

def _get_entry(self):
return self.async_create_entry(
title=self._host,
data={CONF_HOST: self._host, CONF_PASSWORD: self._password},
)

async def async_step_import(self, user_input=None):
"""Handle configuration by yaml file."""
return await self.async_step_user(user_input)

async def async_step_user(self, user_input=None):
"""Handle configuration via user input."""
errors = {}
if user_input is not None:
self._host = user_input[CONF_HOST]
self._password = user_input[CONF_PASSWORD]
await self.async_set_unique_id(self._host)
self._abort_if_unique_id_configured()
self.bridge = PyVLX(host=self._host, password=self._password)
try:
await self.bridge.connect()
await self.bridge.disconnect()
return self._get_entry()
except PyVLXException:
errors["base"] = "invalid_auth"
except OSError:
errors["base"] = "invalid_host"
else:
errors["base"] = "cannot_connect"

data_schema = vol.Schema(
{
vol.Required(CONF_HOST, default=self._host): str,
vol.Required(CONF_PASSWORD, default=self._password): str,
}
)

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

async def async_step_unignore(self, user_input):
Copy link
Member

Choose a reason for hiding this comment

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

No need to add this step.

"""Rediscover a previously ignored discover."""
unique_id = user_input["unique_id"]
await self.async_set_unique_id(unique_id)
return await self.async_step_user()

async def async_step_zeroconf(self, info):
"""Handle discovery by zeroconf."""
if (
info is None
or not info.get("hostname")
or not info["hostname"].startswith("VELUX_KLF_LAN")
):
return self.async_abort(reason="no_devices_found")

self._host = info.get("host")

await self.async_set_unique_id(self._host)
self._abort_if_unique_id_configured()

return await self.async_step_user()
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 @@
"""Constants for Velux Integration."""

DOMAIN = "velux"
PLATFORMS = ["cover", "scene"]
59 changes: 53 additions & 6 deletions homeassistant/components/velux/cover.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
"""Support for Velux covers."""
import logging

from pyvlx import OpeningDevice, Position
from pyvlx.opening_device import Awning, Blind, GarageDoor, Gate, RollerShutter, Window

from homeassistant.components.cover import (
ATTR_POSITION,
ATTR_TILT_POSITION,
DEVICE_CLASS_AWNING,
DEVICE_CLASS_BLIND,
DEVICE_CLASS_GARAGE,
DEVICE_CLASS_GATE,
DEVICE_CLASS_SHUTTER,
DEVICE_CLASS_WINDOW,
SUPPORT_CLOSE,
SUPPORT_CLOSE_TILT,
SUPPORT_OPEN,
SUPPORT_OPEN_TILT,
SUPPORT_SET_POSITION,
SUPPORT_SET_TILT_POSITION,
SUPPORT_STOP,
SUPPORT_STOP_TILT,
CoverEntity,
)
from homeassistant.core import callback

from . import DATA_VELUX
from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up cover(s) for Velux platform."""
entities = []
for node in hass.data[DATA_VELUX].pyvlx.nodes:
gateway = hass.data[DOMAIN][entry.entry_id]
for node in gateway.nodes:
_LOGGER.debug("Node will be added: %s", node.name)
if isinstance(node, OpeningDevice):
entities.append(VeluxCover(node))
async_add_entities(entities)
Expand Down Expand Up @@ -54,7 +65,7 @@ async def async_added_to_hass(self):
@property
def unique_id(self):
"""Return the unique ID of this cover."""
return self.node.serial_number
return self.node.node_id

@property
def name(self):
Expand All @@ -69,13 +80,29 @@ def should_poll(self):
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION | SUPPORT_STOP
supported_features = (
SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION | SUPPORT_STOP
)
if self.current_cover_tilt_position is not None:
supported_features |= (
SUPPORT_OPEN_TILT
| SUPPORT_CLOSE_TILT
| SUPPORT_SET_TILT_POSITION
| SUPPORT_STOP_TILT
)
return supported_features

@property
def current_cover_position(self):
"""Return the current position of the cover."""
return 100 - self.node.position.position_percent

@property
def current_cover_tilt_position(self):
"""Return the current position of the cover."""
if isinstance(self.node, Blind):
return 100 - self.node.orientation.position_percent

@property
def device_class(self):
"""Define this cover as either awning, blind, garage, gate, shutter or window."""
Expand Down Expand Up @@ -110,11 +137,31 @@ async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
if ATTR_POSITION in kwargs:
position_percent = 100 - kwargs[ATTR_POSITION]

await self.node.set_position(
Position(position_percent=position_percent), wait_for_completion=False
)

async def async_stop_cover(self, **kwargs):
"""Stop the cover."""
await self.node.stop(wait_for_completion=False)

async def async_close_cover_tilt(self, **kwargs):
"""Close cover tilt."""
await self.node.close_orientation(wait_for_completion=False)

async def async_open_cover_tilt(self, **kwargs):
"""Open cover tilt."""
await self.node.open_orientation(wait_for_completion=False)

async def async_stop_cover_tilt(self, **kwargs):
"""Stop cover tilt."""
await self.node.stop_orientation(wait_for_completion=False)

async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover to a specific position."""
if ATTR_TILT_POSITION in kwargs:
position_percent = 100 - kwargs[ATTR_TILT_POSITION]
orientation = Position(position_percent=position_percent)
await self.node.set_orientation(
orientation=orientation, wait_for_completion=False
)
14 changes: 11 additions & 3 deletions homeassistant/components/velux/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
"domain": "velux",
"name": "Velux",
"documentation": "https://www.home-assistant.io/integrations/velux",
"requirements": ["pyvlx==0.2.16"],
"codeowners": ["@Julius2342"]
}
"codeowners": [
"@Julius2342"
],
"requirements": [
"pyvlx==0.2.18"
],
"zeroconf": [
"_http._tcp.local."
Copy link
Member

Choose a reason for hiding this comment

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

],
"config_flow": true
}
13 changes: 10 additions & 3 deletions homeassistant/components/velux/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

from homeassistant.components.scene import Scene

from . import _LOGGER, DATA_VELUX
from . import _LOGGER
from .const import DOMAIN


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the scenes for Velux platform."""
entities = [VeluxScene(scene) for scene in hass.data[DATA_VELUX].pyvlx.scenes]
gateway = hass.data[DOMAIN][entry.entry_id]
entities = [VeluxScene(scene) for scene in gateway.scenes]
async_add_entities(entities)


Expand All @@ -25,6 +27,11 @@ def name(self):
"""Return the name of the scene."""
return self.scene.name

@property
def unique_id(self):
"""Return the unique ID of this scene."""
return self.scene.scene_id

async def async_activate(self, **kwargs: Any) -> None:
"""Activate the scene."""
await self.scene.run(wait_for_completion=False)
4 changes: 4 additions & 0 deletions homeassistant/components/velux/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Velux Integration services

reboot_gateway:
description: Reboots the KLF200 Gateway.
Loading