From bef12b849b218490f7e383ed8eb941b477abd854 Mon Sep 17 00:00:00 2001 From: mastameista <59114249+mastameista@users.noreply.github.com> Date: Fri, 28 Aug 2026 08:42:05 +0200 Subject: [PATCH] Fix homematicip_cloud shutter group resetting slats on close (#172278) Co-authored-by: Martin Hjelmare --- .../components/homematicip_cloud/cover.py | 25 ++++++++++++++++--- .../homematicip_cloud/test_cover.py | 23 ++++++++++++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index a3b13f6677f2..16eff4e0345b 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -399,6 +399,12 @@ class HomematicipCoverShutterGroup(HomematicipGenericEntity, CoverEntity): position = kwargs[ATTR_POSITION] # HmIP cover is closed:1 -> open:0 level = 1 - position / 100.0 + if level == HMIP_COVER_CLOSED: + # Route fully-closed position through the same slats-safe call + # as async_close_cover, otherwise slats get reset to 0 on FBL + # group members. See issue #114266. + await self.async_close_cover() + return await self._device.set_shutter_level_async(level) @override @@ -411,13 +417,26 @@ class HomematicipCoverShutterGroup(HomematicipGenericEntity, CoverEntity): @override async def async_open_cover(self, **kwargs: Any) -> None: - """Open the cover.""" + """Open the cover. + + The slats-safe call used in async_close_cover is intentionally not + mirrored here: the regression reported in issue #114266 only + affects close, and for an open cover slats at 0 (horizontal) is + the natural rest position. + """ await self._device.set_shutter_level_async(HMIP_COVER_OPEN) @override async def async_close_cover(self, **kwargs: Any) -> None: - """Close the cover.""" - await self._device.set_shutter_level_async(HMIP_COVER_CLOSED) + """Close the cover. + + Use setSlatsLevel instead of setShutterLevel so HMIP Cloud does + not reset slats to 0 (horizontal) on blind-capable group members + (e.g. HmIP-FBL with firmware >= 1.10.16). See issue #114266. + """ + await self._device.set_slats_level_async( + slatsLevel=HMIP_SLATS_CLOSED, shutterLevel=HMIP_COVER_CLOSED + ) @override async def async_stop_cover(self, **kwargs: Any) -> None: diff --git a/tests/components/homematicip_cloud/test_cover.py b/tests/components/homematicip_cloud/test_cover.py index c3263d06a4f6..64743ce1ffdf 100644 --- a/tests/components/homematicip_cloud/test_cover.py +++ b/tests/components/homematicip_cloud/test_cover.py @@ -507,8 +507,25 @@ async def test_hmip_cover_shutter_group( "cover", "close_cover", {"entity_id": entity_id}, blocking=True ) assert len(hmip_device.mock_calls) == service_call_counter + 5 - assert hmip_device.mock_calls[-1][0] == "set_shutter_level_async" - assert hmip_device.mock_calls[-1][1] == (1,) + assert hmip_device.mock_calls[-1][0] == "set_slats_level_async" + assert hmip_device.mock_calls[-1][2] == {"slatsLevel": 1, "shutterLevel": 1} + await async_manipulate_test_data(hass, hmip_device, "shutterLevel", 1) + ha_state = hass.states.get(entity_id) + assert ha_state.state == CoverState.CLOSED + assert ha_state.attributes[ATTR_CURRENT_POSITION] == 0 + + # set_cover_position with position=0 must route through the same + # slats-safe call as close_cover, otherwise the FBL slats regression + # (#114266) would still hit users moving the slider to fully closed. + await hass.services.async_call( + "cover", + "set_cover_position", + {"entity_id": entity_id, "position": "0"}, + blocking=True, + ) + assert len(hmip_device.mock_calls) == service_call_counter + 7 + assert hmip_device.mock_calls[-1][0] == "set_slats_level_async" + assert hmip_device.mock_calls[-1][2] == {"slatsLevel": 1, "shutterLevel": 1} await async_manipulate_test_data(hass, hmip_device, "shutterLevel", 1) ha_state = hass.states.get(entity_id) assert ha_state.state == CoverState.CLOSED @@ -517,7 +534,7 @@ async def test_hmip_cover_shutter_group( await hass.services.async_call( "cover", "stop_cover", {"entity_id": entity_id}, blocking=True ) - assert len(hmip_device.mock_calls) == service_call_counter + 7 + assert len(hmip_device.mock_calls) == service_call_counter + 9 assert hmip_device.mock_calls[-1][0] == "set_shutter_stop_async" assert hmip_device.mock_calls[-1][1] == ()