From a7922690c435c240e37cac3d11400cd7c3c38510 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Apr 2025 11:34:33 -1000 Subject: [PATCH] Adjust cover reproduce state to prefer setting positions if supported (#143226) --- .../components/cover/reproduce_state.py | 46 ++++++------ .../components/cover/test_reproduce_state.py | 70 +++++++++++++++++-- 2 files changed, 84 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/cover/reproduce_state.py b/homeassistant/components/cover/reproduce_state.py index de3e0cebfb7..927e725460c 100644 --- a/homeassistant/components/cover/reproduce_state.py +++ b/homeassistant/components/cover/reproduce_state.py @@ -73,14 +73,14 @@ async def _async_set_position( 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: + if CoverEntityFeature.SET_POSITION in features: await service_call( SERVICE_SET_COVER_POSITION, service_data | {ATTR_POSITION: target_position} ) + elif 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) else: # Requested a position but the cover doesn't support it return False @@ -98,15 +98,17 @@ async def _async_set_tilt_position( 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: + if CoverEntityFeature.SET_TILT_POSITION in features: await service_call( SERVICE_SET_COVER_TILT_POSITION, service_data | {ATTR_TILT_POSITION: target_tilt_position}, ) + elif ( + 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) else: # Requested a tilt position but the cover doesn't support it return False @@ -183,12 +185,12 @@ async def _async_reproduce_state( 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) + current_position: int | None = current_attrs.get(ATTR_CURRENT_POSITION) + target_position: int | None = 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) + current_tilt_position: int | None = current_attrs.get(ATTR_CURRENT_TILT_POSITION) + target_tilt_position: int | None = target_attrs.get(ATTR_CURRENT_TILT_POSITION) tilt_position_matches = current_tilt_position == target_tilt_position state_matches = cur_state.state == target_state @@ -214,19 +216,11 @@ async def _async_reproduce_state( ) service_data = {ATTR_ENTITY_ID: entity_id} - set_position = ( - not position_matches - and target_position is not None - and await _async_set_position( - service_call, service_data, features, target_position - ) + set_position = 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 - ) + set_tilt = 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: diff --git a/tests/components/cover/test_reproduce_state.py b/tests/components/cover/test_reproduce_state.py index 57fc5aed5e9..dfc22abac91 100644 --- a/tests/components/cover/test_reproduce_state.py +++ b/tests/components/cover/test_reproduce_state.py @@ -178,6 +178,22 @@ async def test_reproducing_states( | CoverEntityFeature.OPEN, }, ) + hass.states.async_set( + "cover.closed_supports_all_features", + CoverState.CLOSED, + { + ATTR_CURRENT_POSITION: 0, + ATTR_CURRENT_TILT_POSITION: 0, + ATTR_SUPPORTED_FEATURES: CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + | CoverEntityFeature.STOP + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION, + }, + ) hass.states.async_set( "cover.tilt_only_open", CoverState.OPEN, @@ -249,6 +265,14 @@ async def test_reproducing_states( await async_reproduce_state( hass, [ + State( + "cover.closed_supports_all_features", + CoverState.CLOSED, + { + ATTR_CURRENT_POSITION: 0, + ATTR_CURRENT_TILT_POSITION: 0, + }, + ), 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), @@ -364,6 +388,11 @@ async def test_reproducing_states( await async_reproduce_state( hass, [ + State( + "cover.closed_supports_all_features", + CoverState.CLOSED, + {ATTR_CURRENT_POSITION: 0, ATTR_CURRENT_TILT_POSITION: 50}, + ), State("cover.entity_close", CoverState.OPEN), State( "cover.closed_only_supports_close_open", @@ -458,7 +487,6 @@ async def test_reproducing_states( valid_close_calls = [ {"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"}, ] @@ -481,11 +509,8 @@ async def test_reproducing_states( valid_open_calls.remove(call.data) 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) == len(valid_close_tilt_calls) @@ -495,9 +520,7 @@ async def test_reproducing_states( valid_close_tilt_calls.remove(call.data) 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) @@ -523,6 +546,14 @@ async def test_reproducing_states( "entity_id": "cover.open_only_supports_position", ATTR_POSITION: 0, }, + { + "entity_id": "cover.closed_supports_all_features", + ATTR_POSITION: 0, + }, + { + "entity_id": "cover.entity_entirely_open", + ATTR_POSITION: 0, + }, ] assert len(position_calls) == len(valid_position_calls) for call in position_calls: @@ -551,7 +582,34 @@ async def test_reproducing_states( "entity_id": "cover.tilt_partial_open_only_supports_tilt_position", ATTR_TILT_POSITION: 70, }, + { + "entity_id": "cover.closed_supports_all_features", + ATTR_TILT_POSITION: 50, + }, + { + "entity_id": "cover.entity_close_tilt", + ATTR_TILT_POSITION: 100, + }, + { + "entity_id": "cover.entity_open_tilt", + ATTR_TILT_POSITION: 0, + }, + { + "entity_id": "cover.entity_entirely_open", + ATTR_TILT_POSITION: 0, + }, + { + "entity_id": "cover.tilt_only_tilt_position_100", + ATTR_TILT_POSITION: 0, + }, + { + "entity_id": "cover.tilt_only_tilt_position_0", + ATTR_TILT_POSITION: 100, + }, ] + for call in position_tilt_calls: + if ATTR_TILT_POSITION not in call.data: + continue assert len(position_tilt_calls) == len(valid_position_tilt_calls) for call in position_tilt_calls: assert call.domain == "cover"