Skip to content

Commit 27fb9fc

Browse files
authored
feat: improve dynamic clean modes (#448)
* feat: add dynamic clean modes * feat: add dynamic clean modes * chore: comment * feat: improve dynamic clean * chore: remove more complicated changes * chore: remove more complicated changes * chore: lint * fix: remove functon * fix: tchange name of cleanmodesold * chore: cap product feature map
1 parent 59d73f3 commit 27fb9fc

File tree

3 files changed

+104
-19
lines changed

3 files changed

+104
-19
lines changed

roborock/clean_modes.py

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from __future__ import annotations
22

3-
from roborock import DeviceFeatures
4-
53
from .code_mappings import RoborockModeEnum
4+
from .device_features import DeviceFeatures
65

76

8-
class CleanModes(RoborockModeEnum):
7+
class VacuumModes(RoborockModeEnum):
98
GENTLE = ("gentle", 105)
109
OFF = ("off", 105)
1110
QUIET = ("quiet", 101)
@@ -27,7 +26,7 @@ class CleanRoutes(RoborockModeEnum):
2726
CUSTOMIZED = ("custom", 302)
2827

2928

30-
class CleanModesOld(RoborockModeEnum):
29+
class VacuumModesOld(RoborockModeEnum):
3130
QUIET = ("quiet", 38)
3231
BALANCED = ("balanced", 60)
3332
TURBO = ("turbo", 75)
@@ -48,18 +47,40 @@ class WaterModes(RoborockModeEnum):
4847
SMART_MODE = ("smart_mode", 209)
4948

5049

51-
def get_clean_modes(features: DeviceFeatures) -> list[CleanModes]:
50+
class WashTowelModes(RoborockModeEnum):
51+
SMART = ("smart", 10)
52+
LIGHT = ("light", 0)
53+
BALANCED = ("balanced", 1)
54+
DEEP = ("deep", 2)
55+
SUPER_DEEP = ("super_deep", 8)
56+
57+
58+
def get_wash_towel_modes(features: DeviceFeatures) -> list[WashTowelModes]:
59+
"""Get the valid wash towel modes for the device"""
60+
modes = [WashTowelModes.LIGHT, WashTowelModes.BALANCED, WashTowelModes.DEEP]
61+
if features.is_super_deep_wash_supported and not features.is_dirty_replenish_clean_supported:
62+
modes.append(WashTowelModes.SUPER_DEEP)
63+
elif features.is_dirty_replenish_clean_supported:
64+
modes.append(WashTowelModes.SMART)
65+
return modes
66+
67+
68+
def get_clean_modes(features: DeviceFeatures) -> list[VacuumModes]:
5269
"""Get the valid clean modes for the device - also known as 'fan power' or 'suction mode'"""
53-
modes = [CleanModes.QUIET, CleanModes.BALANCED, CleanModes.TURBO, CleanModes.MAX]
70+
modes = [VacuumModes.QUIET, VacuumModes.BALANCED, VacuumModes.TURBO, VacuumModes.MAX]
5471
if features.is_max_plus_mode_supported or features.is_none_pure_clean_mop_with_max_plus:
5572
# If the vacuum has max plus mode supported
56-
modes.append(CleanModes.MAX_PLUS)
73+
modes.append(VacuumModes.MAX_PLUS)
5774
if features.is_pure_clean_mop_supported:
5875
# If the vacuum is capable of 'pure mop clean' aka no vacuum
59-
modes.append(CleanModes.OFF)
76+
modes.append(VacuumModes.OFF)
6077
else:
6178
# If not, we can add gentle
62-
modes.append(CleanModes.GENTLE)
79+
modes.append(VacuumModes.GENTLE)
80+
if features.is_smart_clean_mode_set_supported:
81+
modes.append(VacuumModes.SMART_MODE)
82+
if features.is_customized_clean_supported:
83+
modes.append(VacuumModes.CUSTOMIZED)
6384
return modes
6485

6586

@@ -72,7 +93,7 @@ def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]
7293
if not (
7394
features.is_corner_clean_mode_supported
7495
and features.is_clean_route_deep_slow_plus_supported
75-
and region == "CN"
96+
and region == "cn"
7697
):
7798
# for some reason there is a china specific deep plus mode
7899
supported.append(CleanRoutes.DEEP_PLUS_CN)
@@ -81,6 +102,11 @@ def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]
81102

82103
if features.is_clean_route_fast_mode_supported:
83104
supported.append(CleanRoutes.FAST)
105+
if features.is_smart_clean_mode_set_supported:
106+
supported.append(CleanRoutes.SMART_MODE)
107+
if features.is_customized_clean_supported:
108+
supported.append(CleanRoutes.CUSTOMIZED)
109+
84110
return supported
85111

86112

@@ -97,4 +123,18 @@ def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
97123
supported_modes.append(WaterModes.CUSTOM)
98124
if features.is_mop_shake_module_supported and features.is_mop_shake_water_max_supported:
99125
supported_modes.append(WaterModes.EXTREME)
126+
if features.is_smart_clean_mode_set_supported:
127+
supported_modes.append(WaterModes.SMART_MODE)
128+
if features.is_customized_clean_supported:
129+
supported_modes.append(WaterModes.CUSTOMIZED)
130+
100131
return supported_modes
132+
133+
134+
def is_smart_mode_set(water_mode: WaterModes, clean_mode: VacuumModes, mop_mode: CleanRoutes) -> bool:
135+
"""Check if the smart mode is set for the given water mode and clean mode"""
136+
return (
137+
water_mode == WaterModes.SMART_MODE
138+
or clean_mode == VacuumModes.SMART_MODE
139+
or mop_mode == CleanRoutes.SMART_MODE
140+
)

roborock/device_features.py

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,45 @@ class ProductFeatures(StrEnum):
116116
NEW_DEFAULT_FEATURES = [ProductFeatures.REMOTE_BACK, ProductFeatures.CLEANMODE_MAXPLUS]
117117

118118

119-
PEARL_FEATURES = NEW_DEFAULT_FEATURES + SINGLE_LINE_CAMERA_FEATURES + [ProductFeatures.MOP_SPIN_MODULE]
119+
PEARL_FEATURES = SINGLE_LINE_CAMERA_FEATURES + [ProductFeatures.CLEANMODE_MAXPLUS, ProductFeatures.MOP_SPIN_MODULE]
120120
PEARL_PLUS_FEATURES = NEW_DEFAULT_FEATURES + RGB_CAMERA_FEATURES + [ProductFeatures.MOP_SPIN_MODULE]
121121
ULTRON_FEATURES = NEW_DEFAULT_FEATURES + DUAL_LINE_CAMERA_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
122122
ULTRONSV_FEATURES = NEW_DEFAULT_FEATURES + RGB_CAMERA_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
123-
TANOSS_FEATURES = NEW_DEFAULT_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
124-
TOPAZSPOWER_FEATURES = NEW_DEFAULT_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE]
123+
TANOSS_FEATURES = [ProductFeatures.REMOTE_BACK, ProductFeatures.MOP_SHAKE_MODULE]
124+
TOPAZSPOWER_FEATURES = [ProductFeatures.CLEANMODE_MAXPLUS, ProductFeatures.MOP_SHAKE_MODULE]
125125

126-
product_feature_map = {
126+
PRODUCTS_WITHOUT_CUSTOM_CLEAN: set[RoborockProductNickname] = {
127+
RoborockProductNickname.TANOS,
128+
RoborockProductNickname.RUBYPLUS,
129+
RoborockProductNickname.RUBYSC,
130+
RoborockProductNickname.RUBYSE,
131+
}
132+
PRODUCTS_WITHOUT_DEFAULT_3D_MAP: set[RoborockProductNickname] = {
133+
RoborockProductNickname.TANOS,
134+
RoborockProductNickname.TANOSSPLUS,
135+
RoborockProductNickname.TANOSE,
136+
RoborockProductNickname.TANOSV,
137+
RoborockProductNickname.RUBYPLUS,
138+
RoborockProductNickname.RUBYSC,
139+
RoborockProductNickname.RUBYSE,
140+
}
141+
PRODUCTS_WITHOUT_PURE_CLEAN_MOP: set[RoborockProductNickname] = {
142+
RoborockProductNickname.TANOS,
143+
RoborockProductNickname.TANOSE,
144+
RoborockProductNickname.TANOSV,
145+
RoborockProductNickname.TANOSSLITE,
146+
RoborockProductNickname.TANOSSE,
147+
RoborockProductNickname.TANOSSC,
148+
RoborockProductNickname.ULTRONLITE,
149+
RoborockProductNickname.ULTRONE,
150+
RoborockProductNickname.RUBYPLUS,
151+
RoborockProductNickname.RUBYSLITE,
152+
RoborockProductNickname.RUBYSC,
153+
RoborockProductNickname.RUBYSE,
154+
}
155+
156+
# Base map containing the initial, unconditional features for each product.
157+
_BASE_PRODUCT_FEATURE_MAP: dict[RoborockProductNickname, list[ProductFeatures]] = {
127158
RoborockProductNickname.PEARL: PEARL_FEATURES,
128159
RoborockProductNickname.PEARLS: PEARL_FEATURES,
129160
RoborockProductNickname.PEARLPLUS: PEARL_PLUS_FEATURES,
@@ -139,7 +170,8 @@ class ProductFeatures(StrEnum):
139170
RoborockProductNickname.PEARLSLITE: PEARL_FEATURES,
140171
RoborockProductNickname.PEARLE: PEARL_FEATURES,
141172
RoborockProductNickname.PEARLELITE: PEARL_FEATURES,
142-
RoborockProductNickname.VIVIANC: PEARL_PLUS_FEATURES,
173+
RoborockProductNickname.VIVIANC: [ProductFeatures.CLEANMODE_MAXPLUS, ProductFeatures.MOP_SPIN_MODULE]
174+
+ SINGLE_LINE_CAMERA_FEATURES,
143175
RoborockProductNickname.CORALPRO: PEARL_PLUS_FEATURES,
144176
RoborockProductNickname.ULTRONLITE: SINGLE_LINE_CAMERA_FEATURES
145177
+ [ProductFeatures.CLEANMODE_NONE_PURECLEANMOP_WITH_MAXPLUS, ProductFeatures.MOP_ELECTRONIC_MODULE],
@@ -150,7 +182,7 @@ class ProductFeatures(StrEnum):
150182
],
151183
RoborockProductNickname.ULTRONSPLUS: ULTRON_FEATURES,
152184
RoborockProductNickname.VERDELITE: ULTRONSV_FEATURES,
153-
RoborockProductNickname.TOPAZS: NEW_DEFAULT_FEATURES + [ProductFeatures.MOP_SHAKE_MODULE],
185+
RoborockProductNickname.TOPAZS: [ProductFeatures.REMOTE_BACK, ProductFeatures.MOP_SHAKE_MODULE],
154186
RoborockProductNickname.TOPAZSPLUS: NEW_DEFAULT_FEATURES
155187
+ DUAL_LINE_CAMERA_FEATURES
156188
+ [ProductFeatures.MOP_SHAKE_MODULE],
@@ -173,6 +205,16 @@ class ProductFeatures(StrEnum):
173205
RoborockProductNickname.RUBYSLITE: [ProductFeatures.MOP_ELECTRONIC_MODULE],
174206
}
175207

208+
PRODUCT_FEATURE_MAP: dict[RoborockProductNickname, list[ProductFeatures]] = {
209+
product: (
210+
features
211+
+ ([ProductFeatures.DEFAULT_CLEANMODECUSTOM] if product not in PRODUCTS_WITHOUT_CUSTOM_CLEAN else [])
212+
+ ([ProductFeatures.DEFAULT_MAP3D] if product not in PRODUCTS_WITHOUT_DEFAULT_3D_MAP else [])
213+
+ ([ProductFeatures.CLEANMODE_PURECLEANMOP] if product not in PRODUCTS_WITHOUT_PURE_CLEAN_MOP else [])
214+
)
215+
for product, features in _BASE_PRODUCT_FEATURE_MAP.items()
216+
}
217+
176218

177219
@dataclass
178220
class DeviceFeatures:
@@ -424,6 +466,9 @@ class DeviceFeatures:
424466
metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE, ProductFeatures.MOP_SPIN_MODULE]}
425467
)
426468
is_mop_shake_module_supported: bool = field(metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE]})
469+
is_customized_clean_supported: bool = field(
470+
metadata={"product_features": [ProductFeatures.MOP_SHAKE_MODULE, ProductFeatures.MOP_SPIN_MODULE]}
471+
)
427472

428473
@classmethod
429474
def from_feature_flags(
@@ -493,7 +538,7 @@ def from_feature_flags(
493538
kwargs[f.name] = product_nickname not in blacklist
494539
elif (product_features := f.metadata.get("product_features")) is not None:
495540
if product_nickname is not None:
496-
available_features = product_feature_map.get(product_nickname, [])
541+
available_features = PRODUCT_FEATURE_MAP.get(product_nickname, [])
497542
if any(feat in available_features for feat in product_features): # type: ignore
498543
kwargs[f.name] = True
499544

tests/test_supported_features.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ def test_supported_features_qrevo_maxv():
77
model = "roborock.vacuum.a87"
88
product_nickname = SHORT_MODEL_TO_ENUM.get(model.split(".")[-1])
99
device_features = DeviceFeatures.from_feature_flags(
10-
new_feature_info=2247397454282751,
11-
new_feature_info_str="000A177F7EFEFFFF",
10+
new_feature_info=4499197267967999,
11+
new_feature_info_str="508A977F7EFEFFFF",
1212
feature_info=[111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125],
1313
product_nickname=product_nickname,
1414
)

0 commit comments

Comments
 (0)