mirror of
https://github.com/home-assistant/core.git
synced 2026-02-18 03:40:52 +00:00
Compare commits
5 Commits
dev
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b5dc3f016 | ||
|
|
6688281acf | ||
|
|
033da68f02 | ||
|
|
e547e84df7 | ||
|
|
a7d342fe9b |
@@ -138,6 +138,24 @@ class CoverPositionMixin(ZWaveBaseEntity, CoverEntity):
|
||||
"""Return range between fully opened and fully closed position."""
|
||||
return self._fully_open_position - self._fully_closed_position
|
||||
|
||||
@callback
|
||||
def on_value_update(self) -> None:
|
||||
"""Handle value updates for the cover.
|
||||
|
||||
Clear opening/closing state when movement completes.
|
||||
"""
|
||||
# Clear opening/closing state when target matches current
|
||||
if (
|
||||
self._current_position_value
|
||||
and self._current_position_value.value is not None
|
||||
and self._target_position_value
|
||||
and self._target_position_value.value is not None
|
||||
and self._current_position_value.value == self._target_position_value.value
|
||||
):
|
||||
self._attr_is_opening = False
|
||||
self._attr_is_closing = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Return true if cover is closed."""
|
||||
@@ -159,30 +177,58 @@ class CoverPositionMixin(ZWaveBaseEntity, CoverEntity):
|
||||
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
||||
"""Move the cover to a specific position."""
|
||||
assert self._target_position_value
|
||||
assert self._current_position_value
|
||||
|
||||
target_position = self.percent_to_zwave_position(kwargs[ATTR_POSITION])
|
||||
current_position = self._current_position_value.value
|
||||
|
||||
# Determine direction before issuing command
|
||||
if current_position is not None and target_position > current_position:
|
||||
self._attr_is_opening = True
|
||||
self._attr_is_closing = False
|
||||
elif current_position is not None and target_position < current_position:
|
||||
self._attr_is_opening = False
|
||||
self._attr_is_closing = True
|
||||
else:
|
||||
# Target equals current or position is None - clear opening/closing state
|
||||
self._attr_is_opening = False
|
||||
self._attr_is_closing = False
|
||||
|
||||
await self._async_set_value(
|
||||
self._target_position_value,
|
||||
self.percent_to_zwave_position(kwargs[ATTR_POSITION]),
|
||||
target_position,
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Open the cover."""
|
||||
assert self._target_position_value
|
||||
self._attr_is_opening = True
|
||||
self._attr_is_closing = False
|
||||
await self._async_set_value(
|
||||
self._target_position_value, self._fully_open_position
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Close cover."""
|
||||
assert self._target_position_value
|
||||
self._attr_is_opening = False
|
||||
self._attr_is_closing = True
|
||||
await self._async_set_value(
|
||||
self._target_position_value, self._fully_closed_position
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_stop_cover(self, **kwargs: Any) -> None:
|
||||
"""Stop cover."""
|
||||
assert self._stop_position_value
|
||||
# Clear opening/closing state when stopped
|
||||
self._attr_is_opening = False
|
||||
self._attr_is_closing = False
|
||||
# Stop the cover, will stop regardless of the actual direction of travel.
|
||||
await self._async_set_value(self._stop_position_value, False)
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class CoverTiltMixin(ZWaveBaseEntity, CoverEntity):
|
||||
@@ -425,15 +471,24 @@ class ZWaveWindowCovering(CoverPositionMixin, CoverTiltMixin):
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Open the cover."""
|
||||
self._attr_is_opening = True
|
||||
self._attr_is_closing = False
|
||||
await self._async_set_value(self._up_value, True)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Close the cover."""
|
||||
self._attr_is_opening = False
|
||||
self._attr_is_closing = True
|
||||
await self._async_set_value(self._down_value, True)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_stop_cover(self, **kwargs: Any) -> None:
|
||||
"""Stop the cover."""
|
||||
self._attr_is_opening = False
|
||||
self._attr_is_closing = False
|
||||
await self._async_set_value(self._up_value, False)
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity):
|
||||
|
||||
@@ -1202,3 +1202,213 @@ async def test_window_covering_open_close(
|
||||
assert args["value"] is False
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async def test_multilevel_switch_cover_opening_closing_state(
|
||||
hass: HomeAssistant, client, aeotec_nano_shutter, integration
|
||||
) -> None:
|
||||
"""Test multilevel switch cover OPENING and CLOSING states."""
|
||||
node = aeotec_nano_shutter
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
|
||||
assert state
|
||||
assert state.state == CoverState.CLOSED
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 0
|
||||
|
||||
# Call open_cover - should immediately set state to OPENING
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_OPEN_COVER,
|
||||
{ATTR_ENTITY_ID: AEOTEC_SHUTTER_COVER_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.OPENING
|
||||
|
||||
# Simulate cover moving: update targetValue to 99
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 3,
|
||||
"args": {
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"commandClass": 38,
|
||||
"endpoint": 0,
|
||||
"property": "targetValue",
|
||||
"newValue": 99,
|
||||
"prevValue": 0,
|
||||
"propertyName": "targetValue",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
# State should still be OPENING
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.OPENING
|
||||
|
||||
# Simulate cover moving: currentValue is now 50, targetValue is still 99
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 3,
|
||||
"args": {
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"commandClass": 38,
|
||||
"endpoint": 0,
|
||||
"property": "currentValue",
|
||||
"newValue": 50,
|
||||
"prevValue": 0,
|
||||
"propertyName": "currentValue",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
# State should still be OPENING while moving
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.OPENING
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 51
|
||||
|
||||
# Simulate cover fully opened: currentValue reaches targetValue
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 3,
|
||||
"args": {
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"commandClass": 38,
|
||||
"endpoint": 0,
|
||||
"property": "currentValue",
|
||||
"newValue": 99,
|
||||
"prevValue": 50,
|
||||
"propertyName": "currentValue",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
# State should now be OPEN (cleared opening state)
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.OPEN
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 100
|
||||
|
||||
# Call close_cover - should immediately set state to CLOSING
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_CLOSE_COVER,
|
||||
{ATTR_ENTITY_ID: AEOTEC_SHUTTER_COVER_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.CLOSING
|
||||
|
||||
# Simulate target value update
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 3,
|
||||
"args": {
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"commandClass": 38,
|
||||
"endpoint": 0,
|
||||
"property": "targetValue",
|
||||
"newValue": 0,
|
||||
"prevValue": 99,
|
||||
"propertyName": "targetValue",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
# State should still be CLOSING
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.CLOSING
|
||||
|
||||
# Simulate cover moving: currentValue is now 50
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 3,
|
||||
"args": {
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"commandClass": 38,
|
||||
"endpoint": 0,
|
||||
"property": "currentValue",
|
||||
"newValue": 50,
|
||||
"prevValue": 99,
|
||||
"propertyName": "currentValue",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
# State should still be CLOSING while moving
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.CLOSING
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 51
|
||||
|
||||
# Simulate cover fully closed: currentValue reaches targetValue
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": 3,
|
||||
"args": {
|
||||
"commandClassName": "Multilevel Switch",
|
||||
"commandClass": 38,
|
||||
"endpoint": 0,
|
||||
"property": "currentValue",
|
||||
"newValue": 0,
|
||||
"prevValue": 50,
|
||||
"propertyName": "currentValue",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
|
||||
# State should now be CLOSED (cleared closing state)
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.CLOSED
|
||||
assert state.attributes[ATTR_CURRENT_POSITION] == 0
|
||||
|
||||
# Test stop cover clears the state
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_OPEN_COVER,
|
||||
{ATTR_ENTITY_ID: AEOTEC_SHUTTER_COVER_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.OPENING
|
||||
|
||||
# Stop the cover
|
||||
await hass.services.async_call(
|
||||
COVER_DOMAIN,
|
||||
SERVICE_STOP_COVER,
|
||||
{ATTR_ENTITY_ID: AEOTEC_SHUTTER_COVER_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
# Stop command should immediately clear opening/closing state
|
||||
state = hass.states.get(AEOTEC_SHUTTER_COVER_ENTITY)
|
||||
assert state.state == CoverState.CLOSED # Current position is 0 (closed)
|
||||
assert state.state != CoverState.OPENING
|
||||
assert state.state != CoverState.CLOSING
|
||||
|
||||
Reference in New Issue
Block a user