From 3720ca6e80e78a696a763fd5ebab3d880af0bca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 15:55:59 +0100 Subject: [PATCH 01/12] refactor(q10): use readable YXCleanType values with legacy aliases --- roborock/cli.py | 7 +++++- .../data/b01_q10/b01_q10_code_mappings.py | 22 ++++++++++++++++--- tests/devices/traits/b01/q10/test_vacuum.py | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/roborock/cli.py b/roborock/cli.py index 6eb11bf5..52ab81f5 100644 --- a/roborock/cli.py +++ b/roborock/cli.py @@ -1275,7 +1275,12 @@ async def q10_empty_dustbin(ctx: click.Context, device_id: str) -> None: @session.command() @click.option("--device_id", required=True, help="Device ID") -@click.option("--mode", required=True, type=click.Choice(["bothwork", "onlysweep", "onlymop"]), help="Clean mode") +@click.option( + "--mode", + required=True, + type=click.Choice(["vac_and_mop", "vacuum", "mop", "bothwork", "onlysweep", "onlymop"]), + help='Clean mode (preferred: "vac_and_mop", "vacuum", "mop")', +) @click.pass_context @async_command async def q10_set_clean_mode(ctx: click.Context, device_id: str, mode: str) -> None: diff --git a/roborock/data/b01_q10/b01_q10_code_mappings.py b/roborock/data/b01_q10/b01_q10_code_mappings.py index 660a35c5..329c4fc6 100644 --- a/roborock/data/b01_q10/b01_q10_code_mappings.py +++ b/roborock/data/b01_q10/b01_q10_code_mappings.py @@ -157,9 +157,25 @@ class YXRoomMaterial(RoborockModeEnum): class YXCleanType(RoborockModeEnum): UNKNOWN = "unknown", -1 - BOTH_WORK = "bothwork", 1 - ONLY_SWEEP = "onlysweep", 2 - ONLY_MOP = "onlymop", 3 + VAC_AND_MOP = "vac_and_mop", 1 + VACUUM = "vacuum", 2 + MOP = "mop", 3 + + # Legacy aliases + BOTH_WORK = VAC_AND_MOP + ONLY_SWEEP = VACUUM + ONLY_MOP = MOP + + @classmethod + def from_value(cls, value: str): + """Find enum member by string value with legacy support.""" + legacy_values = { + "bothwork": "vac_and_mop", + "onlysweep": "vacuum", + "onlymop": "mop", + } + normalized_value = legacy_values.get(value.lower(), value) + return super().from_value(normalized_value) class YXDeviceState(RoborockModeEnum): diff --git a/tests/devices/traits/b01/q10/test_vacuum.py b/tests/devices/traits/b01/q10/test_vacuum.py index 8e271c7c..499b9bbc 100644 --- a/tests/devices/traits/b01/q10/test_vacuum.py +++ b/tests/devices/traits/b01/q10/test_vacuum.py @@ -34,7 +34,7 @@ def vacuumm_fixture(q10_api: Q10PropertiesApi) -> VacuumTrait: (lambda x: x.stop_clean(), {"206": {}}), (lambda x: x.return_to_dock(), {"203": {}}), (lambda x: x.empty_dustbin(), {"203": 2}), - (lambda x: x.set_clean_mode(YXCleanType.BOTH_WORK), {"137": 1}), + (lambda x: x.set_clean_mode(YXCleanType.VAC_AND_MOP), {"137": 1}), (lambda x: x.set_fan_level(YXFanLevel.BALANCED), {"123": 2}), ], ) From 3bfadcc919b5ae414b33a4a2da7b648eaddaaa49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 16:14:50 +0100 Subject: [PATCH 02/12] fix(cli): make clean mode option case insensitive --- roborock/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roborock/cli.py b/roborock/cli.py index 52ab81f5..862e2ba2 100644 --- a/roborock/cli.py +++ b/roborock/cli.py @@ -1278,7 +1278,7 @@ async def q10_empty_dustbin(ctx: click.Context, device_id: str) -> None: @click.option( "--mode", required=True, - type=click.Choice(["vac_and_mop", "vacuum", "mop", "bothwork", "onlysweep", "onlymop"]), + type=click.Choice(["vac_and_mop", "vacuum", "mop", "bothwork", "onlysweep", "onlymop"], case_sensitive=False), help='Clean mode (preferred: "vac_and_mop", "vacuum", "mop")', ) @click.pass_context From 17dd1a9a0ef3fba1dd8b31761a8682b76162f33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 16:19:32 +0100 Subject: [PATCH 03/12] refactor(YXCleanType): move legacy values to a separate dictionary and update from_value method --- roborock/data/b01_q10/b01_q10_code_mappings.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/roborock/data/b01_q10/b01_q10_code_mappings.py b/roborock/data/b01_q10/b01_q10_code_mappings.py index 329c4fc6..88e6dea6 100644 --- a/roborock/data/b01_q10/b01_q10_code_mappings.py +++ b/roborock/data/b01_q10/b01_q10_code_mappings.py @@ -1,3 +1,5 @@ +from typing import Self + from ..code_mappings import RoborockModeEnum @@ -155,6 +157,13 @@ class YXRoomMaterial(RoborockModeEnum): OTHER = "other", 255 +_YX_CLEAN_TYPE_LEGACY_VALUES: dict[str, str] = { + "bothwork": "vac_and_mop", + "onlysweep": "vacuum", + "onlymop": "mop", +} + + class YXCleanType(RoborockModeEnum): UNKNOWN = "unknown", -1 VAC_AND_MOP = "vac_and_mop", 1 @@ -167,14 +176,9 @@ class YXCleanType(RoborockModeEnum): ONLY_MOP = MOP @classmethod - def from_value(cls, value: str): + def from_value(cls, value: str) -> Self: """Find enum member by string value with legacy support.""" - legacy_values = { - "bothwork": "vac_and_mop", - "onlysweep": "vacuum", - "onlymop": "mop", - } - normalized_value = legacy_values.get(value.lower(), value) + normalized_value = _YX_CLEAN_TYPE_LEGACY_VALUES.get(value.lower(), value) return super().from_value(normalized_value) From 7cceb1fe7411639c5a3eb75fe427aa07fe41689a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 16:21:12 +0100 Subject: [PATCH 04/12] test(YXCleanType): add tests for legacy clean type string aliases --- tests/data/test_code_mappings.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/data/test_code_mappings.py b/tests/data/test_code_mappings.py index 3c25f105..155162ac 100644 --- a/tests/data/test_code_mappings.py +++ b/tests/data/test_code_mappings.py @@ -5,7 +5,7 @@ import pytest from roborock import HomeDataProduct, RoborockCategory -from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP +from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP, YXCleanType def test_from_code() -> None: @@ -52,6 +52,22 @@ def test_invalid_from_value() -> None: B01_Q10_DP.from_value("invalid_value") +@pytest.mark.parametrize( + ("raw_value", "expected"), + [ + ("bothwork", YXCleanType.VAC_AND_MOP), + ("onlysweep", YXCleanType.VACUUM), + ("onlymop", YXCleanType.MOP), + ("BothWork", YXCleanType.VAC_AND_MOP), + ("ONLYSWEEP", YXCleanType.VACUUM), + ("OnlyMop", YXCleanType.MOP), + ], +) +def test_yxcleantype_from_value_legacy_aliases(raw_value: str, expected: YXCleanType) -> None: + """Ensure legacy clean type strings resolve to canonical enum members.""" + assert YXCleanType.from_value(raw_value) is expected + + @pytest.mark.parametrize( "input, expected", [ From de68ff2879ef4a234dabc991be1ee268907c1be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 16:32:22 +0100 Subject: [PATCH 05/12] refactor(YXCleanType): simplify clean type definitions and remove legacy alias support --- .../data/b01_q10/b01_q10_code_mappings.py | 26 +++---------------- tests/data/test_code_mappings.py | 18 +------------ 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/roborock/data/b01_q10/b01_q10_code_mappings.py b/roborock/data/b01_q10/b01_q10_code_mappings.py index 88e6dea6..a5d433e5 100644 --- a/roborock/data/b01_q10/b01_q10_code_mappings.py +++ b/roborock/data/b01_q10/b01_q10_code_mappings.py @@ -1,5 +1,3 @@ -from typing import Self - from ..code_mappings import RoborockModeEnum @@ -157,29 +155,11 @@ class YXRoomMaterial(RoborockModeEnum): OTHER = "other", 255 -_YX_CLEAN_TYPE_LEGACY_VALUES: dict[str, str] = { - "bothwork": "vac_and_mop", - "onlysweep": "vacuum", - "onlymop": "mop", -} - - class YXCleanType(RoborockModeEnum): UNKNOWN = "unknown", -1 - VAC_AND_MOP = "vac_and_mop", 1 - VACUUM = "vacuum", 2 - MOP = "mop", 3 - - # Legacy aliases - BOTH_WORK = VAC_AND_MOP - ONLY_SWEEP = VACUUM - ONLY_MOP = MOP - - @classmethod - def from_value(cls, value: str) -> Self: - """Find enum member by string value with legacy support.""" - normalized_value = _YX_CLEAN_TYPE_LEGACY_VALUES.get(value.lower(), value) - return super().from_value(normalized_value) + VAC_AND_MOP = "vac_and_mop", 1 # bothwork + VACUUM = "vacuum", 2 # onlysweep + MOP = "mop", 3 # onlymop class YXDeviceState(RoborockModeEnum): diff --git a/tests/data/test_code_mappings.py b/tests/data/test_code_mappings.py index 155162ac..3c25f105 100644 --- a/tests/data/test_code_mappings.py +++ b/tests/data/test_code_mappings.py @@ -5,7 +5,7 @@ import pytest from roborock import HomeDataProduct, RoborockCategory -from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP, YXCleanType +from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP def test_from_code() -> None: @@ -52,22 +52,6 @@ def test_invalid_from_value() -> None: B01_Q10_DP.from_value("invalid_value") -@pytest.mark.parametrize( - ("raw_value", "expected"), - [ - ("bothwork", YXCleanType.VAC_AND_MOP), - ("onlysweep", YXCleanType.VACUUM), - ("onlymop", YXCleanType.MOP), - ("BothWork", YXCleanType.VAC_AND_MOP), - ("ONLYSWEEP", YXCleanType.VACUUM), - ("OnlyMop", YXCleanType.MOP), - ], -) -def test_yxcleantype_from_value_legacy_aliases(raw_value: str, expected: YXCleanType) -> None: - """Ensure legacy clean type strings resolve to canonical enum members.""" - assert YXCleanType.from_value(raw_value) is expected - - @pytest.mark.parametrize( "input, expected", [ From 93072e27e27ce049ded3636c616572a908ffd922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 16:51:24 +0100 Subject: [PATCH 06/12] test(YXCleanType): add tests for readable public values and compatibility with aliases --- tests/data/test_code_mappings.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/data/test_code_mappings.py b/tests/data/test_code_mappings.py index 3c25f105..dd9450d1 100644 --- a/tests/data/test_code_mappings.py +++ b/tests/data/test_code_mappings.py @@ -5,7 +5,7 @@ import pytest from roborock import HomeDataProduct, RoborockCategory -from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP +from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP, YXCleanType def test_from_code() -> None: @@ -89,3 +89,25 @@ def test_homedata_product_unknown_category(): product = HomeDataProduct.from_dict(data) assert product.id == "unknown_cat_id" assert product.category == RoborockCategory.UNKNOWN + + +def test_yx_clean_type_legacy_values_stable() -> None: + """Test YXCleanType exposes readable public values.""" + assert YXCleanType.VAC_AND_MOP.value == "vac_and_mop" + assert YXCleanType.VACUUM.value == "vacuum" + assert YXCleanType.MOP.value == "mop" + + +@pytest.mark.parametrize( + ("input_value", "expected"), + [ + ("vac_and_mop", YXCleanType.VAC_AND_MOP), + ("vacuum", YXCleanType.VACUUM), + ("mop", YXCleanType.MOP), + ], +) +def test_yx_clean_type_from_value_compat_aliases( + input_value: str, expected: YXCleanType +) -> None: + """Test YXCleanType accepts readable values.""" + assert YXCleanType.from_value(input_value) is expected From b4fa3a0a83cd34aec7346f606ddfbb505e3c1186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 16:53:23 +0100 Subject: [PATCH 07/12] test(YXCleanType): add compatibility test for readable values in from_value method --- tests/data/test_code_mappings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/data/test_code_mappings.py b/tests/data/test_code_mappings.py index dd9450d1..1ccf6abc 100644 --- a/tests/data/test_code_mappings.py +++ b/tests/data/test_code_mappings.py @@ -106,8 +106,6 @@ def test_yx_clean_type_legacy_values_stable() -> None: ("mop", YXCleanType.MOP), ], ) -def test_yx_clean_type_from_value_compat_aliases( - input_value: str, expected: YXCleanType -) -> None: +def test_yx_clean_type_from_value_compat_aliases(input_value: str, expected: YXCleanType) -> None: """Test YXCleanType accepts readable values.""" assert YXCleanType.from_value(input_value) is expected From 7e1a216a217b3630fa6a78d3e5998e090e4d77cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 17:38:34 +0100 Subject: [PATCH 08/12] test(q10): update clean type code mapping tests Co-authored-by: Allen Porter --- tests/data/test_code_mappings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/data/test_code_mappings.py b/tests/data/test_code_mappings.py index 1ccf6abc..350b4cca 100644 --- a/tests/data/test_code_mappings.py +++ b/tests/data/test_code_mappings.py @@ -109,3 +109,4 @@ def test_yx_clean_type_legacy_values_stable() -> None: def test_yx_clean_type_from_value_compat_aliases(input_value: str, expected: YXCleanType) -> None: """Test YXCleanType accepts readable values.""" assert YXCleanType.from_value(input_value) is expected + assert expected.value == input_value From 4491a1611e90005c0b756699b232d51e5f4f10c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 18:21:15 +0100 Subject: [PATCH 09/12] fix(q10): restrict clean mode cli choices --- roborock/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roborock/cli.py b/roborock/cli.py index 862e2ba2..b2b208ff 100644 --- a/roborock/cli.py +++ b/roborock/cli.py @@ -1278,7 +1278,7 @@ async def q10_empty_dustbin(ctx: click.Context, device_id: str) -> None: @click.option( "--mode", required=True, - type=click.Choice(["vac_and_mop", "vacuum", "mop", "bothwork", "onlysweep", "onlymop"], case_sensitive=False), + type=click.Choice(["vac_and_mop", "vacuum", "mop"], case_sensitive=False), help='Clean mode (preferred: "vac_and_mop", "vacuum", "mop")', ) @click.pass_context From aad3df99918e45ca9ea82e724eb2152f18343316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 18:37:20 +0100 Subject: [PATCH 10/12] refactor(YXCleanType): remove legacy test for readable public values --- tests/data/test_code_mappings.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/data/test_code_mappings.py b/tests/data/test_code_mappings.py index 350b4cca..50643c05 100644 --- a/tests/data/test_code_mappings.py +++ b/tests/data/test_code_mappings.py @@ -91,13 +91,6 @@ def test_homedata_product_unknown_category(): assert product.category == RoborockCategory.UNKNOWN -def test_yx_clean_type_legacy_values_stable() -> None: - """Test YXCleanType exposes readable public values.""" - assert YXCleanType.VAC_AND_MOP.value == "vac_and_mop" - assert YXCleanType.VACUUM.value == "vacuum" - assert YXCleanType.MOP.value == "mop" - - @pytest.mark.parametrize( ("input_value", "expected"), [ From 22db4cbc82f255f23e43dd394003908248bd5974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 19:06:32 +0100 Subject: [PATCH 11/12] test(YXCleanType): update test for readable values to use canonical names --- tests/data/test_code_mappings.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/data/test_code_mappings.py b/tests/data/test_code_mappings.py index 50643c05..b11a8449 100644 --- a/tests/data/test_code_mappings.py +++ b/tests/data/test_code_mappings.py @@ -92,14 +92,16 @@ def test_homedata_product_unknown_category(): @pytest.mark.parametrize( - ("input_value", "expected"), + ("readable_value", "expected_clean_type"), [ ("vac_and_mop", YXCleanType.VAC_AND_MOP), ("vacuum", YXCleanType.VACUUM), ("mop", YXCleanType.MOP), ], ) -def test_yx_clean_type_from_value_compat_aliases(input_value: str, expected: YXCleanType) -> None: - """Test YXCleanType accepts readable values.""" - assert YXCleanType.from_value(input_value) is expected - assert expected.value == input_value +def test_yx_clean_type_from_value_readable_values( + readable_value: str, expected_clean_type: YXCleanType +) -> None: + """Test YXCleanType accepts canonical readable values.""" + assert YXCleanType.from_value(readable_value) is expected_clean_type + assert expected_clean_type.value == readable_value From 0ba26723cbfee77c6ea8a83617a95e226166a65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Sun, 22 Mar 2026 19:10:35 +0100 Subject: [PATCH 12/12] test(YXCleanType): simplify test for readable values in from_value method --- tests/data/test_code_mappings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/data/test_code_mappings.py b/tests/data/test_code_mappings.py index b11a8449..875d6c8c 100644 --- a/tests/data/test_code_mappings.py +++ b/tests/data/test_code_mappings.py @@ -99,9 +99,7 @@ def test_homedata_product_unknown_category(): ("mop", YXCleanType.MOP), ], ) -def test_yx_clean_type_from_value_readable_values( - readable_value: str, expected_clean_type: YXCleanType -) -> None: +def test_yx_clean_type_from_value_readable_values(readable_value: str, expected_clean_type: YXCleanType) -> None: """Test YXCleanType accepts canonical readable values.""" assert YXCleanType.from_value(readable_value) is expected_clean_type assert expected_clean_type.value == readable_value