diff --git a/roborock/devices/traits/b01/q7/__init__.py b/roborock/devices/traits/b01/q7/__init__.py index 13de90fa..a48dc344 100644 --- a/roborock/devices/traits/b01/q7/__init__.py +++ b/roborock/devices/traits/b01/q7/__init__.py @@ -116,6 +116,29 @@ async def set_child_lock(self, enabled: bool) -> None: """Enable or disable the child lock.""" await self.set_prop(RoborockB01Props.CHILD_LOCK, int(enabled)) + async def set_do_not_disturb(self, enabled: bool, begin_time: int, end_time: int) -> None: + """Configure do-not-disturb. + + The device expects all three values together via ``service.set_quiet_time`` + (individual ``prop.set`` calls are ignored). ``begin_time``/``end_time`` are + minutes since midnight and must be in the range 0-1439 (inclusive). + + Ranges that cross midnight are supported by passing a ``begin_time`` that is + greater than ``end_time`` (e.g. 22:00-07:00 is ``begin_time=1320``, + ``end_time=420``). + """ + for name, value in (("begin_time", begin_time), ("end_time", end_time)): + if not 0 <= value <= 1439: + raise ValueError(f"{name} must be between 0 and 1439 minutes since midnight, got {value}") + await self.send( + RoborockB01Q7Methods.SET_QUIET_TIME, + { + "is_open": int(enabled), + "quiet_begin_time": begin_time, + "quiet_end_time": end_time, + }, + ) + async def start_clean(self) -> None: """Start cleaning.""" await self.send( diff --git a/tests/devices/traits/b01/q7/test_init.py b/tests/devices/traits/b01/q7/test_init.py index 673213cc..0c04a261 100644 --- a/tests/devices/traits/b01/q7/test_init.py +++ b/tests/devices/traits/b01/q7/test_init.py @@ -192,6 +192,45 @@ async def test_q7_api_set_child_lock( assert payload_data["dps"]["10000"]["params"] == {RoborockB01Props.CHILD_LOCK: expected_code} +@pytest.mark.parametrize("enabled, expected_is_open", [(True, 1), (False, 0)]) +async def test_q7_api_set_do_not_disturb( + enabled: bool, + expected_is_open: int, + q7_api: Q7PropertiesApi, + fake_channel: FakeChannel, + message_builder: B01MessageBuilder, +): + """Test do-not-disturb is set as a whole via service.set_quiet_time.""" + fake_channel.response_queue.append(message_builder.build({"result": "ok"})) + await q7_api.set_do_not_disturb(enabled, 1200, 420) + + message = fake_channel.published_messages[0] + payload_data = json.loads(unpad(message.payload, AES.block_size)) + assert payload_data["dps"]["10000"]["method"] == "service.set_quiet_time" + assert payload_data["dps"]["10000"]["params"] == { + "is_open": expected_is_open, + "quiet_begin_time": 1200, + "quiet_end_time": 420, + } + + +@pytest.mark.parametrize( + ("begin_time", "end_time"), + [(-1, 420), (1440, 420), (1200, -1), (1200, 1440)], +) +async def test_q7_api_set_do_not_disturb_invalid_time( + begin_time: int, + end_time: int, + q7_api: Q7PropertiesApi, + fake_channel: FakeChannel, +): + """Test out-of-range times raise ValueError and nothing is sent.""" + with pytest.raises(ValueError, match="minutes since midnight"): + await q7_api.set_do_not_disturb(True, begin_time, end_time) + + assert len(fake_channel.published_messages) == 0 + + @pytest.mark.parametrize( ("mode", "expected_code"), [