Skip to content

Commit 1c10b85

Browse files
wollewCopilot
authored andcommitted
Use position percentage for closed status in Velux (home-assistant#151679)
Co-authored-by: Copilot <[email protected]>
1 parent 91a7db0 commit 1c10b85

File tree

7 files changed

+102
-14
lines changed

7 files changed

+102
-14
lines changed

CODEOWNERS

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

homeassistant/components/velux/cover.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44

55
from typing import Any, cast
66

7-
from pyvlx import OpeningDevice, Position
8-
from pyvlx.opening_device import Awning, Blind, GarageDoor, Gate, RollerShutter
7+
from pyvlx import (
8+
Awning,
9+
Blind,
10+
GarageDoor,
11+
Gate,
12+
OpeningDevice,
13+
Position,
14+
RollerShutter,
15+
)
916

1017
from homeassistant.components.cover import (
1118
ATTR_POSITION,
@@ -97,7 +104,10 @@ def current_cover_tilt_position(self) -> int | None:
97104
@property
98105
def is_closed(self) -> bool:
99106
"""Return if the cover is closed."""
100-
return self.node.position.closed
107+
# do not use the node's closed state but rely on cover position
108+
# until https://github.com/Julius2342/pyvlx/pull/543 is merged.
109+
# once merged this can again return self.node.position.closed
110+
return self.current_cover_position == 0
101111

102112
@property
103113
def is_opening(self) -> bool:

homeassistant/components/velux/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"domain": "velux",
33
"name": "Velux",
4-
"codeowners": ["@Julius2342", "@DeerMaximum", "@pawlizio"],
4+
"codeowners": ["@Julius2342", "@DeerMaximum", "@pawlizio", "@wollew"],
55
"config_flow": true,
66
"dhcp": [
77
{

tests/components/velux/__init__.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,31 @@
11
"""Tests for the Velux integration."""
2+
3+
from unittest.mock import MagicMock
4+
5+
from freezegun.api import FrozenDateTimeFactory
6+
7+
from homeassistant.helpers.device_registry import HomeAssistant
8+
from homeassistant.helpers.entity_platform import timedelta
9+
10+
from tests.common import async_fire_time_changed
11+
12+
13+
async def update_callback_entity(
14+
hass: HomeAssistant, mock_velux_node: MagicMock
15+
) -> None:
16+
"""Simulate an update triggered by the pyvlx lib for a Velux node."""
17+
18+
callback = mock_velux_node.register_device_updated_cb.call_args[0][0]
19+
await callback(mock_velux_node)
20+
await hass.async_block_till_done()
21+
22+
23+
async def update_polled_entities(
24+
hass: HomeAssistant, freezer: FrozenDateTimeFactory
25+
) -> None:
26+
"""Simulate an update trigger from polling."""
27+
# just fire a time changed event to trigger the polling
28+
29+
freezer.tick(timedelta(minutes=5))
30+
async_fire_time_changed(hass)
31+
await hass.async_block_till_done()

tests/components/velux/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ def mock_window() -> AsyncMock:
7272
window.rain_sensor = True
7373
window.serial_number = "123456789"
7474
window.get_limitation.return_value = MagicMock(min_value=0)
75+
window.is_opening = False
76+
window.is_closing = False
77+
window.position = MagicMock(position_percent=30, closed=False)
7578
return window
7679

7780

tests/components/velux/test_binary_sensor.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Tests for the Velux binary sensor platform."""
22

3-
from datetime import timedelta
43
from unittest.mock import MagicMock, patch
54

65
from freezegun.api import FrozenDateTimeFactory
@@ -11,7 +10,9 @@
1110
from homeassistant.helpers.device_registry import DeviceRegistry
1211
from homeassistant.helpers.entity_registry import EntityRegistry
1312

14-
from tests.common import MockConfigEntry, async_fire_time_changed
13+
from . import update_polled_entities
14+
15+
from tests.common import MockConfigEntry
1516

1617

1718
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
@@ -33,9 +34,7 @@ async def test_rain_sensor_state(
3334
test_entity_id = "binary_sensor.test_window_rain_sensor"
3435

3536
# simulate no rain detected
36-
freezer.tick(timedelta(minutes=5))
37-
async_fire_time_changed(hass)
38-
await hass.async_block_till_done()
37+
await update_polled_entities(hass, freezer)
3938
state = hass.states.get(test_entity_id)
4039
assert state is not None
4140
assert state.state == STATE_OFF
@@ -49,9 +48,7 @@ async def test_rain_sensor_state(
4948

5049
# simulate rain detected (other Velux models report 93)
5150
mock_window.get_limitation.return_value.min_value = 93
52-
freezer.tick(timedelta(minutes=5))
53-
async_fire_time_changed(hass)
54-
await hass.async_block_till_done()
51+
await update_polled_entities(hass, freezer)
5552
state = hass.states.get(test_entity_id)
5653
assert state is not None
5754
assert state.state == STATE_ON
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Tests for the Velux cover platform."""
2+
3+
from unittest.mock import AsyncMock, patch
4+
5+
from freezegun.api import FrozenDateTimeFactory
6+
import pytest
7+
8+
from homeassistant.const import STATE_CLOSED, STATE_OPEN, Platform
9+
from homeassistant.core import HomeAssistant
10+
11+
from . import update_callback_entity
12+
13+
from tests.common import MockConfigEntry
14+
15+
16+
@pytest.mark.usefixtures("mock_module")
17+
async def test_cover_closed(
18+
hass: HomeAssistant,
19+
mock_window: AsyncMock,
20+
mock_config_entry: MockConfigEntry,
21+
freezer: FrozenDateTimeFactory,
22+
) -> None:
23+
"""Test the cover closed state."""
24+
25+
mock_config_entry.add_to_hass(hass)
26+
with patch("homeassistant.components.velux.PLATFORMS", [Platform.COVER]):
27+
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
28+
await hass.async_block_till_done()
29+
30+
test_entity_id = "cover.test_window"
31+
32+
# Initial state should be open
33+
state = hass.states.get(test_entity_id)
34+
assert state is not None
35+
assert state.state == STATE_OPEN
36+
37+
# Update mock window position to closed percentage
38+
mock_window.position.position_percent = 100
39+
# Also directly set position to closed, so this test should
40+
# continue to be green after the lib is fixed
41+
mock_window.position.closed = True
42+
43+
# Trigger entity state update via registered callback
44+
await update_callback_entity(hass, mock_window)
45+
46+
state = hass.states.get(test_entity_id)
47+
assert state is not None
48+
assert state.state == STATE_CLOSED

0 commit comments

Comments
 (0)