roborock.data.v1.v1_clean_modes

  1from __future__ import annotations
  2
  3import typing
  4from enum import StrEnum
  5from typing import TypeVar
  6
  7from ...exceptions import RoborockUnsupportedFeature
  8from ..code_mappings import RoborockModeEnum
  9
 10if typing.TYPE_CHECKING:
 11    from roborock.device_features import DeviceFeatures
 12
 13
 14class VacuumModes(RoborockModeEnum):
 15    GENTLE = ("gentle", 105)
 16    OFF = ("off", 105)
 17    QUIET = ("quiet", 101)
 18    BALANCED = ("balanced", 102)
 19    TURBO = ("turbo", 103)
 20    MAX = ("max", 104)
 21    MAX_PLUS = ("max_plus", 108)
 22    OFF_RAISE_MAIN_BRUSH = ("off_raise_main_brush", 109)
 23    CUSTOMIZED = ("custom", 106)
 24    SMART_MODE = ("smart_mode", 110)
 25
 26
 27class CleanRoutes(RoborockModeEnum):
 28    STANDARD = ("standard", 300)
 29    DEEP = ("deep", 301)
 30    DEEP_PLUS = ("deep_plus", 303)
 31    FAST = ("fast", 304)
 32    DEEP_PLUS_CN = ("deep_plus", 305)
 33    SMART_MODE = ("smart_mode", 306)
 34    CUSTOMIZED = ("custom", 302)
 35
 36
 37class VacuumModesOld(RoborockModeEnum):
 38    QUIET = ("quiet", 38)
 39    BALANCED = ("balanced", 60)
 40    TURBO = ("turbo", 75)
 41    MAX = ("max", 100)
 42
 43
 44class WaterModes(RoborockModeEnum):
 45    OFF = ("off", 200)
 46    LOW = ("low", 201)
 47    MILD = ("mild", 201)
 48    MEDIUM = ("medium", 202)
 49    STANDARD = ("standard", 202)
 50    HIGH = ("high", 203)
 51    INTENSE = ("intense", 203)
 52    MIN = ("min", 205)
 53    MAX = ("max", 206)
 54    CUSTOMIZED = ("custom", 204)
 55    CUSTOM = ("custom_water_flow", 207)
 56    EXTREME = ("extreme", 208)
 57    SMART_MODE = ("smart_mode", 209)
 58    PURE_WATER_FLOW_START = ("slight", 221)
 59    PURE_WATER_FLOW_SMALL = ("low", 225)
 60    PURE_WATER_FLOW_MIDDLE = ("medium", 235)
 61    PURE_WATER_FLOW_LARGE = ("moderate", 245)
 62    PURE_WATER_SUPER_BEGIN = ("high", 248)
 63    PURE_WATER_FLOW_END = ("extreme", 250)
 64
 65
 66class WashTowelModes(RoborockModeEnum):
 67    SMART = ("smart", 10)
 68    LIGHT = ("light", 0)
 69    BALANCED = ("balanced", 1)
 70    DEEP = ("deep", 2)
 71    SUPER_DEEP = ("super_deep", 8)
 72
 73
 74class CleaningMode(StrEnum):
 75    """High-level cleaning intent derived from the lower-level motor settings.
 76
 77    Prefer this abstraction when you want to present or switch between the
 78    user-facing cleaning behaviors exposed by the app. The lower-level
 79    `VacuumModes`, `WaterModes`, and `CleanRoutes` enums are still useful for
 80    fine-grained selection.
 81    """
 82
 83    VACUUM = "vacuum"
 84    VAC_AND_MOP = "vac_and_mop"
 85    MOP = "mop"
 86    CUSTOM = "custom"
 87    SMART_MODE = "smart_mode"
 88
 89
 90WATER_SLIDE_MODE_MAPPING: dict[int, WaterModes] = {
 91    200: WaterModes.OFF,
 92    221: WaterModes.PURE_WATER_FLOW_START,
 93    225: WaterModes.PURE_WATER_FLOW_SMALL,
 94    235: WaterModes.PURE_WATER_FLOW_MIDDLE,
 95    245: WaterModes.PURE_WATER_FLOW_LARGE,
 96    248: WaterModes.PURE_WATER_SUPER_BEGIN,
 97    250: WaterModes.PURE_WATER_FLOW_END,
 98}
 99
100ModeEnumT = TypeVar("ModeEnumT", bound=RoborockModeEnum)
101
102
103def get_wash_towel_modes(features: DeviceFeatures) -> list[WashTowelModes]:
104    """Get the valid wash towel modes for the device"""
105    modes = [WashTowelModes.LIGHT, WashTowelModes.BALANCED, WashTowelModes.DEEP]
106    if features.is_super_deep_wash_supported and not features.is_dirty_replenish_clean_supported:
107        modes.append(WashTowelModes.SUPER_DEEP)
108    elif features.is_dirty_replenish_clean_supported:
109        modes.append(WashTowelModes.SMART)
110    return modes
111
112
113def get_clean_modes(features: DeviceFeatures) -> list[VacuumModes]:
114    """Get the valid clean modes for the device - also known as 'fan power' or 'suction mode'"""
115    modes = [VacuumModes.QUIET, VacuumModes.BALANCED, VacuumModes.TURBO, VacuumModes.MAX]
116    if features.is_max_plus_mode_supported or features.is_none_pure_clean_mop_with_max_plus:
117        # If the vacuum has max plus mode supported
118        modes.append(VacuumModes.MAX_PLUS)
119    if features.is_pure_clean_mop_supported:
120        # If the vacuum is capable of 'pure mop clean' aka no vacuum
121        if features.is_support_main_brush_up_down_supported:
122            modes.append(VacuumModes.OFF_RAISE_MAIN_BRUSH)
123        else:
124            modes.append(VacuumModes.OFF)
125    else:
126        # If not, we can add gentle
127        modes.append(VacuumModes.GENTLE)
128    if features.is_smart_clean_mode_set_supported:
129        modes.append(VacuumModes.SMART_MODE)
130    if features.is_customized_clean_supported:
131        modes.append(VacuumModes.CUSTOMIZED)
132    return modes
133
134
135def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]:
136    """The routes that the vacuum will take while mopping"""
137    if features.is_none_pure_clean_mop_with_max_plus:
138        return [CleanRoutes.FAST, CleanRoutes.STANDARD]
139    supported = [CleanRoutes.STANDARD, CleanRoutes.DEEP]
140    if features.is_careful_slow_mop_supported:
141        if not (
142            features.is_corner_clean_mode_supported
143            and features.is_clean_route_deep_slow_plus_supported
144            and region == "cn"
145        ):
146            # for some reason there is a china specific deep plus mode
147            supported.append(CleanRoutes.DEEP_PLUS_CN)
148        else:
149            supported.append(CleanRoutes.DEEP_PLUS)
150
151    if features.is_clean_route_fast_mode_supported:
152        supported.append(CleanRoutes.FAST)
153    if features.is_smart_clean_mode_set_supported:
154        supported.append(CleanRoutes.SMART_MODE)
155    if features.is_customized_clean_supported:
156        supported.append(CleanRoutes.CUSTOMIZED)
157
158    return supported
159
160
161def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
162    """Get the valid water modes for the device - also known as 'water flow' or 'water level'"""
163    # Water slide mode supports a separate set of water flow codes.
164    if features.is_water_slide_mode_supported:
165        return list(WATER_SLIDE_MODE_MAPPING.values())
166
167    supported_modes = [WaterModes.OFF]
168    if features.is_mop_shake_module_supported:
169        # For mops that have the vibrating mop pad, they do mild standard intense
170        supported_modes.extend([WaterModes.MILD, WaterModes.STANDARD, WaterModes.INTENSE])
171    else:
172        supported_modes.extend([WaterModes.LOW, WaterModes.MEDIUM, WaterModes.HIGH])
173    if features.is_custom_water_box_distance_supported:
174        # This is for devices that allow you to set a custom water flow from 0-100
175        supported_modes.append(WaterModes.CUSTOM)
176    if features.is_mop_shake_module_supported and features.is_mop_shake_water_max_supported:
177        supported_modes.append(WaterModes.EXTREME)
178    if features.is_smart_clean_mode_set_supported:
179        supported_modes.append(WaterModes.SMART_MODE)
180    if features.is_customized_clean_supported:
181        supported_modes.append(WaterModes.CUSTOMIZED)
182
183    return supported_modes
184
185
186def get_water_mode_mapping(features: DeviceFeatures) -> dict[int, str]:
187    """Get water mode mapping by supported feature set.
188
189    WaterModes contains aliases for multiple codes that share the same value
190    string (e.g. low can be 201 or 225). For water slide mode devices we need
191    explicit code mapping to preserve those slide-specific codes.
192    """
193    if features.is_water_slide_mode_supported:
194        return {code: mode.value for code, mode in WATER_SLIDE_MODE_MAPPING.items()}
195    return {mode.code: mode.value for mode in get_water_modes(features)}
196
197
198def get_cleaning_mode_options(features: DeviceFeatures) -> list[CleaningMode]:
199    """Return the supported high-level cleaning modes for the device.
200
201    These options are the preferred user-facing choices because they bundle the
202    correct fan, water, and mop-route settings together for the device. Callers
203    should generally present these instead of mixing lower-level mode enums
204    unless they explicitly need fine-grained control.
205    """
206    if not features.is_support_water_mode:
207        return []
208
209    supported_water_modes = get_water_modes(features)
210    options = [CleaningMode.VACUUM, CleaningMode.VAC_AND_MOP]
211    if features.is_pure_clean_mop_supported:
212        options.append(CleaningMode.MOP)
213    if features.is_customized_clean_supported and WaterModes.CUSTOMIZED in supported_water_modes:
214        options.append(CleaningMode.CUSTOM)
215    if features.is_smart_clean_mode_set_supported and WaterModes.SMART_MODE in supported_water_modes:
216        options.append(CleaningMode.SMART_MODE)
217    return options
218
219
220def get_mop_only_vacuum_mode(features: DeviceFeatures) -> VacuumModes:
221    """Determine the vacuum mode to use when you just want to mop.
222
223    There are three cases that must be handled:
224    1. The device does not support only mopping.
225    2. The device supports raising the vacuum brush while mopping
226    3. All other cases.
227    """
228    if not features.is_pure_clean_mop_supported:
229        raise RoborockUnsupportedFeature("Mop-only cleaning is not supported")
230    if features.is_support_main_brush_up_down_supported:
231        return VacuumModes.OFF_RAISE_MAIN_BRUSH
232    return VacuumModes.OFF
233
234
235def _get_default_mopping_water_mode(features: DeviceFeatures) -> WaterModes:
236    """Pick a sensible default water mode when mopping for the device."""
237    # Water-slide devices use a disjoint set of water codes; pick a mid-flow
238    # slide code instead of the standard 202, which they don't accept.
239    if features.is_water_slide_mode_supported:
240        return WaterModes.PURE_WATER_FLOW_MIDDLE
241    return WaterModes.STANDARD
242
243
244def _get_clean_motor_mode_params(
245    mode: CleaningMode,
246    features: DeviceFeatures,
247) -> tuple[VacuumModes, WaterModes, CleanRoutes]:
248    """Return (fan_power, water_box_mode, mop_mode) enums for the high-level mode."""
249    if mode == CleaningMode.VACUUM:
250        return (VacuumModes.BALANCED, WaterModes.OFF, CleanRoutes.STANDARD)
251    if mode == CleaningMode.VAC_AND_MOP:
252        return (VacuumModes.BALANCED, _get_default_mopping_water_mode(features), CleanRoutes.STANDARD)
253    if mode == CleaningMode.MOP:
254        return (
255            get_mop_only_vacuum_mode(features),
256            _get_default_mopping_water_mode(features),
257            CleanRoutes.STANDARD,
258        )
259    if mode == CleaningMode.CUSTOM:
260        return (VacuumModes.CUSTOMIZED, WaterModes.CUSTOMIZED, CleanRoutes.CUSTOMIZED)
261    if mode == CleaningMode.SMART_MODE:
262        return (VacuumModes.SMART_MODE, WaterModes.SMART_MODE, CleanRoutes.SMART_MODE)
263    raise RoborockUnsupportedFeature(f"Cleaning mode {mode.value!r} is not supported")
264
265
266def resolve_cleaning_mode(cleaning_mode: str | CleaningMode) -> CleaningMode:
267    """Resolve a string or enum into a CleaningMode value."""
268    if isinstance(cleaning_mode, CleaningMode):
269        return cleaning_mode
270    try:
271        return CleaningMode(cleaning_mode)
272    except ValueError as err:
273        raise RoborockUnsupportedFeature(f"Cleaning mode {cleaning_mode!r} is not supported") from err
274
275
276def get_cleaning_mode_parameters(cleaning_mode: CleaningMode, features: DeviceFeatures) -> list[dict[str, int]]:
277    """Get the RPC payload for switching the high-level cleaning mode."""
278    if cleaning_mode not in get_cleaning_mode_options(features):
279        raise RoborockUnsupportedFeature(f"Cleaning mode {cleaning_mode.value!r} is not supported")
280
281    fan_power, water_box_mode, mop_mode = _get_clean_motor_mode_params(cleaning_mode, features)
282    params: dict[str, int] = {"fan_power": fan_power.code, "water_box_mode": water_box_mode.code}
283    if features.is_clean_route_setting_supported:
284        params["mop_mode"] = mop_mode.code
285    return [params]
286
287
288def _resolve_mode_code(value: int | ModeEnumT | None, mode_cls: type[ModeEnumT]) -> ModeEnumT | None:
289    """Resolve a raw code or enum into a RoborockModeEnum."""
290    if value is None:
291        return None
292    if isinstance(value, mode_cls):
293        return value
294    return mode_cls.from_code_optional(int(value))
295
296
297def _resolve_clean_mode(value: int | VacuumModes | None, features: DeviceFeatures) -> VacuumModes | None:
298    """Resolve a vacuum mode code, accounting for feature-specific code aliases."""
299    if value is None or isinstance(value, VacuumModes):
300        return value
301    if value == VacuumModes.OFF.code:
302        if features.is_pure_clean_mop_supported:
303            return get_mop_only_vacuum_mode(features)
304        return VacuumModes.GENTLE
305    return VacuumModes.from_code_optional(value)
306
307
308def get_current_cleaning_mode(
309    clean_mode: int | VacuumModes | None,
310    water_mode: int | WaterModes | None,
311    mop_mode: int | CleanRoutes | None,
312    features: DeviceFeatures,
313) -> CleaningMode | None:
314    """Classify the current high-level cleaning mode from individual mode codes."""
315    if not features.is_support_water_mode:
316        return None
317    clean_mode_enum = _resolve_clean_mode(clean_mode, features)
318    water_mode_enum = _resolve_mode_code(water_mode, WaterModes)
319    mop_mode_enum = _resolve_mode_code(mop_mode, CleanRoutes)
320    if clean_mode_enum is None or water_mode_enum is None:
321        return None
322
323    if is_smart_mode_set(water_mode_enum, clean_mode_enum, mop_mode_enum):
324        return CleaningMode.SMART_MODE
325    if is_mode_customized(clean_mode_enum, water_mode_enum, mop_mode_enum):
326        return CleaningMode.CUSTOM
327    if water_mode_enum != WaterModes.OFF:
328        try:
329            if clean_mode_enum == get_mop_only_vacuum_mode(features):
330                return CleaningMode.MOP
331        except RoborockUnsupportedFeature:
332            pass
333    if water_mode_enum == WaterModes.OFF:
334        return CleaningMode.VACUUM
335    return CleaningMode.VAC_AND_MOP
336
337
338def is_mode_customized(
339    clean_mode: VacuumModes | None,
340    water_mode: WaterModes | None,
341    mop_mode: CleanRoutes | None,
342) -> bool:
343    """Check if any of the cleaning modes are set to a custom value."""
344    return (
345        clean_mode == VacuumModes.CUSTOMIZED
346        or water_mode == WaterModes.CUSTOMIZED
347        or mop_mode == CleanRoutes.CUSTOMIZED
348    )
349
350
351def is_smart_mode_set(
352    water_mode: WaterModes | None,
353    clean_mode: VacuumModes | None,
354    mop_mode: CleanRoutes | None,
355) -> bool:
356    """Check if the smart mode is set for the given water mode and clean mode"""
357    return (
358        water_mode == WaterModes.SMART_MODE
359        or clean_mode == VacuumModes.SMART_MODE
360        or mop_mode == CleanRoutes.SMART_MODE
361    )
class VacuumModes(roborock.data.code_mappings.RoborockModeEnum):
15class VacuumModes(RoborockModeEnum):
16    GENTLE = ("gentle", 105)
17    OFF = ("off", 105)
18    QUIET = ("quiet", 101)
19    BALANCED = ("balanced", 102)
20    TURBO = ("turbo", 103)
21    MAX = ("max", 104)
22    MAX_PLUS = ("max_plus", 108)
23    OFF_RAISE_MAIN_BRUSH = ("off_raise_main_brush", 109)
24    CUSTOMIZED = ("custom", 106)
25    SMART_MODE = ("smart_mode", 110)

A custom StrEnum that also stores an integer code for each member.

GENTLE = <VacuumModes.GENTLE: 'gentle'>
OFF = <VacuumModes.OFF: 'off'>
QUIET = <VacuumModes.QUIET: 'quiet'>
BALANCED = <VacuumModes.BALANCED: 'balanced'>
TURBO = <VacuumModes.TURBO: 'turbo'>
MAX = <VacuumModes.MAX: 'max'>
MAX_PLUS = <VacuumModes.MAX_PLUS: 'max_plus'>
OFF_RAISE_MAIN_BRUSH = <VacuumModes.OFF_RAISE_MAIN_BRUSH: 'off_raise_main_brush'>
CUSTOMIZED = <VacuumModes.CUSTOMIZED: 'custom'>
SMART_MODE = <VacuumModes.SMART_MODE: 'smart_mode'>
class CleanRoutes(roborock.data.code_mappings.RoborockModeEnum):
28class CleanRoutes(RoborockModeEnum):
29    STANDARD = ("standard", 300)
30    DEEP = ("deep", 301)
31    DEEP_PLUS = ("deep_plus", 303)
32    FAST = ("fast", 304)
33    DEEP_PLUS_CN = ("deep_plus", 305)
34    SMART_MODE = ("smart_mode", 306)
35    CUSTOMIZED = ("custom", 302)

A custom StrEnum that also stores an integer code for each member.

STANDARD = <CleanRoutes.STANDARD: 'standard'>
DEEP = <CleanRoutes.DEEP: 'deep'>
DEEP_PLUS = <CleanRoutes.DEEP_PLUS: 'deep_plus'>
FAST = <CleanRoutes.FAST: 'fast'>
DEEP_PLUS_CN = <CleanRoutes.DEEP_PLUS: 'deep_plus'>
SMART_MODE = <CleanRoutes.SMART_MODE: 'smart_mode'>
CUSTOMIZED = <CleanRoutes.CUSTOMIZED: 'custom'>
class VacuumModesOld(roborock.data.code_mappings.RoborockModeEnum):
38class VacuumModesOld(RoborockModeEnum):
39    QUIET = ("quiet", 38)
40    BALANCED = ("balanced", 60)
41    TURBO = ("turbo", 75)
42    MAX = ("max", 100)

A custom StrEnum that also stores an integer code for each member.

QUIET = <VacuumModesOld.QUIET: 'quiet'>
BALANCED = <VacuumModesOld.BALANCED: 'balanced'>
TURBO = <VacuumModesOld.TURBO: 'turbo'>
MAX = <VacuumModesOld.MAX: 'max'>
class WaterModes(roborock.data.code_mappings.RoborockModeEnum):
45class WaterModes(RoborockModeEnum):
46    OFF = ("off", 200)
47    LOW = ("low", 201)
48    MILD = ("mild", 201)
49    MEDIUM = ("medium", 202)
50    STANDARD = ("standard", 202)
51    HIGH = ("high", 203)
52    INTENSE = ("intense", 203)
53    MIN = ("min", 205)
54    MAX = ("max", 206)
55    CUSTOMIZED = ("custom", 204)
56    CUSTOM = ("custom_water_flow", 207)
57    EXTREME = ("extreme", 208)
58    SMART_MODE = ("smart_mode", 209)
59    PURE_WATER_FLOW_START = ("slight", 221)
60    PURE_WATER_FLOW_SMALL = ("low", 225)
61    PURE_WATER_FLOW_MIDDLE = ("medium", 235)
62    PURE_WATER_FLOW_LARGE = ("moderate", 245)
63    PURE_WATER_SUPER_BEGIN = ("high", 248)
64    PURE_WATER_FLOW_END = ("extreme", 250)

A custom StrEnum that also stores an integer code for each member.

OFF = <WaterModes.OFF: 'off'>
LOW = <WaterModes.LOW: 'low'>
MILD = <WaterModes.MILD: 'mild'>
MEDIUM = <WaterModes.MEDIUM: 'medium'>
STANDARD = <WaterModes.STANDARD: 'standard'>
HIGH = <WaterModes.HIGH: 'high'>
INTENSE = <WaterModes.INTENSE: 'intense'>
MIN = <WaterModes.MIN: 'min'>
MAX = <WaterModes.MAX: 'max'>
CUSTOMIZED = <WaterModes.CUSTOMIZED: 'custom'>
CUSTOM = <WaterModes.CUSTOM: 'custom_water_flow'>
EXTREME = <WaterModes.EXTREME: 'extreme'>
SMART_MODE = <WaterModes.SMART_MODE: 'smart_mode'>
PURE_WATER_FLOW_START = <WaterModes.PURE_WATER_FLOW_START: 'slight'>
PURE_WATER_FLOW_SMALL = <WaterModes.LOW: 'low'>
PURE_WATER_FLOW_MIDDLE = <WaterModes.MEDIUM: 'medium'>
PURE_WATER_FLOW_LARGE = <WaterModes.PURE_WATER_FLOW_LARGE: 'moderate'>
PURE_WATER_SUPER_BEGIN = <WaterModes.HIGH: 'high'>
PURE_WATER_FLOW_END = <WaterModes.EXTREME: 'extreme'>
class WashTowelModes(roborock.data.code_mappings.RoborockModeEnum):
67class WashTowelModes(RoborockModeEnum):
68    SMART = ("smart", 10)
69    LIGHT = ("light", 0)
70    BALANCED = ("balanced", 1)
71    DEEP = ("deep", 2)
72    SUPER_DEEP = ("super_deep", 8)

A custom StrEnum that also stores an integer code for each member.

SMART = <WashTowelModes.SMART: 'smart'>
LIGHT = <WashTowelModes.LIGHT: 'light'>
BALANCED = <WashTowelModes.BALANCED: 'balanced'>
DEEP = <WashTowelModes.DEEP: 'deep'>
SUPER_DEEP = <WashTowelModes.SUPER_DEEP: 'super_deep'>
class CleaningMode(enum.StrEnum):
75class CleaningMode(StrEnum):
76    """High-level cleaning intent derived from the lower-level motor settings.
77
78    Prefer this abstraction when you want to present or switch between the
79    user-facing cleaning behaviors exposed by the app. The lower-level
80    `VacuumModes`, `WaterModes`, and `CleanRoutes` enums are still useful for
81    fine-grained selection.
82    """
83
84    VACUUM = "vacuum"
85    VAC_AND_MOP = "vac_and_mop"
86    MOP = "mop"
87    CUSTOM = "custom"
88    SMART_MODE = "smart_mode"

High-level cleaning intent derived from the lower-level motor settings.

Prefer this abstraction when you want to present or switch between the user-facing cleaning behaviors exposed by the app. The lower-level VacuumModes, WaterModes, and CleanRoutes enums are still useful for fine-grained selection.

VACUUM = <CleaningMode.VACUUM: 'vacuum'>
VAC_AND_MOP = <CleaningMode.VAC_AND_MOP: 'vac_and_mop'>
MOP = <CleaningMode.MOP: 'mop'>
CUSTOM = <CleaningMode.CUSTOM: 'custom'>
SMART_MODE = <CleaningMode.SMART_MODE: 'smart_mode'>
WATER_SLIDE_MODE_MAPPING: dict[int, WaterModes] = {200: <WaterModes.OFF: 'off'>, 221: <WaterModes.PURE_WATER_FLOW_START: 'slight'>, 225: <WaterModes.LOW: 'low'>, 235: <WaterModes.MEDIUM: 'medium'>, 245: <WaterModes.PURE_WATER_FLOW_LARGE: 'moderate'>, 248: <WaterModes.HIGH: 'high'>, 250: <WaterModes.EXTREME: 'extreme'>}
def get_wash_towel_modes( features: roborock.device_features.DeviceFeatures) -> list[WashTowelModes]:
104def get_wash_towel_modes(features: DeviceFeatures) -> list[WashTowelModes]:
105    """Get the valid wash towel modes for the device"""
106    modes = [WashTowelModes.LIGHT, WashTowelModes.BALANCED, WashTowelModes.DEEP]
107    if features.is_super_deep_wash_supported and not features.is_dirty_replenish_clean_supported:
108        modes.append(WashTowelModes.SUPER_DEEP)
109    elif features.is_dirty_replenish_clean_supported:
110        modes.append(WashTowelModes.SMART)
111    return modes

Get the valid wash towel modes for the device

def get_clean_modes( features: roborock.device_features.DeviceFeatures) -> list[VacuumModes]:
114def get_clean_modes(features: DeviceFeatures) -> list[VacuumModes]:
115    """Get the valid clean modes for the device - also known as 'fan power' or 'suction mode'"""
116    modes = [VacuumModes.QUIET, VacuumModes.BALANCED, VacuumModes.TURBO, VacuumModes.MAX]
117    if features.is_max_plus_mode_supported or features.is_none_pure_clean_mop_with_max_plus:
118        # If the vacuum has max plus mode supported
119        modes.append(VacuumModes.MAX_PLUS)
120    if features.is_pure_clean_mop_supported:
121        # If the vacuum is capable of 'pure mop clean' aka no vacuum
122        if features.is_support_main_brush_up_down_supported:
123            modes.append(VacuumModes.OFF_RAISE_MAIN_BRUSH)
124        else:
125            modes.append(VacuumModes.OFF)
126    else:
127        # If not, we can add gentle
128        modes.append(VacuumModes.GENTLE)
129    if features.is_smart_clean_mode_set_supported:
130        modes.append(VacuumModes.SMART_MODE)
131    if features.is_customized_clean_supported:
132        modes.append(VacuumModes.CUSTOMIZED)
133    return modes

Get the valid clean modes for the device - also known as 'fan power' or 'suction mode'

def get_clean_routes( features: roborock.device_features.DeviceFeatures, region: str) -> list[CleanRoutes]:
136def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]:
137    """The routes that the vacuum will take while mopping"""
138    if features.is_none_pure_clean_mop_with_max_plus:
139        return [CleanRoutes.FAST, CleanRoutes.STANDARD]
140    supported = [CleanRoutes.STANDARD, CleanRoutes.DEEP]
141    if features.is_careful_slow_mop_supported:
142        if not (
143            features.is_corner_clean_mode_supported
144            and features.is_clean_route_deep_slow_plus_supported
145            and region == "cn"
146        ):
147            # for some reason there is a china specific deep plus mode
148            supported.append(CleanRoutes.DEEP_PLUS_CN)
149        else:
150            supported.append(CleanRoutes.DEEP_PLUS)
151
152    if features.is_clean_route_fast_mode_supported:
153        supported.append(CleanRoutes.FAST)
154    if features.is_smart_clean_mode_set_supported:
155        supported.append(CleanRoutes.SMART_MODE)
156    if features.is_customized_clean_supported:
157        supported.append(CleanRoutes.CUSTOMIZED)
158
159    return supported

The routes that the vacuum will take while mopping

def get_water_modes( features: roborock.device_features.DeviceFeatures) -> list[WaterModes]:
162def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
163    """Get the valid water modes for the device - also known as 'water flow' or 'water level'"""
164    # Water slide mode supports a separate set of water flow codes.
165    if features.is_water_slide_mode_supported:
166        return list(WATER_SLIDE_MODE_MAPPING.values())
167
168    supported_modes = [WaterModes.OFF]
169    if features.is_mop_shake_module_supported:
170        # For mops that have the vibrating mop pad, they do mild standard intense
171        supported_modes.extend([WaterModes.MILD, WaterModes.STANDARD, WaterModes.INTENSE])
172    else:
173        supported_modes.extend([WaterModes.LOW, WaterModes.MEDIUM, WaterModes.HIGH])
174    if features.is_custom_water_box_distance_supported:
175        # This is for devices that allow you to set a custom water flow from 0-100
176        supported_modes.append(WaterModes.CUSTOM)
177    if features.is_mop_shake_module_supported and features.is_mop_shake_water_max_supported:
178        supported_modes.append(WaterModes.EXTREME)
179    if features.is_smart_clean_mode_set_supported:
180        supported_modes.append(WaterModes.SMART_MODE)
181    if features.is_customized_clean_supported:
182        supported_modes.append(WaterModes.CUSTOMIZED)
183
184    return supported_modes

Get the valid water modes for the device - also known as 'water flow' or 'water level'

def get_water_mode_mapping(features: roborock.device_features.DeviceFeatures) -> dict[int, str]:
187def get_water_mode_mapping(features: DeviceFeatures) -> dict[int, str]:
188    """Get water mode mapping by supported feature set.
189
190    WaterModes contains aliases for multiple codes that share the same value
191    string (e.g. low can be 201 or 225). For water slide mode devices we need
192    explicit code mapping to preserve those slide-specific codes.
193    """
194    if features.is_water_slide_mode_supported:
195        return {code: mode.value for code, mode in WATER_SLIDE_MODE_MAPPING.items()}
196    return {mode.code: mode.value for mode in get_water_modes(features)}

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.

def get_cleaning_mode_options( features: roborock.device_features.DeviceFeatures) -> list[CleaningMode]:
199def get_cleaning_mode_options(features: DeviceFeatures) -> list[CleaningMode]:
200    """Return the supported high-level cleaning modes for the device.
201
202    These options are the preferred user-facing choices because they bundle the
203    correct fan, water, and mop-route settings together for the device. Callers
204    should generally present these instead of mixing lower-level mode enums
205    unless they explicitly need fine-grained control.
206    """
207    if not features.is_support_water_mode:
208        return []
209
210    supported_water_modes = get_water_modes(features)
211    options = [CleaningMode.VACUUM, CleaningMode.VAC_AND_MOP]
212    if features.is_pure_clean_mop_supported:
213        options.append(CleaningMode.MOP)
214    if features.is_customized_clean_supported and WaterModes.CUSTOMIZED in supported_water_modes:
215        options.append(CleaningMode.CUSTOM)
216    if features.is_smart_clean_mode_set_supported and WaterModes.SMART_MODE in supported_water_modes:
217        options.append(CleaningMode.SMART_MODE)
218    return options

Return the supported high-level cleaning modes for the device.

These options are the preferred user-facing choices because they bundle the correct fan, water, and mop-route settings together for the device. Callers should generally present these instead of mixing lower-level mode enums unless they explicitly need fine-grained control.

def get_mop_only_vacuum_mode( features: roborock.device_features.DeviceFeatures) -> VacuumModes:
221def get_mop_only_vacuum_mode(features: DeviceFeatures) -> VacuumModes:
222    """Determine the vacuum mode to use when you just want to mop.
223
224    There are three cases that must be handled:
225    1. The device does not support only mopping.
226    2. The device supports raising the vacuum brush while mopping
227    3. All other cases.
228    """
229    if not features.is_pure_clean_mop_supported:
230        raise RoborockUnsupportedFeature("Mop-only cleaning is not supported")
231    if features.is_support_main_brush_up_down_supported:
232        return VacuumModes.OFF_RAISE_MAIN_BRUSH
233    return VacuumModes.OFF

Determine the vacuum mode to use when you just want to mop.

There are three cases that must be handled:

  1. The device does not support only mopping.
  2. The device supports raising the vacuum brush while mopping
  3. All other cases.
def resolve_cleaning_mode( cleaning_mode: str | CleaningMode) -> CleaningMode:
267def resolve_cleaning_mode(cleaning_mode: str | CleaningMode) -> CleaningMode:
268    """Resolve a string or enum into a CleaningMode value."""
269    if isinstance(cleaning_mode, CleaningMode):
270        return cleaning_mode
271    try:
272        return CleaningMode(cleaning_mode)
273    except ValueError as err:
274        raise RoborockUnsupportedFeature(f"Cleaning mode {cleaning_mode!r} is not supported") from err

Resolve a string or enum into a CleaningMode value.

def get_cleaning_mode_parameters( cleaning_mode: CleaningMode, features: roborock.device_features.DeviceFeatures) -> list[dict[str, int]]:
277def get_cleaning_mode_parameters(cleaning_mode: CleaningMode, features: DeviceFeatures) -> list[dict[str, int]]:
278    """Get the RPC payload for switching the high-level cleaning mode."""
279    if cleaning_mode not in get_cleaning_mode_options(features):
280        raise RoborockUnsupportedFeature(f"Cleaning mode {cleaning_mode.value!r} is not supported")
281
282    fan_power, water_box_mode, mop_mode = _get_clean_motor_mode_params(cleaning_mode, features)
283    params: dict[str, int] = {"fan_power": fan_power.code, "water_box_mode": water_box_mode.code}
284    if features.is_clean_route_setting_supported:
285        params["mop_mode"] = mop_mode.code
286    return [params]

Get the RPC payload for switching the high-level cleaning mode.

def get_current_cleaning_mode( clean_mode: int | VacuumModes | None, water_mode: int | WaterModes | None, mop_mode: int | CleanRoutes | None, features: roborock.device_features.DeviceFeatures) -> CleaningMode | None:
309def get_current_cleaning_mode(
310    clean_mode: int | VacuumModes | None,
311    water_mode: int | WaterModes | None,
312    mop_mode: int | CleanRoutes | None,
313    features: DeviceFeatures,
314) -> CleaningMode | None:
315    """Classify the current high-level cleaning mode from individual mode codes."""
316    if not features.is_support_water_mode:
317        return None
318    clean_mode_enum = _resolve_clean_mode(clean_mode, features)
319    water_mode_enum = _resolve_mode_code(water_mode, WaterModes)
320    mop_mode_enum = _resolve_mode_code(mop_mode, CleanRoutes)
321    if clean_mode_enum is None or water_mode_enum is None:
322        return None
323
324    if is_smart_mode_set(water_mode_enum, clean_mode_enum, mop_mode_enum):
325        return CleaningMode.SMART_MODE
326    if is_mode_customized(clean_mode_enum, water_mode_enum, mop_mode_enum):
327        return CleaningMode.CUSTOM
328    if water_mode_enum != WaterModes.OFF:
329        try:
330            if clean_mode_enum == get_mop_only_vacuum_mode(features):
331                return CleaningMode.MOP
332        except RoborockUnsupportedFeature:
333            pass
334    if water_mode_enum == WaterModes.OFF:
335        return CleaningMode.VACUUM
336    return CleaningMode.VAC_AND_MOP

Classify the current high-level cleaning mode from individual mode codes.

def is_mode_customized( clean_mode: VacuumModes | None, water_mode: WaterModes | None, mop_mode: CleanRoutes | None) -> bool:
339def is_mode_customized(
340    clean_mode: VacuumModes | None,
341    water_mode: WaterModes | None,
342    mop_mode: CleanRoutes | None,
343) -> bool:
344    """Check if any of the cleaning modes are set to a custom value."""
345    return (
346        clean_mode == VacuumModes.CUSTOMIZED
347        or water_mode == WaterModes.CUSTOMIZED
348        or mop_mode == CleanRoutes.CUSTOMIZED
349    )

Check if any of the cleaning modes are set to a custom value.

def is_smart_mode_set( water_mode: WaterModes | None, clean_mode: VacuumModes | None, mop_mode: CleanRoutes | None) -> bool:
352def is_smart_mode_set(
353    water_mode: WaterModes | None,
354    clean_mode: VacuumModes | None,
355    mop_mode: CleanRoutes | None,
356) -> bool:
357    """Check if the smart mode is set for the given water mode and clean mode"""
358    return (
359        water_mode == WaterModes.SMART_MODE
360        or clean_mode == VacuumModes.SMART_MODE
361        or mop_mode == CleanRoutes.SMART_MODE
362    )

Check if the smart mode is set for the given water mode and clean mode