Skip to content

Commit c6ba0d6

Browse files
authored
feat: add device_features to automatically determine what is supported (#365)
* feat: add device_features to automatically determine what is supported * chore: update to the version from the other PR * chore: move device_features to seperate file and add some tests and rework device_features
1 parent cbd6df2 commit c6ba0d6

File tree

4 files changed

+481
-138
lines changed

4 files changed

+481
-138
lines changed

roborock/code_mappings.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import logging
4+
from collections import namedtuple
45
from enum import Enum, IntEnum
56

67
_LOGGER = logging.getLogger(__name__)
@@ -50,6 +51,68 @@ def items(cls: type[RoborockEnum]):
5051
return cls.as_dict().items()
5152

5253

54+
ProductInfo = namedtuple("ProductInfo", ["nickname", "short_models"])
55+
56+
57+
class RoborockProductNickname(Enum):
58+
# Coral Series
59+
CORAL = ProductInfo(nickname="Coral", short_models=("a20", "a21"))
60+
CORALPRO = ProductInfo(nickname="CoralPro", short_models=("a143", "a144"))
61+
62+
# Pearl Series
63+
PEARL = ProductInfo(nickname="Pearl", short_models=("a74", "a75"))
64+
PEARLC = ProductInfo(nickname="PearlC", short_models=("a103", "a104"))
65+
PEARLE = ProductInfo(nickname="PearlE", short_models=("a167", "a168"))
66+
PEARLELITE = ProductInfo(nickname="PearlELite", short_models=("a169", "a170"))
67+
PEARLPLUS = ProductInfo(nickname="PearlPlus", short_models=("a86", "a87"))
68+
PEARLPLUSS = ProductInfo(nickname="PearlPlusS", short_models=("a116", "a117", "a136"))
69+
PEARLS = ProductInfo(nickname="PearlS", short_models=("a100", "a101"))
70+
PEARLSLITE = ProductInfo(nickname="PearlSLite", short_models=("a122", "a123"))
71+
72+
# Ruby Series
73+
RUBYPLUS = ProductInfo(nickname="RubyPlus", short_models=("t4", "s4"))
74+
RUBYSC = ProductInfo(nickname="RubySC", short_models=("p5", "a08"))
75+
RUBYSE = ProductInfo(nickname="RubySE", short_models=("a19",))
76+
RUBYSLITE = ProductInfo(nickname="RubySLite", short_models=("p6", "s5e", "a05"))
77+
78+
# Tanos Series
79+
TANOS = ProductInfo(nickname="Tanos", short_models=("t6", "s6"))
80+
TANOSE = ProductInfo(nickname="TanosE", short_models=("t7", "a11"))
81+
TANOSS = ProductInfo(nickname="TanosS", short_models=("a14", "a15"))
82+
TANOSSC = ProductInfo(nickname="TanosSC", short_models=("a39", "a40"))
83+
TANOSSE = ProductInfo(nickname="TanosSE", short_models=("a33", "a34"))
84+
TANOSSMAX = ProductInfo(nickname="TanosSMax", short_models=("a52",))
85+
TANOSSLITE = ProductInfo(nickname="TanosSLite", short_models=("a37", "a38"))
86+
TANOSSPLUS = ProductInfo(nickname="TanosSPlus", short_models=("a23", "a24"))
87+
TANOSV = ProductInfo(nickname="TanosV", short_models=("t7p", "a09", "a10"))
88+
89+
# Topaz Series
90+
TOPAZS = ProductInfo(nickname="TopazS", short_models=("a29", "a30", "a76"))
91+
TOPAZSC = ProductInfo(nickname="TopazSC", short_models=("a64", "a65"))
92+
TOPAZSPLUS = ProductInfo(nickname="TopazSPlus", short_models=("a46", "a47", "a66"))
93+
TOPAZSPOWER = ProductInfo(nickname="TopazSPower", short_models=("a62",))
94+
TOPAZSV = ProductInfo(nickname="TopazSV", short_models=("a26", "a27"))
95+
96+
# Ultron Series
97+
ULTRON = ProductInfo(nickname="Ultron", short_models=("a50", "a51"))
98+
ULTRONE = ProductInfo(nickname="UltronE", short_models=("a72", "a84"))
99+
ULTRONLITE = ProductInfo(nickname="UltronLite", short_models=("a73", "a85"))
100+
ULTRONSC = ProductInfo(nickname="UltronSC", short_models=("a94", "a95"))
101+
ULTRONSE = ProductInfo(nickname="UltronSE", short_models=("a124", "a125", "a139", "a140"))
102+
ULTRONSPLUS = ProductInfo(nickname="UltronSPlus", short_models=("a68", "a69", "a70"))
103+
ULTRONSV = ProductInfo(nickname="UltronSV", short_models=("a96", "a97"))
104+
105+
# Verdelite Series
106+
VERDELITE = ProductInfo(nickname="Verdelite", short_models=("a146", "a147"))
107+
108+
# Vivian Series
109+
VIVIAN = ProductInfo(nickname="Vivian", short_models=("a134", "a135", "a155", "a156"))
110+
VIVIANC = ProductInfo(nickname="VivianC", short_models=("a158", "a159"))
111+
112+
113+
SHORT_MODEL_TO_ENUM = {model: product for product in RoborockProductNickname for model in product.value.short_models}
114+
115+
53116
class RoborockStateCode(RoborockEnum):
54117
unknown = 0
55118
starting = 1

roborock/containers.py

Lines changed: 8 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import Any, NamedTuple, get_args, get_origin
1212

1313
from .code_mappings import (
14+
SHORT_MODEL_TO_ENUM,
1415
RoborockCategory,
1516
RoborockCleanType,
1617
RoborockDockDustCollectionModeCode,
@@ -53,6 +54,7 @@
5354
RoborockMopModeS8ProUltra,
5455
RoborockMopModeSaros10,
5556
RoborockMopModeSaros10R,
57+
RoborockProductNickname,
5658
RoborockStartType,
5759
RoborockStateCode,
5860
)
@@ -87,6 +89,7 @@
8789
STRAINER_REPLACE_TIME,
8890
ROBOROCK_G20S_Ultra,
8991
)
92+
from .device_features import DeviceFeatures
9093
from .exceptions import RoborockException
9194

9295
_LOGGER = logging.getLogger(__name__)
@@ -306,144 +309,6 @@ class HomeDataDevice(RoborockBase):
306309
silent_ota_switch: bool | None = None
307310
setting: Any | None = None
308311
f: bool | None = None
309-
device_features: DeviceFeatures | None = None
310-
311-
# seemingly not just str like I thought - example: '0000000000002000' and '0000000000002F63'
312-
313-
# def __post_init__(self):
314-
# if self.feature_set is not None and self.new_feature_set is not None and self.new_feature_set != "":
315-
# self.device_features = build_device_features(self.feature_set, self.new_feature_set)
316-
317-
318-
@dataclass
319-
class DeviceFeatures(RoborockBase):
320-
map_carpet_add_supported: bool
321-
show_clean_finish_reason_supported: bool
322-
resegment_supported: bool
323-
video_monitor_supported: bool
324-
any_state_transit_goto_supported: bool
325-
fw_filter_obstacle_supported: bool
326-
video_setting_supported: bool
327-
ignore_unknown_map_object_supported: bool
328-
set_child_supported: bool
329-
carpet_supported: bool
330-
mop_path_supported: bool
331-
multi_map_segment_timer_supported: bool
332-
custom_water_box_distance_supported: bool
333-
wash_then_charge_cmd_supported: bool
334-
room_name_supported: bool
335-
current_map_restore_enabled: bool
336-
photo_upload_supported: bool
337-
shake_mop_set_supported: bool
338-
map_beautify_internal_debug_supported: bool
339-
new_data_for_clean_history: bool
340-
new_data_for_clean_history_detail: bool
341-
flow_led_setting_supported: bool
342-
dust_collection_setting_supported: bool
343-
rpc_retry_supported: bool
344-
avoid_collision_supported: bool
345-
support_set_switch_map_mode: bool
346-
support_smart_scene: bool
347-
support_floor_edit: bool
348-
support_furniture: bool
349-
support_room_tag: bool
350-
support_quick_map_builder: bool
351-
support_smart_global_clean_with_custom_mode: bool
352-
record_allowed: bool
353-
careful_slow_map_supported: bool
354-
egg_mode_supported: bool
355-
unsave_map_reason_supported: bool
356-
carpet_show_on_map: bool
357-
supported_valley_electricity: bool
358-
drying_supported: bool
359-
download_test_voice_supported: bool
360-
support_backup_map: bool
361-
support_custom_mode_in_cleaning: bool
362-
support_remote_control_in_call: bool
363-
support_set_volume_in_call: bool
364-
support_clean_estimate: bool
365-
support_custom_dnd: bool
366-
carpet_deep_clean_supported: bool
367-
stuck_zone_supported: bool
368-
custom_door_sill_supported: bool
369-
clean_route_fast_mode_supported: bool
370-
cliff_zone_supported: bool
371-
smart_door_sill_supported: bool
372-
support_floor_direction: bool
373-
wifi_manage_supported: bool
374-
back_charge_auto_wash_supported: bool
375-
support_incremental_map: bool
376-
offline_map_supported: bool
377-
378-
379-
def build_device_features(feature_set: str, new_feature_set: str) -> DeviceFeatures:
380-
new_feature_set_int = int(new_feature_set)
381-
feature_set_int = int(feature_set)
382-
new_feature_set_divided = int(new_feature_set_int / (2**32))
383-
# Convert last 8 digits of new feature set into hexadecimal number
384-
converted_new_feature_set = int("0x" + new_feature_set[-8:], 16)
385-
new_feature_set_mod_8: bool = len(new_feature_set) % 8 == 0
386-
return DeviceFeatures(
387-
map_carpet_add_supported=bool(1073741824 & new_feature_set_int),
388-
show_clean_finish_reason_supported=bool(1 & new_feature_set_int),
389-
resegment_supported=bool(4 & new_feature_set_int),
390-
video_monitor_supported=bool(8 & new_feature_set_int),
391-
any_state_transit_goto_supported=bool(16 & new_feature_set_int),
392-
fw_filter_obstacle_supported=bool(32 & new_feature_set_int),
393-
video_setting_supported=bool(64 & new_feature_set_int),
394-
ignore_unknown_map_object_supported=bool(128 & new_feature_set_int),
395-
set_child_supported=bool(256 & new_feature_set_int),
396-
carpet_supported=bool(512 & new_feature_set_int),
397-
mop_path_supported=bool(2048 & new_feature_set_int),
398-
multi_map_segment_timer_supported=bool(feature_set_int and 4096 & new_feature_set_int),
399-
custom_water_box_distance_supported=bool(new_feature_set_int and 2147483648 & new_feature_set_int),
400-
wash_then_charge_cmd_supported=bool((new_feature_set_divided >> 5) & 1),
401-
room_name_supported=bool(16384 & new_feature_set_int),
402-
current_map_restore_enabled=bool(8192 & new_feature_set_int),
403-
photo_upload_supported=bool(65536 & new_feature_set_int),
404-
shake_mop_set_supported=bool(262144 & new_feature_set_int),
405-
map_beautify_internal_debug_supported=bool(2097152 & new_feature_set_int),
406-
new_data_for_clean_history=bool(4194304 & new_feature_set_int),
407-
new_data_for_clean_history_detail=bool(8388608 & new_feature_set_int),
408-
flow_led_setting_supported=bool(16777216 & new_feature_set_int),
409-
dust_collection_setting_supported=bool(33554432 & new_feature_set_int),
410-
rpc_retry_supported=bool(67108864 & new_feature_set_int),
411-
avoid_collision_supported=bool(134217728 & new_feature_set_int),
412-
support_set_switch_map_mode=bool(268435456 & new_feature_set_int),
413-
support_smart_scene=bool(new_feature_set_divided & 2),
414-
support_floor_edit=bool(new_feature_set_divided & 8),
415-
support_furniture=bool((new_feature_set_divided >> 4) & 1),
416-
support_room_tag=bool((new_feature_set_divided >> 6) & 1),
417-
support_quick_map_builder=bool((new_feature_set_divided >> 7) & 1),
418-
support_smart_global_clean_with_custom_mode=bool((new_feature_set_divided >> 8) & 1),
419-
record_allowed=bool(1024 & new_feature_set_int),
420-
careful_slow_map_supported=bool((new_feature_set_divided >> 9) & 1),
421-
egg_mode_supported=bool((new_feature_set_divided >> 10) & 1),
422-
unsave_map_reason_supported=bool((new_feature_set_divided >> 14) & 1),
423-
carpet_show_on_map=bool((new_feature_set_divided >> 12) & 1),
424-
supported_valley_electricity=bool((new_feature_set_divided >> 13) & 1),
425-
# This one could actually be incorrect
426-
# ((t.robotNewFeatures / 2 ** 32) >> 15) & 1 && (module422.DMM.isTopazSV_CE || 'cn' == t.deviceLocation));
427-
drying_supported=bool((new_feature_set_divided >> 15) & 1),
428-
download_test_voice_supported=bool((new_feature_set_divided >> 16) & 1),
429-
support_backup_map=bool((new_feature_set_divided >> 17) & 1),
430-
support_custom_mode_in_cleaning=bool((new_feature_set_divided >> 18) & 1),
431-
support_remote_control_in_call=bool((new_feature_set_divided >> 19) & 1),
432-
support_set_volume_in_call=new_feature_set_mod_8 and bool(1 & converted_new_feature_set),
433-
support_clean_estimate=new_feature_set_mod_8 and bool(2 & converted_new_feature_set),
434-
support_custom_dnd=new_feature_set_mod_8 and bool(4 & converted_new_feature_set),
435-
carpet_deep_clean_supported=bool(8 & converted_new_feature_set),
436-
stuck_zone_supported=new_feature_set_mod_8 and bool(16 & converted_new_feature_set),
437-
custom_door_sill_supported=new_feature_set_mod_8 and bool(32 & converted_new_feature_set),
438-
clean_route_fast_mode_supported=bool(256 & converted_new_feature_set),
439-
cliff_zone_supported=new_feature_set_mod_8 and bool(512 & converted_new_feature_set),
440-
smart_door_sill_supported=new_feature_set_mod_8 and bool(1024 & converted_new_feature_set),
441-
support_floor_direction=new_feature_set_mod_8 and bool(2048 & converted_new_feature_set),
442-
wifi_manage_supported=bool(128 & converted_new_feature_set),
443-
back_charge_auto_wash_supported=bool(4096 & converted_new_feature_set),
444-
support_incremental_map=bool(8192 & converted_new_feature_set),
445-
offline_map_supported=bool(16384 & converted_new_feature_set),
446-
)
447312

448313

449314
@dataclass
@@ -890,6 +755,11 @@ class DeviceData(RoborockBase):
890755
device: HomeDataDevice
891756
model: str
892757
host: str | None = None
758+
product_nickname: RoborockProductNickname | None = None
759+
device_features: DeviceFeatures | None = None
760+
761+
def __post_init__(self):
762+
self.product_nickname = SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
893763

894764

895765
@dataclass

0 commit comments

Comments
 (0)