Rework cover reproduce_state to consider supported features (#140558)

* Handle open/closed state in reproduce_state for tilt only covers

fixes #137144

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* cleanups

* rework

* rework

* rework

* rework

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* more coverage

* back compat

* back compat

* back compat

* cleanups

* cleanups

* cleanups

* cleanups

* comments

* comments
This commit is contained in:
J. Nick Koston 2025-03-14 16:14:09 -10:00 committed by GitHub
parent 07e7672b78
commit 5dc1a321dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 570 additions and 93 deletions

View File

@ -3,12 +3,14 @@
from __future__ import annotations
import asyncio
from collections.abc import Iterable
from collections.abc import Coroutine, Iterable
from functools import partial
import logging
from typing import Any
from typing import Any, Final
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
SERVICE_CLOSE_COVER,
SERVICE_CLOSE_COVER_TILT,
SERVICE_OPEN_COVER,
@ -16,7 +18,8 @@ from homeassistant.const import (
SERVICE_SET_COVER_POSITION,
SERVICE_SET_COVER_TILT_POSITION,
)
from homeassistant.core import Context, HomeAssistant, State
from homeassistant.core import Context, HomeAssistant, ServiceResponse, State
from homeassistant.util.enum import try_parse_enum
from . import (
ATTR_CURRENT_POSITION,
@ -24,17 +27,140 @@ from . import (
ATTR_POSITION,
ATTR_TILT_POSITION,
DOMAIN,
CoverEntityFeature,
CoverState,
)
_LOGGER = logging.getLogger(__name__)
VALID_STATES = {
CoverState.CLOSED,
CoverState.CLOSING,
CoverState.OPEN,
CoverState.OPENING,
}
OPENING_STATES = {CoverState.OPENING, CoverState.OPEN}
CLOSING_STATES = {CoverState.CLOSING, CoverState.CLOSED}
VALID_STATES: set[CoverState] = OPENING_STATES | CLOSING_STATES
FULL_OPEN: Final = 100
FULL_CLOSE: Final = 0
def _determine_features(current_attrs: dict[str, Any]) -> CoverEntityFeature:
"""Determine supported features based on current attributes."""
features = CoverEntityFeature(0)
if ATTR_CURRENT_POSITION in current_attrs:
features |= (
CoverEntityFeature.SET_POSITION
| CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
)
if ATTR_CURRENT_TILT_POSITION in current_attrs:
features |= (
CoverEntityFeature.SET_TILT_POSITION
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.CLOSE_TILT
)
if features == CoverEntityFeature(0):
features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
return features
async def _async_set_position(
service_call: partial[Coroutine[Any, Any, ServiceResponse]],
service_data: dict[str, Any],
features: CoverEntityFeature,
target_position: int,
) -> bool:
"""Set the position of the cover.
Returns True if the position was set, False if there is no
supported method for setting the position.
"""
if target_position == FULL_CLOSE and CoverEntityFeature.CLOSE in features:
await service_call(SERVICE_CLOSE_COVER, service_data)
elif target_position == FULL_OPEN and CoverEntityFeature.OPEN in features:
await service_call(SERVICE_OPEN_COVER, service_data)
elif CoverEntityFeature.SET_POSITION in features:
await service_call(
SERVICE_SET_COVER_POSITION, service_data | {ATTR_POSITION: target_position}
)
else:
# Requested a position but the cover doesn't support it
return False
return True
async def _async_set_tilt_position(
service_call: partial[Coroutine[Any, Any, ServiceResponse]],
service_data: dict[str, Any],
features: CoverEntityFeature,
target_tilt_position: int,
) -> bool:
"""Set the tilt position of the cover.
Returns True if the tilt position was set, False if there is no
supported method for setting the tilt position.
"""
if target_tilt_position == FULL_CLOSE and CoverEntityFeature.CLOSE_TILT in features:
await service_call(SERVICE_CLOSE_COVER_TILT, service_data)
elif target_tilt_position == FULL_OPEN and CoverEntityFeature.OPEN_TILT in features:
await service_call(SERVICE_OPEN_COVER_TILT, service_data)
elif CoverEntityFeature.SET_TILT_POSITION in features:
await service_call(
SERVICE_SET_COVER_TILT_POSITION,
service_data | {ATTR_TILT_POSITION: target_tilt_position},
)
else:
# Requested a tilt position but the cover doesn't support it
return False
return True
async def _async_close_cover(
service_call: partial[Coroutine[Any, Any, ServiceResponse]],
service_data: dict[str, Any],
features: CoverEntityFeature,
set_position: bool,
set_tilt: bool,
) -> None:
"""Close the cover if it was not closed by setting the position."""
if not set_position:
if CoverEntityFeature.CLOSE in features:
await service_call(SERVICE_CLOSE_COVER, service_data)
elif CoverEntityFeature.SET_POSITION in features:
await service_call(
SERVICE_SET_COVER_POSITION, service_data | {ATTR_POSITION: FULL_CLOSE}
)
if not set_tilt:
if CoverEntityFeature.CLOSE_TILT in features:
await service_call(SERVICE_CLOSE_COVER_TILT, service_data)
elif CoverEntityFeature.SET_TILT_POSITION in features:
await service_call(
SERVICE_SET_COVER_TILT_POSITION,
service_data | {ATTR_TILT_POSITION: FULL_CLOSE},
)
async def _async_open_cover(
service_call: partial[Coroutine[Any, Any, ServiceResponse]],
service_data: dict[str, Any],
features: CoverEntityFeature,
set_position: bool,
set_tilt: bool,
) -> None:
"""Open the cover if it was not opened by setting the position."""
if not set_position:
if CoverEntityFeature.OPEN in features:
await service_call(SERVICE_OPEN_COVER, service_data)
elif CoverEntityFeature.SET_POSITION in features:
await service_call(
SERVICE_SET_COVER_POSITION, service_data | {ATTR_POSITION: FULL_OPEN}
)
if not set_tilt:
if CoverEntityFeature.OPEN_TILT in features:
await service_call(SERVICE_OPEN_COVER_TILT, service_data)
elif CoverEntityFeature.SET_TILT_POSITION in features:
await service_call(
SERVICE_SET_COVER_TILT_POSITION,
service_data | {ATTR_TILT_POSITION: FULL_OPEN},
)
async def _async_reproduce_state(
@ -45,74 +171,72 @@ async def _async_reproduce_state(
reproduce_options: dict[str, Any] | None = None,
) -> None:
"""Reproduce a single state."""
if (cur_state := hass.states.get(state.entity_id)) is None:
_LOGGER.warning("Unable to find entity %s", state.entity_id)
entity_id = state.entity_id
if (cur_state := hass.states.get(entity_id)) is None:
_LOGGER.warning("Unable to find entity %s", entity_id)
return
if state.state not in VALID_STATES:
_LOGGER.warning(
"Invalid state specified for %s: %s", state.entity_id, state.state
)
if (target_state := state.state) not in VALID_STATES:
_LOGGER.warning("Invalid state specified for %s: %s", entity_id, target_state)
return
current_attrs = cur_state.attributes
target_attrs = state.attributes
current_position = current_attrs.get(ATTR_CURRENT_POSITION)
target_position = target_attrs.get(ATTR_CURRENT_POSITION)
position_matches = current_position == target_position
current_tilt_position = current_attrs.get(ATTR_CURRENT_TILT_POSITION)
target_tilt_position = target_attrs.get(ATTR_CURRENT_TILT_POSITION)
tilt_position_matches = current_tilt_position == target_tilt_position
state_matches = cur_state.state == target_state
# Return if we are already at the right state.
if (
cur_state.state == state.state
and cur_state.attributes.get(ATTR_CURRENT_POSITION)
== state.attributes.get(ATTR_CURRENT_POSITION)
and cur_state.attributes.get(ATTR_CURRENT_TILT_POSITION)
== state.attributes.get(ATTR_CURRENT_TILT_POSITION)
):
if state_matches and position_matches and tilt_position_matches:
return
service_data = {ATTR_ENTITY_ID: state.entity_id}
service_data_tilting = {ATTR_ENTITY_ID: state.entity_id}
features = try_parse_enum(
CoverEntityFeature, current_attrs.get(ATTR_SUPPORTED_FEATURES)
)
if features is None:
# Backwards compatibility for integrations that
# don't set supported features since it previously
# worked without it.
_LOGGER.warning("Supported features is not set for %s", entity_id)
features = _determine_features(current_attrs)
if not (
cur_state.state == state.state
and cur_state.attributes.get(ATTR_CURRENT_POSITION)
== state.attributes.get(ATTR_CURRENT_POSITION)
):
# Open/Close
if state.state in [CoverState.CLOSED, CoverState.CLOSING]:
service = SERVICE_CLOSE_COVER
elif state.state in [CoverState.OPEN, CoverState.OPENING]:
if (
ATTR_CURRENT_POSITION in cur_state.attributes
and ATTR_CURRENT_POSITION in state.attributes
):
service = SERVICE_SET_COVER_POSITION
service_data[ATTR_POSITION] = state.attributes[ATTR_CURRENT_POSITION]
else:
service = SERVICE_OPEN_COVER
service_call = partial(
hass.services.async_call,
DOMAIN,
context=context,
blocking=True,
)
service_data = {ATTR_ENTITY_ID: entity_id}
await hass.services.async_call(
DOMAIN, service, service_data, context=context, blocking=True
set_position = (
not position_matches
and target_position is not None
and await _async_set_position(
service_call, service_data, features, target_position
)
)
set_tilt = (
not tilt_position_matches
and target_tilt_position is not None
and await _async_set_tilt_position(
service_call, service_data, features, target_tilt_position
)
)
if target_state in CLOSING_STATES:
await _async_close_cover(
service_call, service_data, features, set_position, set_tilt
)
if (
ATTR_CURRENT_TILT_POSITION in state.attributes
and ATTR_CURRENT_TILT_POSITION in cur_state.attributes
and cur_state.attributes.get(ATTR_CURRENT_TILT_POSITION)
!= state.attributes.get(ATTR_CURRENT_TILT_POSITION)
):
# Tilt position
if state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 100:
service_tilting = SERVICE_OPEN_COVER_TILT
elif state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 0:
service_tilting = SERVICE_CLOSE_COVER_TILT
else:
service_tilting = SERVICE_SET_COVER_TILT_POSITION
service_data_tilting[ATTR_TILT_POSITION] = state.attributes[
ATTR_CURRENT_TILT_POSITION
]
await hass.services.async_call(
DOMAIN,
service_tilting,
service_data_tilting,
context=context,
blocking=True,
elif target_state in OPENING_STATES:
await _async_open_cover(
service_call, service_data, features, set_position, set_tilt
)

View File

@ -7,9 +7,11 @@ from homeassistant.components.cover import (
ATTR_CURRENT_TILT_POSITION,
ATTR_POSITION,
ATTR_TILT_POSITION,
CoverEntityFeature,
CoverState,
)
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES,
SERVICE_CLOSE_COVER,
SERVICE_CLOSE_COVER_TILT,
SERVICE_OPEN_COVER,
@ -27,35 +29,213 @@ async def test_reproducing_states(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test reproducing Cover states."""
hass.states.async_set("cover.entity_close", CoverState.CLOSED, {})
hass.states.async_set(
"cover.entity_close",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE | CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.closed_only_supports_close_open",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE | CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.open_only_supports_close_open",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE | CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.open_missing_all_features",
CoverState.OPEN,
)
hass.states.async_set(
"cover.closed_missing_all_features_has_position",
CoverState.CLOSED,
{
ATTR_CURRENT_POSITION: 0,
},
)
hass.states.async_set(
"cover.open_missing_all_features_has_tilt_position",
CoverState.OPEN,
{
ATTR_CURRENT_TILT_POSITION: 50,
},
)
hass.states.async_set(
"cover.closed_only_supports_tilt_close_open",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT,
},
)
hass.states.async_set(
"cover.open_only_supports_tilt_close_open",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT,
},
)
hass.states.async_set(
"cover.closed_only_supports_position",
CoverState.CLOSED,
{
ATTR_CURRENT_POSITION: 0,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION,
},
)
hass.states.async_set(
"cover.open_only_supports_position",
CoverState.OPEN,
{ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION},
)
hass.states.async_set(
"cover.entity_close_attr",
CoverState.CLOSED,
{ATTR_CURRENT_POSITION: 0, ATTR_CURRENT_TILT_POSITION: 0},
{
ATTR_CURRENT_POSITION: 0,
ATTR_CURRENT_TILT_POSITION: 0,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.SET_TILT_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.entity_close_tilt", CoverState.CLOSED, {ATTR_CURRENT_TILT_POSITION: 50}
"cover.entity_close_tilt",
CoverState.CLOSED,
{
ATTR_CURRENT_TILT_POSITION: 50,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_TILT_POSITION,
},
)
hass.states.async_set("cover.entity_open", CoverState.OPEN, {})
hass.states.async_set(
"cover.entity_slightly_open", CoverState.OPEN, {ATTR_CURRENT_POSITION: 50}
"cover.entity_open",
CoverState.OPEN,
{ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE | CoverEntityFeature.OPEN},
)
hass.states.async_set(
"cover.entity_slightly_open",
CoverState.OPEN,
{
ATTR_CURRENT_POSITION: 50,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.entity_open_attr",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 100, ATTR_CURRENT_TILT_POSITION: 0},
{
ATTR_CURRENT_POSITION: 100,
ATTR_CURRENT_TILT_POSITION: 0,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.SET_TILT_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.entity_open_tilt",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 50, ATTR_CURRENT_TILT_POSITION: 50},
{
ATTR_CURRENT_POSITION: 50,
ATTR_CURRENT_TILT_POSITION: 50,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.SET_TILT_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.entity_entirely_open",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 100, ATTR_CURRENT_TILT_POSITION: 100},
{
ATTR_CURRENT_POSITION: 100,
ATTR_CURRENT_TILT_POSITION: 100,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.SET_TILT_POSITION
| CoverEntityFeature.CLOSE
| CoverEntityFeature.OPEN,
},
)
hass.states.async_set(
"cover.tilt_only_open",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT,
},
)
hass.states.async_set(
"cover.tilt_only_closed",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT,
},
)
hass.states.async_set(
"cover.tilt_only_tilt_position_100",
CoverState.OPEN,
{
ATTR_CURRENT_TILT_POSITION: 100,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_TILT_POSITION,
},
)
hass.states.async_set(
"cover.tilt_only_tilt_position_0",
CoverState.CLOSED,
{
ATTR_CURRENT_TILT_POSITION: 0,
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.SET_TILT_POSITION,
},
)
hass.states.async_set(
"cover.tilt_open_only_supports_tilt_position",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_TILT_POSITION,
},
)
hass.states.async_set(
"cover.tilt_partial_open_only_supports_tilt_position",
CoverState.OPEN,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_TILT_POSITION,
ATTR_CURRENT_TILT_POSITION: 50,
},
)
hass.states.async_set(
"cover.tilt_closed_only_supports_tilt_position",
CoverState.CLOSED,
{
ATTR_SUPPORTED_FEATURES: CoverEntityFeature.SET_TILT_POSITION,
},
)
close_calls = async_mock_service(hass, "cover", SERVICE_CLOSE_COVER)
open_calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
close_tilt_calls = async_mock_service(hass, "cover", SERVICE_CLOSE_COVER_TILT)
@ -70,6 +250,31 @@ async def test_reproducing_states(
hass,
[
State("cover.entity_close", CoverState.CLOSED),
State("cover.closed_only_supports_close_open", CoverState.CLOSED),
State("cover.closed_only_supports_tilt_close_open", CoverState.CLOSED),
State("cover.open_only_supports_close_open", CoverState.OPEN),
State("cover.open_only_supports_tilt_close_open", CoverState.OPEN),
State("cover.open_missing_all_features", CoverState.OPEN),
State(
"cover.closed_missing_all_features_has_position",
CoverState.CLOSED,
{
ATTR_CURRENT_POSITION: 0,
},
),
State(
"cover.open_missing_all_features_has_tilt_position",
CoverState.OPEN,
{
ATTR_CURRENT_TILT_POSITION: 50,
},
),
State(
"cover.closed_only_supports_position",
CoverState.CLOSED,
{ATTR_CURRENT_POSITION: 0},
),
State("cover.open_only_supports_position", CoverState.OPEN),
State(
"cover.entity_close_attr",
CoverState.CLOSED,
@ -101,6 +306,39 @@ async def test_reproducing_states(
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 100, ATTR_CURRENT_TILT_POSITION: 100},
),
State(
"cover.tilt_only_open",
CoverState.OPEN,
{},
),
State(
"cover.tilt_only_tilt_position_100",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 100},
),
State(
"cover.tilt_only_closed",
CoverState.CLOSED,
{},
),
State(
"cover.tilt_only_tilt_position_0",
CoverState.CLOSED,
{ATTR_CURRENT_TILT_POSITION: 0},
),
State(
"cover.tilt_partial_open_only_supports_tilt_position",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 50},
),
State(
"cover.tilt_open_only_supports_tilt_position",
CoverState.OPEN,
),
State(
"cover.tilt_closed_only_supports_tilt_position",
CoverState.CLOSED,
),
],
)
@ -127,6 +365,35 @@ async def test_reproducing_states(
hass,
[
State("cover.entity_close", CoverState.OPEN),
State(
"cover.closed_only_supports_close_open",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 100},
),
State(
"cover.open_only_supports_close_open",
CoverState.CLOSED,
{ATTR_CURRENT_POSITION: 50},
),
State(
"cover.open_only_supports_tilt_close_open",
CoverState.CLOSED,
{ATTR_CURRENT_TILT_POSITION: 50},
),
State("cover.closed_only_supports_tilt_close_open", CoverState.OPEN),
State("cover.open_missing_all_features", CoverState.CLOSED),
State(
"cover.closed_missing_all_features_has_position",
CoverState.OPEN,
{ATTR_CURRENT_POSITION: 70},
),
State(
"cover.open_missing_all_features_has_tilt_position",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 20},
),
State("cover.closed_only_supports_position", CoverState.OPEN),
State("cover.open_only_supports_position", CoverState.CLOSED),
State(
"cover.entity_close_attr",
CoverState.OPEN,
@ -152,6 +419,39 @@ async def test_reproducing_states(
),
# Should not raise
State("cover.non_existing", "on"),
State(
"cover.tilt_only_open",
CoverState.CLOSED,
{},
),
State(
"cover.tilt_only_tilt_position_100",
CoverState.CLOSED,
{ATTR_CURRENT_TILT_POSITION: 0},
),
State(
"cover.tilt_only_closed",
CoverState.OPEN,
{},
),
State(
"cover.tilt_only_tilt_position_0",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 100},
),
State(
"cover.tilt_partial_open_only_supports_tilt_position",
CoverState.OPEN,
{ATTR_CURRENT_TILT_POSITION: 70},
),
State(
"cover.tilt_open_only_supports_tilt_position",
CoverState.CLOSED,
),
State(
"cover.tilt_closed_only_supports_tilt_position",
CoverState.OPEN,
),
],
)
@ -159,8 +459,10 @@ async def test_reproducing_states(
{"entity_id": "cover.entity_open"},
{"entity_id": "cover.entity_open_attr"},
{"entity_id": "cover.entity_entirely_open"},
{"entity_id": "cover.open_only_supports_close_open"},
{"entity_id": "cover.open_missing_all_features"},
]
assert len(close_calls) == 3
assert len(close_calls) == len(valid_close_calls)
for call in close_calls:
assert call.domain == "cover"
assert call.data in valid_close_calls
@ -170,8 +472,9 @@ async def test_reproducing_states(
{"entity_id": "cover.entity_close"},
{"entity_id": "cover.entity_slightly_open"},
{"entity_id": "cover.entity_open_tilt"},
{"entity_id": "cover.closed_only_supports_close_open"},
]
assert len(open_calls) == 3
assert len(open_calls) == len(valid_open_calls)
for call in open_calls:
assert call.domain == "cover"
assert call.data in valid_open_calls
@ -180,27 +483,77 @@ async def test_reproducing_states(
valid_close_tilt_calls = [
{"entity_id": "cover.entity_open_tilt"},
{"entity_id": "cover.entity_entirely_open"},
{"entity_id": "cover.tilt_only_open"},
{"entity_id": "cover.entity_open_attr"},
{"entity_id": "cover.tilt_only_tilt_position_100"},
{"entity_id": "cover.open_only_supports_tilt_close_open"},
]
assert len(close_tilt_calls) == 2
assert len(close_tilt_calls) == len(valid_close_tilt_calls)
for call in close_tilt_calls:
assert call.domain == "cover"
assert call.data in valid_close_tilt_calls
valid_close_tilt_calls.remove(call.data)
assert len(open_tilt_calls) == 1
assert open_tilt_calls[0].domain == "cover"
assert open_tilt_calls[0].data == {"entity_id": "cover.entity_close_tilt"}
valid_open_tilt_calls = [
{"entity_id": "cover.entity_close_tilt"},
{"entity_id": "cover.tilt_only_closed"},
{"entity_id": "cover.tilt_only_tilt_position_0"},
{"entity_id": "cover.closed_only_supports_tilt_close_open"},
]
assert len(open_tilt_calls) == len(valid_open_tilt_calls)
for call in open_tilt_calls:
assert call.domain == "cover"
assert call.data in valid_open_tilt_calls
valid_open_tilt_calls.remove(call.data)
assert len(position_calls) == 1
assert position_calls[0].domain == "cover"
assert position_calls[0].data == {
"entity_id": "cover.entity_close_attr",
ATTR_POSITION: 50,
}
valid_position_calls = [
{
"entity_id": "cover.entity_close_attr",
ATTR_POSITION: 50,
},
{
"entity_id": "cover.closed_missing_all_features_has_position",
ATTR_POSITION: 70,
},
{
"entity_id": "cover.closed_only_supports_position",
ATTR_POSITION: 100,
},
{
"entity_id": "cover.open_only_supports_position",
ATTR_POSITION: 0,
},
]
assert len(position_calls) == len(valid_position_calls)
for call in position_calls:
assert call.domain == "cover"
assert call.data in valid_position_calls
valid_position_calls.remove(call.data)
assert len(position_tilt_calls) == 1
assert position_tilt_calls[0].domain == "cover"
assert position_tilt_calls[0].data == {
"entity_id": "cover.entity_close_attr",
ATTR_TILT_POSITION: 50,
}
valid_position_tilt_calls = [
{
"entity_id": "cover.entity_close_attr",
ATTR_TILT_POSITION: 50,
},
{
"entity_id": "cover.open_missing_all_features_has_tilt_position",
ATTR_TILT_POSITION: 20,
},
{
"entity_id": "cover.tilt_open_only_supports_tilt_position",
ATTR_TILT_POSITION: 0,
},
{
"entity_id": "cover.tilt_closed_only_supports_tilt_position",
ATTR_TILT_POSITION: 100,
},
{
"entity_id": "cover.tilt_partial_open_only_supports_tilt_position",
ATTR_TILT_POSITION: 70,
},
]
assert len(position_tilt_calls) == len(valid_position_tilt_calls)
for call in position_tilt_calls:
assert call.domain == "cover"
assert call.data in valid_position_tilt_calls
valid_position_tilt_calls.remove(call.data)