Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions roborock/data/v1/v1_clean_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ class WashTowelModes(RoborockModeEnum):
SUPER_DEEP = ("super_deep", 8)


WATER_SLIDE_MODE_MAPPING: dict[int, WaterModes] = {
200: WaterModes.OFF,
221: WaterModes.PURE_WATER_FLOW_START,
225: WaterModes.PURE_WATER_FLOW_SMALL,
235: WaterModes.PURE_WATER_FLOW_MIDDLE,
245: WaterModes.PURE_WATER_FLOW_LARGE,
248: WaterModes.PURE_WATER_SUPER_BEGIN,
250: WaterModes.PURE_WATER_FLOW_END,
}


def get_wash_towel_modes(features: DeviceFeatures) -> list[WashTowelModes]:
"""Get the valid wash towel modes for the device"""
modes = [WashTowelModes.LIGHT, WashTowelModes.BALANCED, WashTowelModes.DEEP]
Expand Down Expand Up @@ -128,17 +139,9 @@ def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]

def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
"""Get the valid water modes for the device - also known as 'water flow' or 'water level'"""
# If the device supports water slide mode, it uses a completely different set of modes. Technically, it can even
# support values in between. But for now we will just support the main values.
# Water slide mode supports a separate set of water flow codes.
if features.is_water_slide_mode_supported:
return [
WaterModes.PURE_WATER_FLOW_START,
WaterModes.PURE_WATER_FLOW_SMALL,
WaterModes.PURE_WATER_FLOW_MIDDLE,
WaterModes.PURE_WATER_FLOW_LARGE,
WaterModes.PURE_WATER_SUPER_BEGIN,
WaterModes.PURE_WATER_FLOW_END,
]
return list(WATER_SLIDE_MODE_MAPPING.values())

supported_modes = [WaterModes.OFF]
if features.is_mop_shake_module_supported:
Expand All @@ -159,6 +162,18 @@ def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
return supported_modes


def get_water_mode_mapping(features: DeviceFeatures) -> dict[int, str]:
"""Get water mode mapping by supported feature set.

WaterModes contains aliases for multiple codes that share the same value
string (e.g. low can be 201 or 225). For water slide mode devices we need
explicit code mapping to preserve those slide-specific codes.
"""
if features.is_water_slide_mode_supported:
return {code: mode.value for code, mode in WATER_SLIDE_MODE_MAPPING.items()}
return {mode.code: mode.value for mode in get_water_modes(features)}


def is_mode_customized(clean_mode: VacuumModes, water_mode: WaterModes, mop_mode: CleanRoutes) -> bool:
"""Check if any of the cleaning modes are set to a custom value."""
return (
Expand Down
13 changes: 11 additions & 2 deletions roborock/devices/traits/v1/status.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
from functools import cached_property
from typing import Self

from roborock import CleanRoutes, StatusV2, VacuumModes, WaterModes, get_clean_modes, get_clean_routes, get_water_modes
from roborock import (
CleanRoutes,
StatusV2,
VacuumModes,
WaterModes,
get_clean_modes,
get_clean_routes,
get_water_mode_mapping,
get_water_modes,
)
from roborock.roborock_typing import RoborockCommand

from . import common
Expand Down Expand Up @@ -55,7 +64,7 @@ def water_mode_options(self) -> list[WaterModes]:

@cached_property
def water_mode_mapping(self) -> dict[int, str]:
return {mop.code: mop.value for mop in self.water_mode_options}
return get_water_mode_mapping(self._device_features_trait)

@cached_property
def mop_route_options(self) -> list[CleanRoutes]:
Expand Down
42 changes: 42 additions & 0 deletions tests/devices/traits/v1/test_status.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
"""Tests for the StatusTrait class."""

from typing import cast
from unittest.mock import AsyncMock

import pytest

from roborock.data import SHORT_MODEL_TO_ENUM
from roborock.data.v1 import (
RoborockStateCode,
)
from roborock.device_features import DeviceFeatures
from roborock.devices.device import RoborockDevice
from roborock.devices.traits.v1.device_features import DeviceFeaturesTrait
from roborock.devices.traits.v1.status import StatusTrait
from roborock.exceptions import RoborockException
from roborock.roborock_typing import RoborockCommand
from tests import mock_data
from tests.mock_data import STATUS


Expand Down Expand Up @@ -80,3 +85,40 @@ def test_options(status_trait: StatusTrait) -> None:
assert len(status_trait.water_mode_options) > 0
assert isinstance(status_trait.mop_route_options, list)
assert len(status_trait.mop_route_options) > 0


def test_water_slide_mode_mapping() -> None:
"""Test feature-aware water mode mapping for water slide mode devices."""
short_model = mock_data.A114_PRODUCT_DATA["model"].split(".")[-1]
features = DeviceFeatures.from_feature_flags(
new_feature_info=int(mock_data.SAROS_10R_DEVICE_DATA["featureSet"]),
new_feature_info_str=mock_data.SAROS_10R_DEVICE_DATA["newFeatureSet"],
feature_info=[],
product_nickname=SHORT_MODEL_TO_ENUM[short_model],
)
status_trait = StatusTrait(cast(DeviceFeaturesTrait, features), region="eu")

assert features.is_water_slide_mode_supported
assert status_trait.water_mode_mapping == {
200: "off",
221: "slight",
225: "low",
235: "medium",
245: "moderate",
248: "high",
250: "extreme",
}
assert [mode.value for mode in status_trait.water_mode_options] == [
"off",
"slight",
"low",
"medium",
"moderate",
"high",
"extreme",
]

status_trait.water_box_mode = 225
assert status_trait.water_mode_name == "low"
status_trait.water_box_mode = 200
assert status_trait.water_mode_name == "off"