Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
38 changes: 34 additions & 4 deletions homeassistant/components/velux/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
"""Support for VELUX KLF 200 devices."""

from __future__ import annotations

from dataclasses import dataclass

from pyvlx import PyVLX, PyVLXException

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr

from .const import DOMAIN, LOGGER, PLATFORMS

type VeluxConfigEntry = ConfigEntry[PyVLX]

@dataclass
class VeluxData:
"""Runtime data for Velux integration."""

pyvlx: PyVLX
gateway_device_id: tuple[str, str]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
type VeluxConfigEntry = ConfigEntry[VeluxData]


async def async_setup_entry(hass: HomeAssistant, entry: VeluxConfigEntry) -> bool:
"""Set up the velux component."""
host = entry.data[CONF_HOST]
password = entry.data[CONF_PASSWORD]
Expand All @@ -25,7 +39,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
LOGGER.exception("Can't connect to velux interface: %s", ex)
return False

entry.runtime_data = pyvlx
device_identifier = (DOMAIN, f"gateway_{entry.entry_id}")
entry.runtime_data = VeluxData(pyvlx=pyvlx, gateway_device_id=device_identifier)

device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={device_identifier},
name="KLF 200 Gateway",
manufacturer="Velux",
model="KLF 200",
hw_version=str(pyvlx.klf200.version.hardwareversion)
if pyvlx.klf200.version
else None,
sw_version=str(pyvlx.klf200.version.softwareversion)
if pyvlx.klf200.version
else None,
)

async def on_hass_stop(event):
"""Close connection when hass stops."""
Expand All @@ -46,6 +76,6 @@ async def async_reboot_gateway(service_call: ServiceCall) -> None:
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: VeluxConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
10 changes: 5 additions & 5 deletions homeassistant/components/velux/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@

async def async_setup_entry(
hass: HomeAssistant,
config: VeluxConfigEntry,
config_entry: VeluxConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up rain sensor(s) for Velux platform."""
pyvlx = config.runtime_data
pyvlx = config_entry.runtime_data.pyvlx

async_add_entities(
VeluxRainSensor(node, config.entry_id)
VeluxRainSensor(node, config_entry)
for node in pyvlx.nodes
if isinstance(node, Window) and node.rain_sensor
)
Expand All @@ -46,9 +46,9 @@ class VeluxRainSensor(VeluxEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.MOISTURE
_attr_translation_key = "rain_sensor"

def __init__(self, node: OpeningDevice, config_entry_id: str) -> None:
def __init__(self, node: OpeningDevice, config_entry: VeluxConfigEntry) -> None:
"""Initialize VeluxRainSensor."""
super().__init__(node, config_entry_id)
super().__init__(node, config_entry)
self._attr_unique_id = f"{self._attr_unique_id}_rain_sensor"

async def async_update(self) -> None:
Expand Down
10 changes: 5 additions & 5 deletions homeassistant/components/velux/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@

async def async_setup_entry(
hass: HomeAssistant,
config: VeluxConfigEntry,
config_entry: VeluxConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up cover(s) for Velux platform."""
pyvlx = config.runtime_data
pyvlx = config_entry.runtime_data.pyvlx
async_add_entities(
VeluxCover(node, config.entry_id)
VeluxCover(node, config_entry)
for node in pyvlx.nodes
if isinstance(node, OpeningDevice)
)
Expand All @@ -53,9 +53,9 @@ class VeluxCover(VeluxEntity, CoverEntity):
# Do not name the "main" feature of the device (position control)
_attr_name = None

def __init__(self, node: OpeningDevice, config_entry_id: str) -> None:
def __init__(self, node: OpeningDevice, config_entry: VeluxConfigEntry) -> None:
"""Initialize VeluxCover."""
super().__init__(node, config_entry_id)
super().__init__(node, config_entry)
# Window is the default device class for covers
self._attr_device_class = CoverDeviceClass.WINDOW
if isinstance(node, Awning):
Expand Down
14 changes: 8 additions & 6 deletions homeassistant/components/velux/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity

from . import VeluxConfigEntry
from .const import DOMAIN


Expand All @@ -15,25 +16,26 @@ class VeluxEntity(Entity):
_attr_should_poll = False
_attr_has_entity_name = True

def __init__(self, node: Node, config_entry_id: str) -> None:
def __init__(self, node: Node, config_entry: VeluxConfigEntry) -> None:
"""Initialize the Velux device."""
self.node = node
self._attr_unique_id = (
unique_id = (
node.serial_number
if node.serial_number
else f"{config_entry_id}_{node.node_id}"
else f"{config_entry.entry_id}_{node.node_id}"
)
self._attr_unique_id = unique_id

self._attr_device_info = DeviceInfo(
identifiers={
(
DOMAIN,
node.serial_number
if node.serial_number
else f"{config_entry_id}_{node.node_id}",
unique_id,
)
},
name=node.name if node.name else f"#{node.node_id}",
serial_number=node.serial_number,
via_device=config_entry.runtime_data.gateway_device_id,
)

@callback
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/velux/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

async def async_setup_entry(
hass: HomeAssistant,
config: VeluxConfigEntry,
config_entry: VeluxConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up light(s) for Velux platform."""
pyvlx = config.runtime_data
pyvlx = config_entry.runtime_data.pyvlx
async_add_entities(
VeluxLight(node, config.entry_id)
VeluxLight(node, config_entry)
for node in pyvlx.nodes
if isinstance(node, LighteningDevice)
)
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/velux/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@

async def async_setup_entry(
hass: HomeAssistant,
config: VeluxConfigEntry,
config_entry: VeluxConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the scenes for Velux platform."""
pyvlx = config.runtime_data
pyvlx = config_entry.runtime_data.pyvlx

entities = [VeluxScene(scene) for scene in pyvlx.scenes]
async_add_entities(entities)
Expand Down
8 changes: 8 additions & 0 deletions tests/components/velux/test_binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,11 @@ async def test_rain_sensor_device_association(
# Verify device has correct identifiers
assert ("velux", mock_window.serial_number) in device_entry.identifiers
assert device_entry.name == mock_window.name

# Verify via_device is gateway
assert device_entry.via_device_id is not None
via_device_entry = device_registry.async_get(device_entry.via_device_id)
assert via_device_entry is not None
assert via_device_entry.identifiers == {
mock_config_entry.runtime_data.gateway_device_id
}
Loading