mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
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:
parent
07e7672b78
commit
5dc1a321dd
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user