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_cn", 305, "deep_plus")
 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    SMART_WATER = ("smart_water", 210)
 59    PURE_WATER_FLOW_START = ("slight", 221)
 60    PURE_WATER_FLOW_SMALL = ("pure_water_flow_small", 225, "low")
 61    PURE_WATER_FLOW_GENTLE = ("gentle", 230)
 62    PURE_WATER_FLOW_MIDDLE = ("pure_water_flow_middle", 235, "medium")
 63    PURE_WATER_FLOW_LARGE = ("moderate", 240)
 64    PURE_WATER_SUPER_BEGIN = ("pure_water_super_begin", 245, "high")
 65    PURE_WATER_FLOW_END = ("pure_water_flow_end", 250, "extreme")
 66
 67
 68class WashTowelModes(RoborockModeEnum):
 69    SMART = ("smart", 10)
 70    LIGHT = ("light", 0)
 71    BALANCED = ("balanced", 1)
 72    DEEP = ("deep", 2)
 73    SUPER_DEEP = ("super_deep", 8)
 74
 75
 76class CleaningMode(StrEnum):
 77    """High-level cleaning intent derived from the lower-level motor settings.
 78
 79    Prefer this abstraction when you want to present or switch between the
 80    user-facing cleaning behaviors exposed by the app. The lower-level
 81    `VacuumModes`, `WaterModes`, and `CleanRoutes` enums are still useful for
 82    fine-grained selection.
 83    """
 84
 85    VACUUM = "vacuum"
 86    VAC_AND_MOP = "vac_and_mop"
 87    MOP = "mop"
 88    CUSTOM = "custom"
 89    SMART_MODE = "smart_mode"
 90
 91
 92_WATER_SLIDE_MODES: tuple[WaterModes, ...] = (
 93    WaterModes.OFF,
 94    WaterModes.PURE_WATER_FLOW_START,
 95    WaterModes.PURE_WATER_FLOW_SMALL,
 96    WaterModes.PURE_WATER_FLOW_GENTLE,
 97    WaterModes.PURE_WATER_FLOW_MIDDLE,
 98    WaterModes.PURE_WATER_FLOW_LARGE,
 99    WaterModes.PURE_WATER_SUPER_BEGIN,
100    WaterModes.PURE_WATER_FLOW_END,
101)
102
103# Water slide devices report the raw slider position (1-30) as 220 + position.
104# The app groups positions into named ranges, keyed here by their last position.
105_WATER_SLIDE_RANGES: tuple[tuple[int, WaterModes], ...] = (
106    (3, WaterModes.PURE_WATER_FLOW_START),
107    (7, WaterModes.PURE_WATER_FLOW_SMALL),
108    (12, WaterModes.PURE_WATER_FLOW_GENTLE),
109    (17, WaterModes.PURE_WATER_FLOW_MIDDLE),
110    (22, WaterModes.PURE_WATER_FLOW_LARGE),
111    (27, WaterModes.PURE_WATER_SUPER_BEGIN),
112    (30, WaterModes.PURE_WATER_FLOW_END),
113)
114
115ModeEnumT = TypeVar("ModeEnumT", bound=RoborockModeEnum)
116
117
118def get_wash_towel_modes(features: DeviceFeatures) -> list[WashTowelModes]:
119    """Get the valid wash towel modes for the device"""
120    modes = [WashTowelModes.LIGHT, WashTowelModes.BALANCED, WashTowelModes.DEEP]
121    if features.is_super_deep_wash_supported and not features.is_dirty_replenish_clean_supported:
122        modes.append(WashTowelModes.SUPER_DEEP)
123    elif features.is_dirty_replenish_clean_supported:
124        modes.append(WashTowelModes.SMART)
125    return modes
126
127
128def get_clean_modes(features: DeviceFeatures) -> list[VacuumModes]:
129    """Get the valid clean modes for the device - also known as 'fan power' or 'suction mode'"""
130    modes = [VacuumModes.QUIET, VacuumModes.BALANCED, VacuumModes.TURBO, VacuumModes.MAX]
131    if features.is_max_plus_mode_supported or features.is_none_pure_clean_mop_with_max_plus:
132        # If the vacuum has max plus mode supported
133        modes.append(VacuumModes.MAX_PLUS)
134    if features.is_pure_clean_mop_supported:
135        # If the vacuum is capable of 'pure mop clean' aka no vacuum
136        modes.append(VacuumModes.OFF)
137    else:
138        # If not, we can add gentle
139        modes.append(VacuumModes.GENTLE)
140    if features.is_smart_clean_mode_set_supported:
141        modes.append(VacuumModes.SMART_MODE)
142    if features.is_customized_clean_supported:
143        modes.append(VacuumModes.CUSTOMIZED)
144    return modes
145
146
147def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]:
148    """The routes that the vacuum will take while mopping"""
149    if not features.is_clean_route_setting_supported:
150        return []
151    if features.is_none_pure_clean_mop_with_max_plus:
152        return [CleanRoutes.FAST, CleanRoutes.STANDARD]
153    supported = [CleanRoutes.STANDARD]
154    if not features.is_clean_efficiency_supported:
155        supported.append(CleanRoutes.DEEP)
156    if features.is_careful_slow_mop_supported:
157        if (
158            not features.is_corner_clean_mode_supported
159            and features.is_clean_route_deep_slow_plus_supported
160            and region == "cn"
161        ):
162            supported.append(CleanRoutes.DEEP_PLUS_CN)
163        else:
164            supported.append(CleanRoutes.DEEP_PLUS)
165
166    if features.is_clean_route_fast_mode_supported:
167        supported.append(CleanRoutes.FAST)
168    if features.is_smart_clean_mode_set_supported:
169        supported.append(CleanRoutes.SMART_MODE)
170    if features.is_customized_clean_supported:
171        supported.append(CleanRoutes.CUSTOMIZED)
172
173    return supported
174
175
176def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
177    """Get the valid water modes for the device - also known as 'water flow' or 'water level'"""
178    # Water slide mode supports a separate set of water flow codes.
179    if features.is_water_slide_mode_supported:
180        return list(_WATER_SLIDE_MODES)
181
182    supported_modes = [WaterModes.OFF]
183    if features.is_mop_shake_module_supported:
184        # For mops that have the vibrating mop pad, they do mild standard intense
185        supported_modes.extend([WaterModes.MILD, WaterModes.STANDARD, WaterModes.INTENSE])
186    else:
187        supported_modes.extend([WaterModes.LOW, WaterModes.MEDIUM, WaterModes.HIGH])
188    if features.is_custom_water_box_distance_supported:
189        # This is for devices that allow you to set a custom water flow from 0-100
190        supported_modes.append(WaterModes.CUSTOM)
191    if features.is_mop_shake_module_supported and features.is_mop_shake_water_max_supported:
192        supported_modes.append(WaterModes.EXTREME)
193    if features.is_smart_clean_mode_set_supported:
194        supported_modes.append(WaterModes.SMART_MODE)
195    if features.is_customized_clean_supported:
196        supported_modes.append(WaterModes.CUSTOMIZED)
197
198    return supported_modes
199
200
201def get_water_slide_mode_name(code: int) -> str | None:
202    """Get the app's range name for a water slide code, or None if out of range."""
203    mode = _resolve_water_slide_mode(code)
204    return mode.display_name if mode is not None else None
205
206
207def _resolve_water_slide_mode(code: int) -> WaterModes | None:
208    """Resolve a raw slider code to the water mode for its app range."""
209    if code == 200:
210        return WaterModes.OFF
211    if code == WaterModes.SMART_WATER.code:
212        return WaterModes.SMART_WATER
213    position = code - 220
214    if position < 1:
215        return None
216    for last_position, mode in _WATER_SLIDE_RANGES:
217        if position <= last_position:
218            return mode
219    return None
220
221
222def get_water_mode_mapping(features: DeviceFeatures) -> dict[int, str]:
223    """Get water mode mapping by supported feature set.
224
225    Water slide modes share user-facing names with legacy modes (e.g. "low" is
226    201 or 225), so the mapping uses display_name rather than the enum value.
227    """
228    return {mode.code: mode.display_name for mode in get_water_modes(features)}
229
230
231def get_cleaning_mode_options(features: DeviceFeatures) -> list[CleaningMode]:
232    """Return the supported high-level cleaning modes for the device.
233
234    These options are the preferred user-facing choices because they bundle the
235    correct fan, water, and mop-route settings together for the device. Callers
236    should generally present these instead of mixing lower-level mode enums
237    unless they explicitly need fine-grained control.
238    """
239    if not features.is_support_water_mode:
240        return []
241
242    supported_water_modes = get_water_modes(features)
243    options = [CleaningMode.VACUUM, CleaningMode.VAC_AND_MOP]
244    if features.is_pure_clean_mop_supported:
245        options.append(CleaningMode.MOP)
246    if features.is_customized_clean_supported and WaterModes.CUSTOMIZED in supported_water_modes:
247        options.append(CleaningMode.CUSTOM)
248    if features.is_smart_clean_mode_set_supported and WaterModes.SMART_MODE in supported_water_modes:
249        options.append(CleaningMode.SMART_MODE)
250    return options
251
252
253def get_mop_only_vacuum_mode(features: DeviceFeatures) -> VacuumModes:
254    """Return the vacuum mode used by the app for mop-only cleaning."""
255    if not features.is_pure_clean_mop_supported:
256        raise RoborockUnsupportedFeature("Mop-only cleaning is not supported")
257    # Main-brush lift is a device capability, not an alternate fan-power
258    # command. The app still sends CleanModeZero (105) for mop-only cleaning.
259    return VacuumModes.OFF
260
261
262def _get_default_mopping_water_mode(features: DeviceFeatures) -> WaterModes:
263    """Pick a sensible default water mode when mopping for the device."""
264    # Water-slide devices use a disjoint set of water codes; pick a mid-flow
265    # slide code instead of the standard 202, which they don't accept.
266    if features.is_water_slide_mode_supported:
267        return WaterModes.PURE_WATER_FLOW_MIDDLE
268    return WaterModes.STANDARD
269
270
271def _get_clean_motor_mode_params(
272    mode: CleaningMode,
273    features: DeviceFeatures,
274) -> tuple[VacuumModes, WaterModes, CleanRoutes]:
275    """Return (fan_power, water_box_mode, mop_mode) enums for the high-level mode."""
276    if mode == CleaningMode.VACUUM:
277        return (VacuumModes.BALANCED, WaterModes.OFF, CleanRoutes.STANDARD)
278    if mode == CleaningMode.VAC_AND_MOP:
279        return (VacuumModes.BALANCED, _get_default_mopping_water_mode(features), CleanRoutes.STANDARD)
280    if mode == CleaningMode.MOP:
281        return (
282            get_mop_only_vacuum_mode(features),
283            _get_default_mopping_water_mode(features),
284            CleanRoutes.STANDARD,
285        )
286    if mode == CleaningMode.CUSTOM:
287        return (VacuumModes.CUSTOMIZED, WaterModes.CUSTOMIZED, CleanRoutes.CUSTOMIZED)
288    if mode == CleaningMode.SMART_MODE:
289        return (VacuumModes.SMART_MODE, WaterModes.SMART_MODE, CleanRoutes.SMART_MODE)
290    raise RoborockUnsupportedFeature(f"Cleaning mode {mode.value!r} is not supported")
291
292
293def resolve_cleaning_mode(cleaning_mode: str | CleaningMode) -> CleaningMode:
294    """Resolve a string or enum into a CleaningMode value."""
295    if isinstance(cleaning_mode, CleaningMode):
296        return cleaning_mode
297    try:
298        return CleaningMode(cleaning_mode)
299    except ValueError as err:
300        raise RoborockUnsupportedFeature(f"Cleaning mode {cleaning_mode!r} is not supported") from err
301
302
303def get_cleaning_mode_parameters(cleaning_mode: CleaningMode, features: DeviceFeatures) -> list[dict[str, int]]:
304    """Get the RPC payload for switching the high-level cleaning mode."""
305    if cleaning_mode not in get_cleaning_mode_options(features):
306        raise RoborockUnsupportedFeature(f"Cleaning mode {cleaning_mode.value!r} is not supported")
307
308    fan_power, water_box_mode, mop_mode = _get_clean_motor_mode_params(cleaning_mode, features)
309    params: dict[str, int] = {"fan_power": fan_power.code, "water_box_mode": water_box_mode.code}
310    if features.is_clean_route_setting_supported:
311        params["mop_mode"] = mop_mode.code
312    return [params]
313
314
315def _resolve_mode_code(value: int | ModeEnumT | None, mode_cls: type[ModeEnumT]) -> ModeEnumT | None:
316    """Resolve a raw code or enum into a RoborockModeEnum."""
317    if value is None:
318        return None
319    if isinstance(value, mode_cls):
320        return value
321    return mode_cls.from_code_optional(int(value))
322
323
324def _resolve_clean_mode(value: int | VacuumModes | None, features: DeviceFeatures) -> VacuumModes | None:
325    """Resolve a vacuum mode code, accounting for feature-specific code aliases."""
326    if value is None or isinstance(value, VacuumModes):
327        return value
328    if value == VacuumModes.OFF.code:
329        if features.is_pure_clean_mop_supported:
330            return get_mop_only_vacuum_mode(features)
331        return VacuumModes.GENTLE
332    return VacuumModes.from_code_optional(value)
333
334
335def _resolve_water_mode(value: int | WaterModes | None, features: DeviceFeatures) -> WaterModes | None:
336    """Resolve a water mode code, mapping raw slider positions on water slide devices."""
337    if value is None or isinstance(value, WaterModes):
338        return value
339    if features.is_water_slide_mode_supported:
340        return _resolve_water_slide_mode(value) or WaterModes.from_code_optional(value)
341    return WaterModes.from_code_optional(value)
342
343
344def get_current_cleaning_mode(
345    clean_mode: int | VacuumModes | None,
346    water_mode: int | WaterModes | None,
347    mop_mode: int | CleanRoutes | None,
348    features: DeviceFeatures,
349) -> CleaningMode | None:
350    """Classify the current high-level cleaning mode from individual mode codes."""
351    if not features.is_support_water_mode:
352        return None
353    clean_mode_enum = _resolve_clean_mode(clean_mode, features)
354    water_mode_enum = _resolve_water_mode(water_mode, features)
355    mop_mode_enum = _resolve_mode_code(mop_mode, CleanRoutes)
356    if clean_mode_enum is None or water_mode_enum is None:
357        return None
358
359    if is_smart_mode_set(water_mode_enum, clean_mode_enum, mop_mode_enum):
360        return CleaningMode.SMART_MODE
361    if is_mode_customized(clean_mode_enum, water_mode_enum, mop_mode_enum):
362        return CleaningMode.CUSTOM
363    if water_mode_enum != WaterModes.OFF:
364        try:
365            if clean_mode_enum == get_mop_only_vacuum_mode(features):
366                return CleaningMode.MOP
367        except RoborockUnsupportedFeature:
368            pass
369    if water_mode_enum == WaterModes.OFF:
370        return CleaningMode.VACUUM
371    return CleaningMode.VAC_AND_MOP
372
373
374def is_mode_customized(
375    clean_mode: VacuumModes | None,
376    water_mode: WaterModes | None,
377    mop_mode: CleanRoutes | None,
378) -> bool:
379    """Check if any of the cleaning modes are set to a custom value."""
380    return (
381        clean_mode == VacuumModes.CUSTOMIZED
382        or water_mode == WaterModes.CUSTOMIZED
383        or mop_mode == CleanRoutes.CUSTOMIZED
384    )
385
386
387def is_smart_mode_set(
388    water_mode: WaterModes | None,
389    clean_mode: VacuumModes | None,
390    mop_mode: CleanRoutes | None,
391) -> bool:
392    """Check if the smart mode is set for the given water mode and clean mode"""
393    return (
394        water_mode == WaterModes.SMART_MODE
395        or clean_mode == VacuumModes.SMART_MODE
396        or mop_mode == CleanRoutes.SMART_MODE
397    )
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_cn", 305, "deep_plus")
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_CN: 'deep_plus_cn'>
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    SMART_WATER = ("smart_water", 210)
60    PURE_WATER_FLOW_START = ("slight", 221)
61    PURE_WATER_FLOW_SMALL = ("pure_water_flow_small", 225, "low")
62    PURE_WATER_FLOW_GENTLE = ("gentle", 230)
63    PURE_WATER_FLOW_MIDDLE = ("pure_water_flow_middle", 235, "medium")
64    PURE_WATER_FLOW_LARGE = ("moderate", 240)
65    PURE_WATER_SUPER_BEGIN = ("pure_water_super_begin", 245, "high")
66    PURE_WATER_FLOW_END = ("pure_water_flow_end", 250, "extreme")

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'>
SMART_WATER = <WaterModes.SMART_WATER: 'smart_water'>
PURE_WATER_FLOW_START = <WaterModes.PURE_WATER_FLOW_START: 'slight'>
PURE_WATER_FLOW_SMALL = <WaterModes.PURE_WATER_FLOW_SMALL: 'pure_water_flow_small'>
PURE_WATER_FLOW_GENTLE = <WaterModes.PURE_WATER_FLOW_GENTLE: 'gentle'>
PURE_WATER_FLOW_MIDDLE = <WaterModes.PURE_WATER_FLOW_MIDDLE: 'pure_water_flow_middle'>
PURE_WATER_FLOW_LARGE = <WaterModes.PURE_WATER_FLOW_LARGE: 'moderate'>
PURE_WATER_SUPER_BEGIN = <WaterModes.PURE_WATER_SUPER_BEGIN: 'pure_water_super_begin'>
PURE_WATER_FLOW_END = <WaterModes.PURE_WATER_FLOW_END: 'pure_water_flow_end'>
class WashTowelModes(roborock.data.code_mappings.RoborockModeEnum):
69class WashTowelModes(RoborockModeEnum):
70    SMART = ("smart", 10)
71    LIGHT = ("light", 0)
72    BALANCED = ("balanced", 1)
73    DEEP = ("deep", 2)
74    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):
77class CleaningMode(StrEnum):
78    """High-level cleaning intent derived from the lower-level motor settings.
79
80    Prefer this abstraction when you want to present or switch between the
81    user-facing cleaning behaviors exposed by the app. The lower-level
82    `VacuumModes`, `WaterModes`, and `CleanRoutes` enums are still useful for
83    fine-grained selection.
84    """
85
86    VACUUM = "vacuum"
87    VAC_AND_MOP = "vac_and_mop"
88    MOP = "mop"
89    CUSTOM = "custom"
90    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'>
def get_wash_towel_modes( features: roborock.device_features.DeviceFeatures) -> list[WashTowelModes]:
119def get_wash_towel_modes(features: DeviceFeatures) -> list[WashTowelModes]:
120    """Get the valid wash towel modes for the device"""
121    modes = [WashTowelModes.LIGHT, WashTowelModes.BALANCED, WashTowelModes.DEEP]
122    if features.is_super_deep_wash_supported and not features.is_dirty_replenish_clean_supported:
123        modes.append(WashTowelModes.SUPER_DEEP)
124    elif features.is_dirty_replenish_clean_supported:
125        modes.append(WashTowelModes.SMART)
126    return modes

Get the valid wash towel modes for the device

def get_clean_modes( features: roborock.device_features.DeviceFeatures) -> list[VacuumModes]:
129def get_clean_modes(features: DeviceFeatures) -> list[VacuumModes]:
130    """Get the valid clean modes for the device - also known as 'fan power' or 'suction mode'"""
131    modes = [VacuumModes.QUIET, VacuumModes.BALANCED, VacuumModes.TURBO, VacuumModes.MAX]
132    if features.is_max_plus_mode_supported or features.is_none_pure_clean_mop_with_max_plus:
133        # If the vacuum has max plus mode supported
134        modes.append(VacuumModes.MAX_PLUS)
135    if features.is_pure_clean_mop_supported:
136        # If the vacuum is capable of 'pure mop clean' aka no vacuum
137        modes.append(VacuumModes.OFF)
138    else:
139        # If not, we can add gentle
140        modes.append(VacuumModes.GENTLE)
141    if features.is_smart_clean_mode_set_supported:
142        modes.append(VacuumModes.SMART_MODE)
143    if features.is_customized_clean_supported:
144        modes.append(VacuumModes.CUSTOMIZED)
145    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]:
148def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]:
149    """The routes that the vacuum will take while mopping"""
150    if not features.is_clean_route_setting_supported:
151        return []
152    if features.is_none_pure_clean_mop_with_max_plus:
153        return [CleanRoutes.FAST, CleanRoutes.STANDARD]
154    supported = [CleanRoutes.STANDARD]
155    if not features.is_clean_efficiency_supported:
156        supported.append(CleanRoutes.DEEP)
157    if features.is_careful_slow_mop_supported:
158        if (
159            not features.is_corner_clean_mode_supported
160            and features.is_clean_route_deep_slow_plus_supported
161            and region == "cn"
162        ):
163            supported.append(CleanRoutes.DEEP_PLUS_CN)
164        else:
165            supported.append(CleanRoutes.DEEP_PLUS)
166
167    if features.is_clean_route_fast_mode_supported:
168        supported.append(CleanRoutes.FAST)
169    if features.is_smart_clean_mode_set_supported:
170        supported.append(CleanRoutes.SMART_MODE)
171    if features.is_customized_clean_supported:
172        supported.append(CleanRoutes.CUSTOMIZED)
173
174    return supported

The routes that the vacuum will take while mopping

def get_water_modes( features: roborock.device_features.DeviceFeatures) -> list[WaterModes]:
177def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
178    """Get the valid water modes for the device - also known as 'water flow' or 'water level'"""
179    # Water slide mode supports a separate set of water flow codes.
180    if features.is_water_slide_mode_supported:
181        return list(_WATER_SLIDE_MODES)
182
183    supported_modes = [WaterModes.OFF]
184    if features.is_mop_shake_module_supported:
185        # For mops that have the vibrating mop pad, they do mild standard intense
186        supported_modes.extend([WaterModes.MILD, WaterModes.STANDARD, WaterModes.INTENSE])
187    else:
188        supported_modes.extend([WaterModes.LOW, WaterModes.MEDIUM, WaterModes.HIGH])
189    if features.is_custom_water_box_distance_supported:
190        # This is for devices that allow you to set a custom water flow from 0-100
191        supported_modes.append(WaterModes.CUSTOM)
192    if features.is_mop_shake_module_supported and features.is_mop_shake_water_max_supported:
193        supported_modes.append(WaterModes.EXTREME)
194    if features.is_smart_clean_mode_set_supported:
195        supported_modes.append(WaterModes.SMART_MODE)
196    if features.is_customized_clean_supported:
197        supported_modes.append(WaterModes.CUSTOMIZED)
198
199    return supported_modes

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

def get_water_slide_mode_name(code: int) -> str | None:
202def get_water_slide_mode_name(code: int) -> str | None:
203    """Get the app's range name for a water slide code, or None if out of range."""
204    mode = _resolve_water_slide_mode(code)
205    return mode.display_name if mode is not None else None

Get the app's range name for a water slide code, or None if out of range.

def get_water_mode_mapping(features: roborock.device_features.DeviceFeatures) -> dict[int, str]:
223def get_water_mode_mapping(features: DeviceFeatures) -> dict[int, str]:
224    """Get water mode mapping by supported feature set.
225
226    Water slide modes share user-facing names with legacy modes (e.g. "low" is
227    201 or 225), so the mapping uses display_name rather than the enum value.
228    """
229    return {mode.code: mode.display_name for mode in get_water_modes(features)}

Get water mode mapping by supported feature set.

Water slide modes share user-facing names with legacy modes (e.g. "low" is 201 or 225), so the mapping uses display_name rather than the enum value.

def get_cleaning_mode_options( features: roborock.device_features.DeviceFeatures) -> list[CleaningMode]:
232def get_cleaning_mode_options(features: DeviceFeatures) -> list[CleaningMode]:
233    """Return the supported high-level cleaning modes for the device.
234
235    These options are the preferred user-facing choices because they bundle the
236    correct fan, water, and mop-route settings together for the device. Callers
237    should generally present these instead of mixing lower-level mode enums
238    unless they explicitly need fine-grained control.
239    """
240    if not features.is_support_water_mode:
241        return []
242
243    supported_water_modes = get_water_modes(features)
244    options = [CleaningMode.VACUUM, CleaningMode.VAC_AND_MOP]
245    if features.is_pure_clean_mop_supported:
246        options.append(CleaningMode.MOP)
247    if features.is_customized_clean_supported and WaterModes.CUSTOMIZED in supported_water_modes:
248        options.append(CleaningMode.CUSTOM)
249    if features.is_smart_clean_mode_set_supported and WaterModes.SMART_MODE in supported_water_modes:
250        options.append(CleaningMode.SMART_MODE)
251    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:
254def get_mop_only_vacuum_mode(features: DeviceFeatures) -> VacuumModes:
255    """Return the vacuum mode used by the app for mop-only cleaning."""
256    if not features.is_pure_clean_mop_supported:
257        raise RoborockUnsupportedFeature("Mop-only cleaning is not supported")
258    # Main-brush lift is a device capability, not an alternate fan-power
259    # command. The app still sends CleanModeZero (105) for mop-only cleaning.
260    return VacuumModes.OFF

Return the vacuum mode used by the app for mop-only cleaning.

def resolve_cleaning_mode( cleaning_mode: str | CleaningMode) -> CleaningMode:
294def resolve_cleaning_mode(cleaning_mode: str | CleaningMode) -> CleaningMode:
295    """Resolve a string or enum into a CleaningMode value."""
296    if isinstance(cleaning_mode, CleaningMode):
297        return cleaning_mode
298    try:
299        return CleaningMode(cleaning_mode)
300    except ValueError as err:
301        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]]:
304def get_cleaning_mode_parameters(cleaning_mode: CleaningMode, features: DeviceFeatures) -> list[dict[str, int]]:
305    """Get the RPC payload for switching the high-level cleaning mode."""
306    if cleaning_mode not in get_cleaning_mode_options(features):
307        raise RoborockUnsupportedFeature(f"Cleaning mode {cleaning_mode.value!r} is not supported")
308
309    fan_power, water_box_mode, mop_mode = _get_clean_motor_mode_params(cleaning_mode, features)
310    params: dict[str, int] = {"fan_power": fan_power.code, "water_box_mode": water_box_mode.code}
311    if features.is_clean_route_setting_supported:
312        params["mop_mode"] = mop_mode.code
313    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:
345def get_current_cleaning_mode(
346    clean_mode: int | VacuumModes | None,
347    water_mode: int | WaterModes | None,
348    mop_mode: int | CleanRoutes | None,
349    features: DeviceFeatures,
350) -> CleaningMode | None:
351    """Classify the current high-level cleaning mode from individual mode codes."""
352    if not features.is_support_water_mode:
353        return None
354    clean_mode_enum = _resolve_clean_mode(clean_mode, features)
355    water_mode_enum = _resolve_water_mode(water_mode, features)
356    mop_mode_enum = _resolve_mode_code(mop_mode, CleanRoutes)
357    if clean_mode_enum is None or water_mode_enum is None:
358        return None
359
360    if is_smart_mode_set(water_mode_enum, clean_mode_enum, mop_mode_enum):
361        return CleaningMode.SMART_MODE
362    if is_mode_customized(clean_mode_enum, water_mode_enum, mop_mode_enum):
363        return CleaningMode.CUSTOM
364    if water_mode_enum != WaterModes.OFF:
365        try:
366            if clean_mode_enum == get_mop_only_vacuum_mode(features):
367                return CleaningMode.MOP
368        except RoborockUnsupportedFeature:
369            pass
370    if water_mode_enum == WaterModes.OFF:
371        return CleaningMode.VACUUM
372    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:
375def is_mode_customized(
376    clean_mode: VacuumModes | None,
377    water_mode: WaterModes | None,
378    mop_mode: CleanRoutes | None,
379) -> bool:
380    """Check if any of the cleaning modes are set to a custom value."""
381    return (
382        clean_mode == VacuumModes.CUSTOMIZED
383        or water_mode == WaterModes.CUSTOMIZED
384        or mop_mode == CleanRoutes.CUSTOMIZED
385    )

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:
388def is_smart_mode_set(
389    water_mode: WaterModes | None,
390    clean_mode: VacuumModes | None,
391    mop_mode: CleanRoutes | None,
392) -> bool:
393    """Check if the smart mode is set for the given water mode and clean mode"""
394    return (
395        water_mode == WaterModes.SMART_MODE
396        or clean_mode == VacuumModes.SMART_MODE
397        or mop_mode == CleanRoutes.SMART_MODE
398    )

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