From aed73e9759b44a94d2e1747b0a253e489a50584b Mon Sep 17 00:00:00 2001 From: Samir Jafferali Date: Wed, 4 Mar 2026 22:52:40 -0800 Subject: [PATCH] Add quietdrift (quiet mode) support for roller shades The open(), close(), and set_position() methods now accept a mode parameter (0 = performance, 1 = quiet) that controls motor speed. Previously open() and close() ignored the mode parameter and always sent hardcoded performance-mode commands. --- switchbot/devices/roller_shade.py | 18 +++++++++------ tests/test_roller_shade.py | 38 +++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/switchbot/devices/roller_shade.py b/switchbot/devices/roller_shade.py index 879213cf..d356a593 100644 --- a/switchbot/devices/roller_shade.py +++ b/switchbot/devices/roller_shade.py @@ -14,11 +14,11 @@ OPEN_KEYS = [ f"{REQ_HEADER}{ROLLERSHADE_COMMAND}01{CONTROL_SOURCE}0100", - f"{REQ_HEADER}{ROLLERSHADE_COMMAND}05{CONTROL_SOURCE}0000", + f"{REQ_HEADER}{ROLLERSHADE_COMMAND}05{CONTROL_SOURCE}", # +mode + "00" ] CLOSE_KEYS = [ f"{REQ_HEADER}{ROLLERSHADE_COMMAND}01{CONTROL_SOURCE}0164", - f"{REQ_HEADER}{ROLLERSHADE_COMMAND}05{CONTROL_SOURCE}0064", + f"{REQ_HEADER}{ROLLERSHADE_COMMAND}05{CONTROL_SOURCE}", # +mode + "64" ] POSITION_KEYS = [ f"{REQ_HEADER}{ROLLERSHADE_COMMAND}01{CONTROL_SOURCE}01", @@ -50,17 +50,21 @@ def _set_parsed_data( @update_after_operation async def open(self, mode: int = 0) -> bool: - """Send open command. 0 - performance mode, 1 - unfelt mode.""" + """Send open command. 0 - performance mode, 1 - quiet mode.""" self._is_opening = True self._is_closing = False - return await self._send_multiple_commands(OPEN_KEYS) + return await self._send_multiple_commands( + [OPEN_KEYS[0], f"{OPEN_KEYS[1]}{mode:02X}00"] + ) @update_after_operation - async def close(self, speed: int = 0) -> bool: - """Send close command. 0 - performance mode, 1 - unfelt mode.""" + async def close(self, mode: int = 0) -> bool: + """Send close command. 0 - performance mode, 1 - quiet mode.""" self._is_closing = True self._is_opening = False - return await self._send_multiple_commands(CLOSE_KEYS) + return await self._send_multiple_commands( + [CLOSE_KEYS[0], f"{CLOSE_KEYS[1]}{mode:02X}64"] + ) @update_after_operation async def stop(self) -> bool: diff --git a/tests/test_roller_shade.py b/tests/test_roller_shade.py index 7aafb584..ef059118 100644 --- a/tests/test_roller_shade.py +++ b/tests/test_roller_shade.py @@ -58,7 +58,18 @@ async def test_open(): assert roller_shade_device.is_opening() is True assert roller_shade_device.is_closing() is False roller_shade_device._send_multiple_commands.assert_awaited_once_with( - roller_shade.OPEN_KEYS + [roller_shade.OPEN_KEYS[0], f"{roller_shade.OPEN_KEYS[1]}0000"] + ) + + +@pytest.mark.asyncio +async def test_open_quietdrift(): + roller_shade_device = create_device_for_command_testing() + await roller_shade_device.open(mode=1) + assert roller_shade_device.is_opening() is True + assert roller_shade_device.is_closing() is False + roller_shade_device._send_multiple_commands.assert_awaited_once_with( + [roller_shade.OPEN_KEYS[0], f"{roller_shade.OPEN_KEYS[1]}0100"] ) @@ -69,7 +80,18 @@ async def test_close(): assert roller_shade_device.is_opening() is False assert roller_shade_device.is_closing() is True roller_shade_device._send_multiple_commands.assert_awaited_once_with( - roller_shade.CLOSE_KEYS + [roller_shade.CLOSE_KEYS[0], f"{roller_shade.CLOSE_KEYS[1]}0064"] + ) + + +@pytest.mark.asyncio +async def test_close_quietdrift(): + roller_shade_device = create_device_for_command_testing() + await roller_shade_device.close(mode=1) + assert roller_shade_device.is_opening() is False + assert roller_shade_device.is_closing() is True + roller_shade_device._send_multiple_commands.assert_awaited_once_with( + [roller_shade.CLOSE_KEYS[0], f"{roller_shade.CLOSE_KEYS[1]}0164"] ) @@ -204,6 +226,18 @@ async def test_set_position_closing(): curtain_device._send_multiple_commands.assert_awaited_once() +@pytest.mark.asyncio +async def test_set_position_quietdrift(): + curtain_device = create_device_for_command_testing(reverse_mode=True) + await curtain_device.set_position(50, mode=1) + curtain_device._send_multiple_commands.assert_awaited_once_with( + [ + f"{roller_shade.POSITION_KEYS[0]}32", + f"{roller_shade.POSITION_KEYS[1]}0132", + ] + ) + + def test_get_position(): curtain_device = create_device_for_command_testing() assert curtain_device.get_position() == 50