Skip to content

Commit 2a999bd

Browse files
committed
feature(velux): add gateway as device and make it via_device
1 parent c0db966 commit 2a999bd

File tree

7 files changed

+65
-25
lines changed

7 files changed

+65
-25
lines changed

homeassistant/components/velux/__init__.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
11
"""Support for VELUX KLF 200 devices."""
22

3+
from __future__ import annotations
4+
5+
from dataclasses import dataclass
6+
37
from pyvlx import PyVLX, PyVLXException
48

59
from homeassistant.config_entries import ConfigEntry
610
from homeassistant.const import CONF_HOST, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP
711
from homeassistant.core import HomeAssistant, ServiceCall
12+
from homeassistant.helpers import device_registry as dr
813

914
from .const import DOMAIN, LOGGER, PLATFORMS
1015

11-
type VeluxConfigEntry = ConfigEntry[PyVLX]
16+
17+
@dataclass
18+
class VeluxData:
19+
"""Runtime data for Velux integration."""
20+
21+
pyvlx: PyVLX
22+
gateway_device_id: tuple[str, str]
1223

1324

14-
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
25+
type VeluxConfigEntry = ConfigEntry[VeluxData]
26+
27+
28+
async def async_setup_entry(hass: HomeAssistant, entry: VeluxConfigEntry) -> bool:
1529
"""Set up the velux component."""
1630
host = entry.data[CONF_HOST]
1731
password = entry.data[CONF_PASSWORD]
@@ -25,7 +39,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
2539
LOGGER.exception("Can't connect to velux interface: %s", ex)
2640
return False
2741

28-
entry.runtime_data = pyvlx
42+
device_identifier = (DOMAIN, f"gateway_{entry.entry_id}")
43+
entry.runtime_data = VeluxData(pyvlx=pyvlx, gateway_device_id=device_identifier)
44+
45+
device_registry = dr.async_get(hass)
46+
device_registry.async_get_or_create(
47+
config_entry_id=entry.entry_id,
48+
identifiers={device_identifier},
49+
name="KLF 200 Gateway",
50+
manufacturer="Velux",
51+
model="KLF 200",
52+
hw_version=str(pyvlx.klf200.version.hardwareversion)
53+
if pyvlx.klf200.version
54+
else None,
55+
sw_version=str(pyvlx.klf200.version.softwareversion)
56+
if pyvlx.klf200.version
57+
else None,
58+
)
2959

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

4878

49-
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
79+
async def async_unload_entry(hass: HomeAssistant, entry: VeluxConfigEntry) -> bool:
5080
"""Unload a config entry."""
5181
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

homeassistant/components/velux/binary_sensor.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@
2424

2525
async def async_setup_entry(
2626
hass: HomeAssistant,
27-
config: VeluxConfigEntry,
27+
config_entry: VeluxConfigEntry,
2828
async_add_entities: AddConfigEntryEntitiesCallback,
2929
) -> None:
3030
"""Set up rain sensor(s) for Velux platform."""
31-
pyvlx = config.runtime_data
31+
pyvlx = config_entry.runtime_data.pyvlx
3232

3333
async_add_entities(
34-
VeluxRainSensor(node, config.entry_id)
34+
VeluxRainSensor(node, config_entry)
3535
for node in pyvlx.nodes
3636
if isinstance(node, Window) and node.rain_sensor
3737
)
@@ -46,9 +46,9 @@ class VeluxRainSensor(VeluxEntity, BinarySensorEntity):
4646
_attr_device_class = BinarySensorDeviceClass.MOISTURE
4747
_attr_translation_key = "rain_sensor"
4848

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

5454
async def async_update(self) -> None:

homeassistant/components/velux/cover.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@
3232

3333
async def async_setup_entry(
3434
hass: HomeAssistant,
35-
config: VeluxConfigEntry,
35+
config_entry: VeluxConfigEntry,
3636
async_add_entities: AddConfigEntryEntitiesCallback,
3737
) -> None:
3838
"""Set up cover(s) for Velux platform."""
39-
pyvlx = config.runtime_data
39+
pyvlx = config_entry.runtime_data.pyvlx
4040
async_add_entities(
41-
VeluxCover(node, config.entry_id)
41+
VeluxCover(node, config_entry)
4242
for node in pyvlx.nodes
4343
if isinstance(node, OpeningDevice)
4444
)
@@ -53,9 +53,9 @@ class VeluxCover(VeluxEntity, CoverEntity):
5353
# Do not name the "main" feature of the device (position control)
5454
_attr_name = None
5555

56-
def __init__(self, node: OpeningDevice, config_entry_id: str) -> None:
56+
def __init__(self, node: OpeningDevice, config_entry: VeluxConfigEntry) -> None:
5757
"""Initialize VeluxCover."""
58-
super().__init__(node, config_entry_id)
58+
super().__init__(node, config_entry)
5959
# Window is the default device class for covers
6060
self._attr_device_class = CoverDeviceClass.WINDOW
6161
if isinstance(node, Awning):

homeassistant/components/velux/entity.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from homeassistant.helpers.device_registry import DeviceInfo
77
from homeassistant.helpers.entity import Entity
88

9+
from . import VeluxConfigEntry
910
from .const import DOMAIN
1011

1112

@@ -15,25 +16,26 @@ class VeluxEntity(Entity):
1516
_attr_should_poll = False
1617
_attr_has_entity_name = True
1718

18-
def __init__(self, node: Node, config_entry_id: str) -> None:
19+
def __init__(self, node: Node, config_entry: VeluxConfigEntry) -> None:
1920
"""Initialize the Velux device."""
2021
self.node = node
21-
self._attr_unique_id = (
22+
unique_id = (
2223
node.serial_number
2324
if node.serial_number
24-
else f"{config_entry_id}_{node.node_id}"
25+
else f"{config_entry.entry_id}_{node.node_id}"
2526
)
27+
self._attr_unique_id = unique_id
28+
2629
self._attr_device_info = DeviceInfo(
2730
identifiers={
2831
(
2932
DOMAIN,
30-
node.serial_number
31-
if node.serial_number
32-
else f"{config_entry_id}_{node.node_id}",
33+
unique_id,
3334
)
3435
},
3536
name=node.name if node.name else f"#{node.node_id}",
3637
serial_number=node.serial_number,
38+
via_device=config_entry.runtime_data.gateway_device_id,
3739
)
3840

3941
@callback

homeassistant/components/velux/light.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818

1919
async def async_setup_entry(
2020
hass: HomeAssistant,
21-
config: VeluxConfigEntry,
21+
config_entry: VeluxConfigEntry,
2222
async_add_entities: AddConfigEntryEntitiesCallback,
2323
) -> None:
2424
"""Set up light(s) for Velux platform."""
25-
pyvlx = config.runtime_data
25+
pyvlx = config_entry.runtime_data.pyvlx
2626
async_add_entities(
27-
VeluxLight(node, config.entry_id)
27+
VeluxLight(node, config_entry)
2828
for node in pyvlx.nodes
2929
if isinstance(node, LighteningDevice)
3030
)

homeassistant/components/velux/scene.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515

1616
async def async_setup_entry(
1717
hass: HomeAssistant,
18-
config: VeluxConfigEntry,
18+
config_entry: VeluxConfigEntry,
1919
async_add_entities: AddConfigEntryEntitiesCallback,
2020
) -> None:
2121
"""Set up the scenes for Velux platform."""
22-
pyvlx = config.runtime_data
22+
pyvlx = config_entry.runtime_data.pyvlx
2323

2424
entities = [VeluxScene(scene) for scene in pyvlx.scenes]
2525
async_add_entities(entities)

tests/components/velux/test_binary_sensor.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,11 @@ async def test_rain_sensor_device_association(
9595
# Verify device has correct identifiers
9696
assert ("velux", mock_window.serial_number) in device_entry.identifiers
9797
assert device_entry.name == mock_window.name
98+
99+
# Verify via_device is gateway
100+
assert device_entry.via_device_id is not None
101+
via_device_entry = device_registry.async_get(device_entry.via_device_id)
102+
assert via_device_entry is not None
103+
assert via_device_entry.identifiers == {
104+
mock_config_entry.runtime_data.gateway_device_id
105+
}

0 commit comments

Comments
 (0)