diff --git a/roborock/data/v1/v1_clean_modes.py b/roborock/data/v1/v1_clean_modes.py index 0b1e6f27..f4b12c08 100644 --- a/roborock/data/v1/v1_clean_modes.py +++ b/roborock/data/v1/v1_clean_modes.py @@ -57,9 +57,10 @@ class WaterModes(RoborockModeEnum): SMART_MODE = ("smart_mode", 209) PURE_WATER_FLOW_START = ("slight", 221) PURE_WATER_FLOW_SMALL = ("low", 225) + PURE_WATER_FLOW_GENTLE = ("gentle", 230) PURE_WATER_FLOW_MIDDLE = ("medium", 235) - PURE_WATER_FLOW_LARGE = ("moderate", 245) - PURE_WATER_SUPER_BEGIN = ("high", 248) + PURE_WATER_FLOW_LARGE = ("moderate", 240) + PURE_WATER_SUPER_BEGIN = ("high", 245) PURE_WATER_FLOW_END = ("extreme", 250) @@ -91,12 +92,25 @@ class CleaningMode(StrEnum): 200: WaterModes.OFF, 221: WaterModes.PURE_WATER_FLOW_START, 225: WaterModes.PURE_WATER_FLOW_SMALL, + 230: WaterModes.PURE_WATER_FLOW_GENTLE, 235: WaterModes.PURE_WATER_FLOW_MIDDLE, - 245: WaterModes.PURE_WATER_FLOW_LARGE, - 248: WaterModes.PURE_WATER_SUPER_BEGIN, + 240: WaterModes.PURE_WATER_FLOW_LARGE, + 245: WaterModes.PURE_WATER_SUPER_BEGIN, 250: WaterModes.PURE_WATER_FLOW_END, } +# Water slide devices report the raw slider position (1-30) as 220 + position. +# The app groups positions into named ranges, keyed here by their last position. +_WATER_SLIDE_RANGES: tuple[tuple[int, WaterModes], ...] = ( + (3, WaterModes.PURE_WATER_FLOW_START), + (7, WaterModes.PURE_WATER_FLOW_SMALL), + (12, WaterModes.PURE_WATER_FLOW_GENTLE), + (17, WaterModes.PURE_WATER_FLOW_MIDDLE), + (22, WaterModes.PURE_WATER_FLOW_LARGE), + (27, WaterModes.PURE_WATER_SUPER_BEGIN), + (30, WaterModes.PURE_WATER_FLOW_END), +) + ModeEnumT = TypeVar("ModeEnumT", bound=RoborockModeEnum) @@ -183,6 +197,19 @@ def get_water_modes(features: DeviceFeatures) -> list[WaterModes]: return supported_modes +def get_water_slide_mode_name(code: int) -> str | None: + """Get the app's range name for a water slide code, or None if out of range.""" + if code == 200: + return WaterModes.OFF.value + position = code - 220 + if position < 1: + return None + for last_position, mode in _WATER_SLIDE_RANGES: + if position <= last_position: + return mode.value + return None + + def get_water_mode_mapping(features: DeviceFeatures) -> dict[int, str]: """Get water mode mapping by supported feature set. diff --git a/roborock/devices/traits/v1/status.py b/roborock/devices/traits/v1/status.py index fa83f603..0ba1fb22 100644 --- a/roborock/devices/traits/v1/status.py +++ b/roborock/devices/traits/v1/status.py @@ -15,6 +15,7 @@ get_current_cleaning_mode, get_water_mode_mapping, get_water_modes, + get_water_slide_mode_name, resolve_cleaning_mode, ) from roborock.devices.traits.common import DpsDataConverter, TraitUpdateListener @@ -103,6 +104,8 @@ def fan_speed_name(self) -> str | None: def water_mode_name(self) -> str | None: if self.water_box_mode is None: return None + if self._device_features_trait.is_water_slide_mode_supported: + return get_water_slide_mode_name(self.water_box_mode) return self.water_mode_mapping.get(self.water_box_mode) @property diff --git a/tests/devices/traits/v1/test_status.py b/tests/devices/traits/v1/test_status.py index 32c82524..30d1fb72 100644 --- a/tests/devices/traits/v1/test_status.py +++ b/tests/devices/traits/v1/test_status.py @@ -586,15 +586,17 @@ def test_water_slide_mode_mapping() -> None: 200: "off", 221: "slight", 225: "low", + 230: "gentle", 235: "medium", - 245: "moderate", - 248: "high", + 240: "moderate", + 245: "high", 250: "extreme", } assert [mode.value for mode in status_trait.water_mode_options] == [ "off", "slight", "low", + "gentle", "medium", "moderate", "high", @@ -607,6 +609,38 @@ def test_water_slide_mode_mapping() -> None: assert status_trait.water_mode_name == "off" +@pytest.mark.parametrize( + ("water_box_mode", "expected_name"), + [ + (200, "off"), + (221, "slight"), + (223, "slight"), + (224, "low"), + (227, "low"), + (228, "gentle"), + (232, "gentle"), + (233, "medium"), + (236, "medium"), + (237, "medium"), + (238, "moderate"), + (242, "moderate"), + (243, "high"), + (247, "high"), + (248, "extreme"), + (250, "extreme"), + (220, None), + (251, None), + (999, None), + ], +) +def test_water_slide_mode_name_covers_slider_positions(water_box_mode: int, expected_name: str | None) -> None: + """Every slider position resolves to the label of its range in the Roborock app.""" + status_trait = _create_cleaning_mode_status_trait(is_water_slide_mode_supported=True) + status_trait.water_box_mode = water_box_mode + + assert status_trait.water_mode_name == expected_name + + def test_update_from_dps(status_trait: StatusTrait) -> None: """Test updating status from data protocol push message.""" assert status_trait.battery is None