From 6f71fae4b93469c6043c8bec05a099c85a4fb3bf Mon Sep 17 00:00:00 2001 From: Kraina68512 Date: Tue, 7 Apr 2026 22:23:11 +0300 Subject: [PATCH 1/6] Add Q10 remote control trait --- roborock/devices/traits/b01/q10/__init__.py | 5 ++ roborock/devices/traits/b01/q10/remote.py | 58 +++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 roborock/devices/traits/b01/q10/remote.py diff --git a/roborock/devices/traits/b01/q10/__init__.py b/roborock/devices/traits/b01/q10/__init__.py index 1cd89bd8..ea415ef8 100644 --- a/roborock/devices/traits/b01/q10/__init__.py +++ b/roborock/devices/traits/b01/q10/__init__.py @@ -12,6 +12,7 @@ from .command import CommandTrait from .status import StatusTrait from .vacuum import VacuumTrait +from .remote import RemoteTrait __all__ = [ "Q10PropertiesApi", @@ -32,11 +33,15 @@ class Q10PropertiesApi(Trait): vacuum: VacuumTrait """Trait for sending vacuum related commands to Q10 devices.""" + remote: RemoteTrait + """Trait for sending remote control related commands to Q10 devices.""" + def __init__(self, channel: MqttChannel) -> None: """Initialize the B01Props API.""" self._channel = channel self.command = CommandTrait(channel) self.vacuum = VacuumTrait(self.command) + self.remote = RemoteTrait(self.command) self.status = StatusTrait() self._subscribe_task: asyncio.Task[None] | None = None diff --git a/roborock/devices/traits/b01/q10/remote.py b/roborock/devices/traits/b01/q10/remote.py new file mode 100644 index 00000000..ded5b0c4 --- /dev/null +++ b/roborock/devices/traits/b01/q10/remote.py @@ -0,0 +1,58 @@ +"""Traits for Q10 B01 devices.""" + +from roborock.data.b01_q10.b01_q10_code_mappings import ( + B01_Q10_DP +) +from typing import Any + +from .command import CommandTrait + +REMOTE_COMMANDS = { + "forward": 0, + "left": 2, + "right": 3, + "stop": 4, + "exit": 5, +} + +class RemoteTrait: + """Trait for sending vacuum commands. + + This is a wrapper around the CommandTrait for sending vacuum related + commands to Q10 devices. + """ + + def __init__(self, command: CommandTrait) -> None: + """Initialize the VacuumTrait.""" + self._command = command + + async def send_common_dp(self, command: B01_Q10_DP, value: Any) -> None: + await self._command.send(B01_Q10_DP.COMMON, params={command.code: value}) + + async def send_remote(self, action: str) -> None: + await self.send_common_dp(B01_Q10_DP.REMOTE, REMOTE_COMMANDS[action]) + + async def forward(self) -> None: + """Move forward.""" + + await self.send_remote("forward") + + async def left(self) -> None: + """Turn left.""" + + await self.send_remote("left") + + async def right(self) -> None: + """Turn right.""" + + await self.send_remote("right") + + async def stop(self) -> None: + """Stop last moving command or start remote control.""" + + await self.send_remote("stop") + + async def exit(self) -> None: + """Exit remote control.""" + + await self.send_remote("exit") From dc86444d4922b9034ab7d79626a7aeea29499d4a Mon Sep 17 00:00:00 2001 From: Kraina68512 Date: Thu, 9 Apr 2026 18:53:29 +0300 Subject: [PATCH 2/6] Deleted command Ids from remote.py Added commands enum to b01_q10_code_mappings.py Created pytest file --- .../data/b01_q10/b01_q10_code_mappings.py | 8 +++ roborock/devices/traits/b01/q10/remote.py | 36 ++++--------- tests/devices/traits/b01/q10/test_remote.py | 50 +++++++++++++++++++ 3 files changed, 69 insertions(+), 25 deletions(-) create mode 100644 tests/devices/traits/b01/q10/test_remote.py diff --git a/roborock/data/b01_q10/b01_q10_code_mappings.py b/roborock/data/b01_q10/b01_q10_code_mappings.py index 8020894d..c48f5b38 100644 --- a/roborock/data/b01_q10/b01_q10_code_mappings.py +++ b/roborock/data/b01_q10/b01_q10_code_mappings.py @@ -1,4 +1,5 @@ from ..code_mappings import RoborockModeEnum +from enum import Enum class B01_Q10_DP(RoborockModeEnum): @@ -217,3 +218,10 @@ class YXDeviceDustCollectionFrequency(RoborockModeEnum): INTERVAL_30 = "interval_30", 30 INTERVAL_45 = "interval_45", 45 INTERVAL_60 = "interval_60", 60 + +class RemoteCommand(Enum): + FORWARD = 0 + LEFT = 2 + RIGHT = 3 + STOP = 4 + EXIT = 5 diff --git a/roborock/devices/traits/b01/q10/remote.py b/roborock/devices/traits/b01/q10/remote.py index ded5b0c4..cfa43c0e 100644 --- a/roborock/devices/traits/b01/q10/remote.py +++ b/roborock/devices/traits/b01/q10/remote.py @@ -1,58 +1,44 @@ """Traits for Q10 B01 devices.""" from roborock.data.b01_q10.b01_q10_code_mappings import ( - B01_Q10_DP + B01_Q10_DP, + RemoteCommand, ) from typing import Any from .command import CommandTrait -REMOTE_COMMANDS = { - "forward": 0, - "left": 2, - "right": 3, - "stop": 4, - "exit": 5, -} class RemoteTrait: """Trait for sending vacuum commands. - This is a wrapper around the CommandTrait for sending vacuum related + This is a wrapper around the CommandTrait for sending remote related commands to Q10 devices. """ def __init__(self, command: CommandTrait) -> None: - """Initialize the VacuumTrait.""" + """Initialize the RemoteTrait.""" self._command = command - async def send_common_dp(self, command: B01_Q10_DP, value: Any) -> None: - await self._command.send(B01_Q10_DP.COMMON, params={command.code: value}) - - async def send_remote(self, action: str) -> None: - await self.send_common_dp(B01_Q10_DP.REMOTE, REMOTE_COMMANDS[action]) + async def _send_remote(self, action: RemoteCommand) -> None: + await self._command.send(B01_Q10_DP.COMMON, params={B01_Q10_DP.REMOTE: action.value}) async def forward(self) -> None: """Move forward.""" - - await self.send_remote("forward") + await self._send_remote(RemoteCommand.FORWARD) async def left(self) -> None: """Turn left.""" - - await self.send_remote("left") + await self._send_remote(RemoteCommand.LEFT) async def right(self) -> None: """Turn right.""" - - await self.send_remote("right") + await self._send_remote(RemoteCommand.RIGHT) async def stop(self) -> None: """Stop last moving command or start remote control.""" - - await self.send_remote("stop") + await self._send_remote(RemoteCommand.STOP) async def exit(self) -> None: """Exit remote control.""" - - await self.send_remote("exit") + await self._send_remote(RemoteCommand.EXIT) diff --git a/tests/devices/traits/b01/q10/test_remote.py b/tests/devices/traits/b01/q10/test_remote.py new file mode 100644 index 00000000..f2cabff3 --- /dev/null +++ b/tests/devices/traits/b01/q10/test_remote.py @@ -0,0 +1,50 @@ +import json +from collections.abc import Awaitable, Callable +from typing import Any + +import pytest + +from roborock.devices.traits.b01.q10 import Q10PropertiesApi +from roborock.devices.traits.b01.q10.remote import RemoteTrait +from tests.fixtures.channel_fixtures import FakeChannel + + +@pytest.fixture(name="fake_channel") +def fake_channel_fixture() -> FakeChannel: + return FakeChannel() + + +@pytest.fixture(name="q10_api") +def q10_api_fixture(fake_channel: FakeChannel) -> Q10PropertiesApi: + return Q10PropertiesApi(fake_channel) # type: ignore[arg-type] + + +@pytest.fixture(name="remote") +def remote_fixture(q10_api: Q10PropertiesApi) -> RemoteTrait: + return q10_api.remote + + +@pytest.mark.parametrize( + ("command_fn", "expected_payload"), + [ + (lambda x: x.forward(), {"101": {"12": 0}}), + (lambda x: x.left(), {"101": {"12": 2}}), + (lambda x: x.right(), {"101": {"12": 3}}), + (lambda x: x.stop(), {"101": {"12": 4}}), + (lambda x: x.exit(), {"101": {"12": 5}}), + ], +) +async def test_remote_commands( + remote: RemoteTrait, + fake_channel: FakeChannel, + command_fn: Callable[[RemoteTrait], Awaitable[None]], + expected_payload: dict[str, Any], +) -> None: + """Test sending a remote start command.""" + await command_fn(remote) + + assert len(fake_channel.published_messages) == 1 + message = fake_channel.published_messages[0] + assert message.payload + payload_data = json.loads(message.payload.decode()) + assert payload_data == {"dps": expected_payload} From 43d8e7cbbcb9c9084b87e80a76b8a6b184606154 Mon Sep 17 00:00:00 2001 From: Kraina68512 Date: Fri, 10 Apr 2026 17:00:54 +0300 Subject: [PATCH 3/6] Updated test and deleted unneeded import --- roborock/devices/traits/b01/q10/remote.py | 1 - tests/devices/traits/b01/q10/test_remote.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/roborock/devices/traits/b01/q10/remote.py b/roborock/devices/traits/b01/q10/remote.py index cfa43c0e..4438f405 100644 --- a/roborock/devices/traits/b01/q10/remote.py +++ b/roborock/devices/traits/b01/q10/remote.py @@ -4,7 +4,6 @@ B01_Q10_DP, RemoteCommand, ) -from typing import Any from .command import CommandTrait diff --git a/tests/devices/traits/b01/q10/test_remote.py b/tests/devices/traits/b01/q10/test_remote.py index f2cabff3..f497e35d 100644 --- a/tests/devices/traits/b01/q10/test_remote.py +++ b/tests/devices/traits/b01/q10/test_remote.py @@ -27,11 +27,11 @@ def remote_fixture(q10_api: Q10PropertiesApi) -> RemoteTrait: @pytest.mark.parametrize( ("command_fn", "expected_payload"), [ - (lambda x: x.forward(), {"101": {"12": 0}}), - (lambda x: x.left(), {"101": {"12": 2}}), - (lambda x: x.right(), {"101": {"12": 3}}), - (lambda x: x.stop(), {"101": {"12": 4}}), - (lambda x: x.exit(), {"101": {"12": 5}}), + (lambda x: x.forward(), {"101": {"dpRemote": 0}}), + (lambda x: x.left(), {"101": {"dpRemote": 2}}), + (lambda x: x.right(), {"101": {"dpRemote": 3}}), + (lambda x: x.stop(), {"101": {"dpRemote": 4}}), + (lambda x: x.exit(), {"101": {"dpRemote": 5}}), ], ) async def test_remote_commands( From 2dd3cecacde60e1aef5b169724d7002ebcbdabf4 Mon Sep 17 00:00:00 2001 From: Kraina68512 Date: Fri, 10 Apr 2026 17:09:14 +0300 Subject: [PATCH 4/6] Changed some things, the test should be passed Well, i cant tell From 503051600bed4af9af829ec706e4d263137143ed Mon Sep 17 00:00:00 2001 From: Kraina68512 Date: Fri, 10 Apr 2026 17:23:02 +0300 Subject: [PATCH 5/6] Changed tests --- roborock/data/b01_q10/b01_q10_code_mappings.py | 4 +++- roborock/devices/traits/b01/q10/__init__.py | 3 +-- roborock/devices/traits/b01/q10/remote.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/roborock/data/b01_q10/b01_q10_code_mappings.py b/roborock/data/b01_q10/b01_q10_code_mappings.py index c48f5b38..33ce7f41 100644 --- a/roborock/data/b01_q10/b01_q10_code_mappings.py +++ b/roborock/data/b01_q10/b01_q10_code_mappings.py @@ -1,6 +1,7 @@ -from ..code_mappings import RoborockModeEnum from enum import Enum +from ..code_mappings import RoborockModeEnum + class B01_Q10_DP(RoborockModeEnum): CLEAN_TIME = ("dpCleanTime", 6) @@ -219,6 +220,7 @@ class YXDeviceDustCollectionFrequency(RoborockModeEnum): INTERVAL_45 = "interval_45", 45 INTERVAL_60 = "interval_60", 60 + class RemoteCommand(Enum): FORWARD = 0 LEFT = 2 diff --git a/roborock/devices/traits/b01/q10/__init__.py b/roborock/devices/traits/b01/q10/__init__.py index ea415ef8..184de2d2 100644 --- a/roborock/devices/traits/b01/q10/__init__.py +++ b/roborock/devices/traits/b01/q10/__init__.py @@ -2,7 +2,6 @@ import asyncio import logging -from typing import Any from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP from roborock.devices.rpc.b01_q10_channel import stream_decoded_responses @@ -10,9 +9,9 @@ from roborock.devices.transport.mqtt_channel import MqttChannel from .command import CommandTrait +from .remote import RemoteTrait from .status import StatusTrait from .vacuum import VacuumTrait -from .remote import RemoteTrait __all__ = [ "Q10PropertiesApi", diff --git a/roborock/devices/traits/b01/q10/remote.py b/roborock/devices/traits/b01/q10/remote.py index 4438f405..7949f78c 100644 --- a/roborock/devices/traits/b01/q10/remote.py +++ b/roborock/devices/traits/b01/q10/remote.py @@ -37,7 +37,7 @@ async def right(self) -> None: async def stop(self) -> None: """Stop last moving command or start remote control.""" await self._send_remote(RemoteCommand.STOP) - + async def exit(self) -> None: """Exit remote control.""" await self._send_remote(RemoteCommand.EXIT) From a6c1008ebb2fcb840bacc14fd0ed895bdf41ee68 Mon Sep 17 00:00:00 2001 From: Kraina68512 Date: Fri, 10 Apr 2026 17:25:37 +0300 Subject: [PATCH 6/6] Trying to patch the test