mirror of
https://github.com/home-assistant/core.git
synced 2025-09-12 08:11:38 +02:00
Add DeviceInfo to Velux entities (#149575)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Support for rain sensors build into some velux windows."""
|
||||
"""Support for rain sensors built into some Velux windows."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -44,12 +44,12 @@ class VeluxRainSensor(VeluxEntity, BinarySensorEntity):
|
||||
_attr_should_poll = True # the rain sensor / opening limitations needs polling unlike the rest of the Velux devices
|
||||
_attr_entity_registry_enabled_default = False
|
||||
_attr_device_class = BinarySensorDeviceClass.MOISTURE
|
||||
_attr_translation_key = "rain_sensor"
|
||||
|
||||
def __init__(self, node: OpeningDevice, config_entry_id: str) -> None:
|
||||
"""Initialize VeluxRainSensor."""
|
||||
super().__init__(node, config_entry_id)
|
||||
self._attr_unique_id = f"{self._attr_unique_id}_rain_sensor"
|
||||
self._attr_name = f"{node.name} Rain sensor"
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Fetch the latest state from the device."""
|
||||
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from typing import Any, cast
|
||||
|
||||
from pyvlx import OpeningDevice, Position
|
||||
from pyvlx.opening_device import Awning, Blind, GarageDoor, Gate, RollerShutter, Window
|
||||
from pyvlx.opening_device import Awning, Blind, GarageDoor, Gate, RollerShutter
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION,
|
||||
@@ -44,9 +44,13 @@ class VeluxCover(VeluxEntity, CoverEntity):
|
||||
_is_blind = False
|
||||
node: OpeningDevice
|
||||
|
||||
# Do not name the "main" feature of the device (position control)
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, node: OpeningDevice, config_entry_id: str) -> None:
|
||||
"""Initialize VeluxCover."""
|
||||
super().__init__(node, config_entry_id)
|
||||
# Window is the default device class for covers
|
||||
self._attr_device_class = CoverDeviceClass.WINDOW
|
||||
if isinstance(node, Awning):
|
||||
self._attr_device_class = CoverDeviceClass.AWNING
|
||||
@@ -59,8 +63,6 @@ class VeluxCover(VeluxEntity, CoverEntity):
|
||||
self._attr_device_class = CoverDeviceClass.GATE
|
||||
if isinstance(node, RollerShutter):
|
||||
self._attr_device_class = CoverDeviceClass.SHUTTER
|
||||
if isinstance(node, Window):
|
||||
self._attr_device_class = CoverDeviceClass.WINDOW
|
||||
|
||||
@property
|
||||
def supported_features(self) -> CoverEntityFeature:
|
||||
|
@@ -3,13 +3,17 @@
|
||||
from pyvlx import Node
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class VeluxEntity(Entity):
|
||||
"""Abstraction for al Velux entities."""
|
||||
"""Abstraction for all Velux entities."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, node: Node, config_entry_id: str) -> None:
|
||||
"""Initialize the Velux device."""
|
||||
@@ -19,7 +23,18 @@ class VeluxEntity(Entity):
|
||||
if node.serial_number
|
||||
else f"{config_entry_id}_{node.node_id}"
|
||||
)
|
||||
self._attr_name = node.name if node.name else f"#{node.node_id}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={
|
||||
(
|
||||
DOMAIN,
|
||||
node.serial_number
|
||||
if node.serial_number
|
||||
else f"{config_entry_id}_{node.node_id}",
|
||||
)
|
||||
},
|
||||
name=node.name if node.name else f"#{node.node_id}",
|
||||
serial_number=node.serial_number,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_register_callbacks(self):
|
||||
|
@@ -27,5 +27,12 @@
|
||||
"name": "Reboot gateway",
|
||||
"description": "Reboots the KLF200 Gateway."
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"rain_sensor": {
|
||||
"name": "Rain sensor"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,8 @@ import pytest
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceRegistry
|
||||
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
@@ -21,17 +23,15 @@ async def test_rain_sensor_state(
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test the rain sensor."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
test_entity_id = "binary_sensor.test_window_rain_sensor"
|
||||
|
||||
with (
|
||||
patch("homeassistant.components.velux.PLATFORMS", [Platform.BINARY_SENSOR]),
|
||||
):
|
||||
with patch("homeassistant.components.velux.PLATFORMS", [Platform.BINARY_SENSOR]):
|
||||
# setup config entry
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
test_entity_id = "binary_sensor.test_window_rain_sensor"
|
||||
|
||||
# simulate no rain detected
|
||||
freezer.tick(timedelta(minutes=5))
|
||||
async_fire_time_changed(hass)
|
||||
@@ -48,3 +48,39 @@ async def test_rain_sensor_state(
|
||||
state = hass.states.get(test_entity_id)
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.usefixtures("mock_module")
|
||||
async def test_rain_sensor_device_association(
|
||||
hass: HomeAssistant,
|
||||
mock_window: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: EntityRegistry,
|
||||
device_registry: DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test the rain sensor is properly associated with its device."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.velux.PLATFORMS", [Platform.BINARY_SENSOR]):
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
test_entity_id = "binary_sensor.test_window_rain_sensor"
|
||||
|
||||
# Verify entity exists
|
||||
state = hass.states.get(test_entity_id)
|
||||
assert state is not None
|
||||
|
||||
# Get entity entry
|
||||
entity_entry = entity_registry.async_get(test_entity_id)
|
||||
assert entity_entry is not None
|
||||
assert entity_entry.device_id is not None
|
||||
|
||||
# Get device entry
|
||||
device_entry = device_registry.async_get(entity_entry.device_id)
|
||||
assert device_entry is not None
|
||||
|
||||
# Verify device has correct identifiers
|
||||
assert ("velux", mock_window.serial_number) in device_entry.identifiers
|
||||
assert device_entry.name == mock_window.name
|
||||
|
Reference in New Issue
Block a user