mirror of
https://github.com/home-assistant/core.git
synced 2026-08-28 18:24:50 -05:00
Return None for Velux cover position when unknown (#168566)
This commit is contained in:
@@ -97,13 +97,17 @@ class VeluxCover(VeluxEntity, CoverEntity):
|
||||
self._attr_device_class = CoverDeviceClass.SHUTTER
|
||||
|
||||
@property
|
||||
def current_cover_position(self) -> int:
|
||||
def current_cover_position(self) -> int | None:
|
||||
"""Return the current position of the cover."""
|
||||
if not self.node.position.known:
|
||||
return None
|
||||
return 100 - self.node.position.position_percent
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Return if the cover is closed."""
|
||||
if not self.node.position.known:
|
||||
return None
|
||||
return self.node.position.closed
|
||||
|
||||
@property
|
||||
@@ -168,22 +172,29 @@ class VeluxDualRollerShutter(VeluxCover):
|
||||
self.part = part
|
||||
|
||||
@property
|
||||
def current_cover_position(self) -> int:
|
||||
"""Return the current position of the cover."""
|
||||
def _part_position(self) -> Position:
|
||||
"""Return the pyvlx Position for this part of the shutter."""
|
||||
if self.part == VeluxDualRollerPart.UPPER:
|
||||
return 100 - self.node.position_upper_curtain.position_percent
|
||||
return self.node.position_upper_curtain
|
||||
if self.part == VeluxDualRollerPart.LOWER:
|
||||
return 100 - self.node.position_lower_curtain.position_percent
|
||||
return 100 - self.node.position.position_percent
|
||||
return self.node.position_lower_curtain
|
||||
return self.node.position
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
def current_cover_position(self) -> int | None:
|
||||
"""Return the current position of the cover."""
|
||||
position = self._part_position
|
||||
if not position.known:
|
||||
return None
|
||||
return 100 - position.position_percent
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Return if the cover is closed."""
|
||||
if self.part == VeluxDualRollerPart.UPPER:
|
||||
return self.node.position_upper_curtain.closed
|
||||
if self.part == VeluxDualRollerPart.LOWER:
|
||||
return self.node.position_lower_curtain.closed
|
||||
return self.node.position.closed
|
||||
position = self._part_position
|
||||
if not position.known:
|
||||
return None
|
||||
return position.closed
|
||||
|
||||
@wrap_pyvlx_call_exceptions
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
@@ -227,6 +238,8 @@ class VeluxBlind(VeluxCover):
|
||||
@property
|
||||
def current_cover_tilt_position(self) -> int | None:
|
||||
"""Return the current tilt position of the cover."""
|
||||
if not self.node.orientation.known:
|
||||
return None
|
||||
return 100 - self.node.orientation.position_percent
|
||||
|
||||
@wrap_pyvlx_call_exceptions
|
||||
|
||||
@@ -74,7 +74,7 @@ def mock_window() -> AsyncMock:
|
||||
window.device_updated_cbs = []
|
||||
window.is_opening = False
|
||||
window.is_closing = False
|
||||
window.position = MagicMock(position_percent=30, closed=False)
|
||||
window.position = MagicMock(position_percent=30, closed=False, known=True)
|
||||
window.wink = AsyncMock()
|
||||
window.pyvlx = MagicMock()
|
||||
return window
|
||||
@@ -89,9 +89,13 @@ def mock_dual_roller_shutter() -> AsyncMock:
|
||||
cover.serial_number = "987654321"
|
||||
cover.is_opening = False
|
||||
cover.is_closing = False
|
||||
cover.position_upper_curtain = MagicMock(position_percent=30, closed=False)
|
||||
cover.position_lower_curtain = MagicMock(position_percent=30, closed=False)
|
||||
cover.position = MagicMock(position_percent=30, closed=False)
|
||||
cover.position_upper_curtain = MagicMock(
|
||||
position_percent=30, closed=False, known=True
|
||||
)
|
||||
cover.position_lower_curtain = MagicMock(
|
||||
position_percent=30, closed=False, known=True
|
||||
)
|
||||
cover.position = MagicMock(position_percent=30, closed=False, known=True)
|
||||
cover.pyvlx = MagicMock()
|
||||
return cover
|
||||
|
||||
@@ -104,11 +108,11 @@ def mock_blind() -> AsyncMock:
|
||||
blind.name = "Test Blind"
|
||||
blind.serial_number = "4711"
|
||||
# Standard cover position (used by current_cover_position)
|
||||
blind.position = MagicMock(position_percent=40, closed=False)
|
||||
blind.position = MagicMock(position_percent=40, closed=False, known=True)
|
||||
blind.is_opening = False
|
||||
blind.is_closing = False
|
||||
# Orientation/tilt-related attributes and methods
|
||||
blind.orientation = MagicMock(position_percent=25)
|
||||
blind.orientation = MagicMock(position_percent=25, known=True)
|
||||
blind.open_orientation = AsyncMock()
|
||||
blind.close_orientation = AsyncMock()
|
||||
blind.stop_orientation = AsyncMock()
|
||||
@@ -175,9 +179,13 @@ def mock_cover_type(request: pytest.FixtureRequest) -> AsyncMock:
|
||||
cover.serial_number = f"serial_{request.param.__name__}"
|
||||
cover.is_opening = False
|
||||
cover.is_closing = False
|
||||
cover.position = MagicMock(position_percent=30, closed=False)
|
||||
cover.position_upper_curtain = MagicMock(position_percent=30, closed=False)
|
||||
cover.position_lower_curtain = MagicMock(position_percent=30, closed=False)
|
||||
cover.position = MagicMock(position_percent=30, closed=False, known=True)
|
||||
cover.position_upper_curtain = MagicMock(
|
||||
position_percent=30, closed=False, known=True
|
||||
)
|
||||
cover.position_lower_curtain = MagicMock(
|
||||
position_percent=30, closed=False, known=True
|
||||
)
|
||||
cover.pyvlx = MagicMock()
|
||||
return cover
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ from homeassistant.const import (
|
||||
STATE_CLOSING,
|
||||
STATE_OPEN,
|
||||
STATE_OPENING,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -475,6 +476,77 @@ async def test_non_blind_has_no_tilt_position(
|
||||
assert "current_tilt_position" not in state.attributes
|
||||
|
||||
|
||||
# Unknown position tests
|
||||
|
||||
|
||||
async def test_window_unknown_position(
|
||||
hass: HomeAssistant, mock_window: AsyncMock
|
||||
) -> None:
|
||||
"""When the device position is not known, state and position must be unknown."""
|
||||
|
||||
entity_id = "cover.test_window"
|
||||
|
||||
mock_window.position.known = False
|
||||
await update_callback_entity(hass, mock_window)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.attributes.get("current_position") is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("unknown_attr", "unknown_entity_id"),
|
||||
[
|
||||
("position", "cover.test_dual_roller_shutter"),
|
||||
("position_upper_curtain", "cover.test_dual_roller_shutter_upper_shutter"),
|
||||
("position_lower_curtain", "cover.test_dual_roller_shutter_lower_shutter"),
|
||||
],
|
||||
)
|
||||
async def test_dual_roller_shutter_unknown_position(
|
||||
hass: HomeAssistant,
|
||||
mock_dual_roller_shutter: AsyncMock,
|
||||
unknown_attr: str,
|
||||
unknown_entity_id: str,
|
||||
) -> None:
|
||||
"""Each part falls back to unknown independently when only its position is unknown."""
|
||||
|
||||
all_entity_ids = {
|
||||
"cover.test_dual_roller_shutter",
|
||||
"cover.test_dual_roller_shutter_upper_shutter",
|
||||
"cover.test_dual_roller_shutter_lower_shutter",
|
||||
}
|
||||
|
||||
getattr(mock_dual_roller_shutter, unknown_attr).known = False
|
||||
await update_callback_entity(hass, mock_dual_roller_shutter)
|
||||
|
||||
state = hass.states.get(unknown_entity_id)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.attributes.get("current_position") is None
|
||||
|
||||
for entity_id in all_entity_ids - {unknown_entity_id}:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state != STATE_UNKNOWN
|
||||
assert state.attributes.get("current_position") == 70
|
||||
|
||||
|
||||
async def test_blind_unknown_tilt_position(
|
||||
hass: HomeAssistant, mock_blind: AsyncMock
|
||||
) -> None:
|
||||
"""Tilt position must be None when the orientation is not known."""
|
||||
|
||||
entity_id = "cover.test_blind"
|
||||
|
||||
mock_blind.orientation.known = False
|
||||
await update_callback_entity(hass, mock_blind)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.attributes.get("current_tilt_position") is None
|
||||
|
||||
|
||||
# Exception handling tests
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user