diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 71e89797c05..5c7139d6290 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -480,15 +480,30 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def _get_toggle_function( self, fns: dict[str, Callable[_P, _R]] ) -> Callable[_P, _R]: + # If we are opening or closing and we support stopping, then we should stop if self.supported_features & CoverEntityFeature.STOP and ( self.is_closing or self.is_opening ): return fns["stop"] - if self.is_closed: + + # If we are fully closed or in the process of closing, then we should open + if self.is_closed or self.is_closing: return fns["open"] - if self._cover_is_last_toggle_direction_open: + + # If we are fully open or in the process of opening, then we should close + if self.current_cover_position == 100 or self.is_opening: return fns["close"] - return fns["open"] + + # We are any of: + # * fully open but do not report `current_cover_position` + # * stopped partially open + # * either opening or closing, but do not report them + # If we previously reported opening/closing, we should move in the opposite direction. + # Otherwise, we must assume we are (partially) open and should always close. + # Note: _cover_is_last_toggle_direction_open will always remain True if we never report opening/closing. + return ( + fns["close"] if self._cover_is_last_toggle_direction_open else fns["open"] + ) # These can be removed if no deprecated constant are in this module anymore diff --git a/tests/components/cover/test_init.py b/tests/components/cover/test_init.py index 0052093298e..5ccd948cc6b 100644 --- a/tests/components/cover/test_init.py +++ b/tests/components/cover/test_init.py @@ -108,6 +108,15 @@ async def test_services( await call_service(hass, SERVICE_TOGGLE, ent6) assert is_opening(hass, ent6) + # After the unusual state transition: closing -> fully open, toggle should close + set_state(ent5, STATE_OPEN) + await call_service(hass, SERVICE_TOGGLE, ent5) # Start closing + assert is_closing(hass, ent5) + set_state(ent5, STATE_OPEN) # Unusual state transition from closing -> fully open + set_cover_position(ent5, 100) + await call_service(hass, SERVICE_TOGGLE, ent5) # Should close, not open + assert is_closing(hass, ent5) + def call_service(hass, service, ent): """Call any service on entity."""