|
4 | 4 | import json
|
5 | 5 | import logging
|
6 | 6 | import re
|
7 |
| -from dataclasses import asdict, dataclass, field, fields |
| 7 | +from dataclasses import asdict, dataclass, field |
8 | 8 | from datetime import timezone
|
9 |
| -from enum import Enum, IntEnum |
| 9 | +from enum import Enum |
10 | 10 | from functools import cached_property
|
11 | 11 | from typing import Any, NamedTuple, get_args, get_origin
|
12 | 12 |
|
13 | 13 | from .code_mappings import (
|
| 14 | + SHORT_MODEL_TO_ENUM, |
14 | 15 | RoborockCategory,
|
15 | 16 | RoborockCleanType,
|
16 | 17 | RoborockDockDustCollectionModeCode,
|
|
56 | 57 | RoborockProductNickname,
|
57 | 58 | RoborockStartType,
|
58 | 59 | RoborockStateCode,
|
59 |
| - short_model_to_enum, |
60 | 60 | )
|
61 | 61 | from .const import (
|
62 | 62 | CLEANING_BRUSH_REPLACE_TIME,
|
|
89 | 89 | STRAINER_REPLACE_TIME,
|
90 | 90 | ROBOROCK_G20S_Ultra,
|
91 | 91 | )
|
| 92 | +from .device_features import DeviceFeatures |
92 | 93 | from .exceptions import RoborockException
|
93 | 94 |
|
94 | 95 | _LOGGER = logging.getLogger(__name__)
|
@@ -310,238 +311,6 @@ class HomeDataDevice(RoborockBase):
|
310 | 311 | f: bool | None = None
|
311 | 312 |
|
312 | 313 |
|
313 |
| -class NewFeatureStrBit(IntEnum): |
314 |
| - TWO_KEY_REAL_TIME_VIDEO = 32 |
315 |
| - TWO_KEY_RTV_IN_CHARGING = 33 |
316 |
| - DIRTY_REPLENISH_CLEAN = 34 |
317 |
| - AUTO_DELIVERY_FIELD_IN_GLOBAL_STATUS = 35 |
318 |
| - AVOID_COLLISION_MODE = 36 |
319 |
| - VOICE_CONTROL = 37 |
320 |
| - NEW_ENDPOINT = 38 |
321 |
| - PUMPING_WATER = 39 |
322 |
| - CORNER_MOP_STRECH = 40 |
323 |
| - HOT_WASH_TOWEL = 41 |
324 |
| - FLOOR_DIR_CLEAN_ANY_TIME = 42 |
325 |
| - PET_SUPPLIES_DEEP_CLEAN = 43 |
326 |
| - MOP_SHAKE_WATER_MAX = 45 |
327 |
| - EXACT_CUSTOM_MODE = 47 |
328 |
| - CARPET_CUSTOM_CLEAN = 49 |
329 |
| - PET_SNAPSHOT = 50 |
330 |
| - CUSTOM_CLEAN_MODE_COUNT = 51 |
331 |
| - NEW_AI_RECOGNITION = 52 |
332 |
| - AUTO_COLLECTION_2 = 53 |
333 |
| - RIGHT_BRUSH_STRETCH = 54 |
334 |
| - SMART_CLEAN_MODE_SET = 55 |
335 |
| - DIRTY_OBJECT_DETECT = 56 |
336 |
| - NO_NEED_CARPET_PRESS_SET = 57 |
337 |
| - VOICE_CONTROL_LED = 58 |
338 |
| - WATER_LEAK_CHECK = 60 |
339 |
| - MIN_BATTERY_15_TO_CLEAN_TASK = 62 |
340 |
| - GAP_DEEP_CLEAN = 63 |
341 |
| - OBJECT_DETECT_CHECK = 64 |
342 |
| - IDENTIFY_ROOM = 66 |
343 |
| - MATTER = 67 |
344 |
| - WORKDAY_HOLIDAY = 69 |
345 |
| - CLEAN_DIRECT_STATUS = 70 |
346 |
| - MAP_ERASER = 71 |
347 |
| - OPTIMIZE_BATTERY = 72 |
348 |
| - ACTIVATE_VIDEO_CHARGING_AND_STANDBY = 73 |
349 |
| - CARPET_LONG_HAIRED = 75 |
350 |
| - CLEAN_HISTORY_TIME_LINE = 76 |
351 |
| - MAX_ZONE_OPENED = 77 |
352 |
| - EXHIBITION_FUNCTION = 78 |
353 |
| - LDS_LIFTING = 79 |
354 |
| - AUTO_TEAR_DOWN_MOP = 80 |
355 |
| - SAMLL_SIDE_MOP = 81 |
356 |
| - SUPPORT_SIDE_BRUSH_UP_DOWN = 82 |
357 |
| - DRY_INTERVAL_TIMER = 83 |
358 |
| - UVC_STERILIZE = 84 |
359 |
| - MIDWAY_BACK_TO_DOCK = 85 |
360 |
| - SUPPORT_MAIN_BRUSH_UP_DOWN = 86 |
361 |
| - EGG_DANCE_MODE = 87 |
362 |
| - |
363 |
| - |
364 |
| -@dataclass |
365 |
| -class DeviceFeatures(RoborockBase): |
366 |
| - """Represents the features supported by a Roborock device.""" |
367 |
| - |
368 |
| - # Features derived from robot_new_features |
369 |
| - is_show_clean_finish_reason_supported: bool = field(metadata={"robot_new_features": 1}) |
370 |
| - is_resegment_supported: bool = field(metadata={"robot_new_features": 4}) |
371 |
| - is_video_monitor_supported: bool = field(metadata={"robot_new_features": 8}) |
372 |
| - is_any_state_transit_goto_supported: bool = field(metadata={"robot_new_features": 16}) |
373 |
| - is_fw_filter_obstacle_supported: bool = field(metadata={"robot_new_features": 32}) |
374 |
| - is_video_settings_supported: bool = field(metadata={"robot_new_features": 64}) |
375 |
| - is_ignore_unknown_map_object_supported: bool = field(metadata={"robot_new_features": 128}) |
376 |
| - is_set_child_supported: bool = field(metadata={"robot_new_features": 256}) |
377 |
| - is_carpet_supported: bool = field(metadata={"robot_new_features": 512}) |
378 |
| - is_record_allowed: bool = field(metadata={"robot_new_features": 1024}) |
379 |
| - is_mop_path_supported: bool = field(metadata={"robot_new_features": 2048}) |
380 |
| - is_current_map_restore_enabled: bool = field(metadata={"robot_new_features": 8192}) |
381 |
| - is_room_name_supported: bool = field(metadata={"robot_new_features": 16384}) |
382 |
| - is_photo_upload_supported: bool = field(metadata={"robot_new_features": 65536}) |
383 |
| - is_shake_mop_set_supported: bool = field(metadata={"robot_new_features": 262144}) |
384 |
| - is_map_beautify_internal_debug_supported: bool = field(metadata={"robot_new_features": 2097152}) |
385 |
| - is_new_data_for_clean_history_supported: bool = field(metadata={"robot_new_features": 4194304}) |
386 |
| - is_new_data_for_clean_history_detail_supported: bool = field(metadata={"robot_new_features": 8388608}) |
387 |
| - is_flow_led_setting_supported: bool = field(metadata={"robot_new_features": 16777216}) |
388 |
| - is_dust_collection_setting_supported: bool = field(metadata={"robot_new_features": 33554432}) |
389 |
| - is_rpc_retry_supported: bool = field(metadata={"robot_new_features": 67108864}) |
390 |
| - is_avoid_collision_supported: bool = field(metadata={"robot_new_features": 134217728}) |
391 |
| - is_support_set_switch_map_mode_supported: bool = field(metadata={"robot_new_features": 268435456}) |
392 |
| - is_map_carpet_add_support: bool = field(metadata={"robot_new_features": 1073741824}) |
393 |
| - is_custom_water_box_distance_supported: bool = field(metadata={"robot_new_features": 2147483648}) |
394 |
| - |
395 |
| - # Features derived from unhexed_feature_info |
396 |
| - is_support_smart_scene_supported: bool = field(metadata={"upper_32_bits": 1}) |
397 |
| - is_support_floor_edit_supported: bool = field(metadata={"upper_32_bits": 3}) |
398 |
| - is_support_furniture_supported: bool = field(metadata={"upper_32_bits": 4}) |
399 |
| - is_wash_then_charge_cmd_supported: bool = field(metadata={"upper_32_bits": 5}) |
400 |
| - is_support_room_tag_supported: bool = field(metadata={"upper_32_bits": 6}) |
401 |
| - is_support_quick_map_builder_supported: bool = field(metadata={"upper_32_bits": 7}) |
402 |
| - is_support_smart_global_clean_with_custom_mode_supported: bool = field(metadata={"upper_32_bits": 8}) |
403 |
| - is_careful_slow_mop_supported: bool = field(metadata={"upper_32_bits": 9}) |
404 |
| - is_egg_mode_supported: bool = field(metadata={"upper_32_bits": 10}) |
405 |
| - is_carpet_show_on_map_supported: bool = field(metadata={"upper_32_bits": 12}) |
406 |
| - is_supported_valley_electricity_supported: bool = field(metadata={"upper_32_bits": 13}) |
407 |
| - is_unsave_map_reason_supported: bool = field(metadata={"upper_32_bits": 14}) |
408 |
| - is_supported_download_test_voice_supported: bool = field(metadata={"upper_32_bits": 16}) |
409 |
| - is_support_backup_map_supported: bool = field(metadata={"upper_32_bits": 17}) |
410 |
| - is_support_custom_mode_in_cleaning_supported: bool = field(metadata={"upper_32_bits": 18}) |
411 |
| - is_support_remote_control_in_call_supported: bool = field(metadata={"upper_32_bits": 19}) |
412 |
| - |
413 |
| - is_support_set_volume_in_call: bool = field(metadata={"unhexed_feature_info": 1}) |
414 |
| - is_support_clean_estimate: bool = field(metadata={"unhexed_feature_info": 2}) |
415 |
| - is_support_custom_dnd: bool = field(metadata={"unhexed_feature_info": 4}) |
416 |
| - is_carpet_deep_clean_supported: bool = field(metadata={"unhexed_feature_info": 8}) |
417 |
| - is_support_stuck_zone: bool = field(metadata={"unhexed_feature_info": 16}) |
418 |
| - is_support_custom_door_sill: bool = field(metadata={"unhexed_feature_info": 32}) |
419 |
| - is_wifi_manage_supported: bool = field(metadata={"unhexed_feature_info": 128}) |
420 |
| - is_clean_route_fast_mode_supported: bool = field(metadata={"unhexed_feature_info": 256}) |
421 |
| - is_support_cliff_zone: bool = field(metadata={"unhexed_feature_info": 512}) |
422 |
| - is_support_smart_door_sill: bool = field(metadata={"unhexed_feature_info": 1024}) |
423 |
| - is_support_floor_direction: bool = field(metadata={"unhexed_feature_info": 2048}) |
424 |
| - is_back_charge_auto_wash_supported: bool = field(metadata={"unhexed_feature_info": 4096}) |
425 |
| - is_super_deep_wash_supported: bool = field(metadata={"unhexed_feature_info": 32768}) |
426 |
| - is_ces2022_supported: bool = field(metadata={"unhexed_feature_info": 65536}) |
427 |
| - is_dss_believable_supported: bool = field(metadata={"unhexed_feature_info": 131072}) |
428 |
| - is_main_brush_up_down_supported: bool = field(metadata={"unhexed_feature_info": 262144}) |
429 |
| - is_goto_pure_clean_path_supported: bool = field(metadata={"unhexed_feature_info": 524288}) |
430 |
| - is_water_up_down_drain_supported: bool = field(metadata={"unhexed_feature_info": 1048576}) |
431 |
| - is_setting_carpet_first_supported: bool = field(metadata={"unhexed_feature_info": 8388608}) |
432 |
| - is_clean_route_deep_slow_plus_supported: bool = field(metadata={"unhexed_feature_info": 16777216}) |
433 |
| - is_left_water_drain_supported: bool = field(metadata={"unhexed_feature_info": 134217728}) |
434 |
| - is_clean_count_setting_supported: bool = field(metadata={"unhexed_feature_info": 1073741824}) |
435 |
| - is_corner_clean_mode_supported: bool = field(metadata={"unhexed_feature_info": 2147483648}) |
436 |
| - |
437 |
| - # --- Features from new_feature_info_str --- |
438 |
| - is_two_key_real_time_video_supported: bool = field( |
439 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.TWO_KEY_REAL_TIME_VIDEO} |
440 |
| - ) |
441 |
| - is_two_key_rtv_in_charging_supported: bool = field( |
442 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.TWO_KEY_RTV_IN_CHARGING} |
443 |
| - ) |
444 |
| - is_dirty_replenish_clean_supported: bool = field( |
445 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.DIRTY_REPLENISH_CLEAN} |
446 |
| - ) |
447 |
| - is_avoid_collision_mode_str_supported: bool = field( |
448 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.AVOID_COLLISION_MODE} |
449 |
| - ) |
450 |
| - is_voice_control_str_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.VOICE_CONTROL}) |
451 |
| - is_new_endpoint_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.NEW_ENDPOINT}) |
452 |
| - is_corner_mop_strech_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.CORNER_MOP_STRECH}) |
453 |
| - is_hot_wash_towel_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.HOT_WASH_TOWEL}) |
454 |
| - is_floor_dir_clean_any_time_supported: bool = field( |
455 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.FLOOR_DIR_CLEAN_ANY_TIME} |
456 |
| - ) |
457 |
| - is_pet_supplies_deep_clean_supported: bool = field( |
458 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.PET_SUPPLIES_DEEP_CLEAN} |
459 |
| - ) |
460 |
| - is_mop_shake_water_max_supported: bool = field( |
461 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.MOP_SHAKE_WATER_MAX} |
462 |
| - ) |
463 |
| - is_exact_custom_mode_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.EXACT_CUSTOM_MODE}) |
464 |
| - is_carpet_custom_clean_supported: bool = field( |
465 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.CARPET_CUSTOM_CLEAN} |
466 |
| - ) |
467 |
| - is_pet_snapshot_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.PET_SNAPSHOT}) |
468 |
| - is_custom_clean_mode_count_supported: bool = field( |
469 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.CUSTOM_CLEAN_MODE_COUNT} |
470 |
| - ) |
471 |
| - is_new_ai_recognition_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.NEW_AI_RECOGNITION}) |
472 |
| - is_auto_collection_2_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.AUTO_COLLECTION_2}) |
473 |
| - is_right_brush_stretch_supported: bool = field( |
474 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.RIGHT_BRUSH_STRETCH} |
475 |
| - ) |
476 |
| - is_smart_clean_mode_set_supported: bool = field( |
477 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.SMART_CLEAN_MODE_SET} |
478 |
| - ) |
479 |
| - is_dirty_object_detect_supported: bool = field( |
480 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.DIRTY_OBJECT_DETECT} |
481 |
| - ) |
482 |
| - is_no_need_carpet_press_set_supported: bool = field( |
483 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.NO_NEED_CARPET_PRESS_SET} |
484 |
| - ) |
485 |
| - is_voice_control_led_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.VOICE_CONTROL_LED}) |
486 |
| - is_water_leak_check_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.WATER_LEAK_CHECK}) |
487 |
| - is_min_battery_15_to_clean_task_supported: bool = field( |
488 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.MIN_BATTERY_15_TO_CLEAN_TASK} |
489 |
| - ) |
490 |
| - is_gap_deep_clean_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.GAP_DEEP_CLEAN}) |
491 |
| - is_object_detect_check_supported: bool = field( |
492 |
| - metadata={"new_feature_str_bit": NewFeatureStrBit.OBJECT_DETECT_CHECK} |
493 |
| - ) |
494 |
| - is_identify_room_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.IDENTIFY_ROOM}) |
495 |
| - is_matter_supported: bool = field(metadata={"new_feature_str_bit": NewFeatureStrBit.MATTER}) |
496 |
| - |
497 |
| - # is_multi_map_segment_timer_supported: bool = field(default=False) |
498 |
| - # is_supported_drying_supported: bool = field(default=False) |
499 |
| - |
500 |
| - @classmethod |
501 |
| - def _is_new_feature_str_support(cls, o: int, new_feature_info_str: str) -> bool: |
502 |
| - """ |
503 |
| - Checks feature 'o' in hex string 'new_feature_info_str'. |
504 |
| - """ |
505 |
| - try: |
506 |
| - l = o % 4 |
507 |
| - target_index = -((o // 4) + 1) |
508 |
| - p = new_feature_info_str[target_index] |
509 |
| - hex_char_value = int(p, 16) |
510 |
| - is_set = (hex_char_value >> l) & 1 |
511 |
| - return bool(is_set) |
512 |
| - except (IndexError, ValueError): |
513 |
| - return False |
514 |
| - |
515 |
| - @classmethod |
516 |
| - def from_feature_flags( |
517 |
| - cls, robot_new_features: int, new_feature_set: str, product_nickname: RoborockProductNickname |
518 |
| - ) -> DeviceFeatures: |
519 |
| - """Creates a DeviceFeatures instance from raw feature flags.""" |
520 |
| - unhexed_feature_info = int(new_feature_set[-8:], 16) if new_feature_set and len(new_feature_set) >= 8 else 0 |
521 |
| - upper_32_bits = robot_new_features // (2**32) |
522 |
| - |
523 |
| - kwargs: dict[str, Any] = {} |
524 |
| - |
525 |
| - for f in fields(cls): |
526 |
| - if not f.metadata: |
527 |
| - continue |
528 |
| - |
529 |
| - if "robot_new_features" in f.metadata: |
530 |
| - mask = f.metadata["robot_new_features"] |
531 |
| - kwargs[f.name] = bool(mask & robot_new_features) |
532 |
| - elif "upper_32_bits" in f.metadata: |
533 |
| - bit_index = f.metadata["upper_32_bits"] |
534 |
| - kwargs[f.name] = bool(robot_new_features and ((upper_32_bits >> bit_index) & 1)) |
535 |
| - elif "unhexed_feature_info" in f.metadata: |
536 |
| - mask = f.metadata["unhexed_feature_info"] |
537 |
| - kwargs[f.name] = bool(mask & unhexed_feature_info) |
538 |
| - elif "new_feature_str_bit" in f.metadata: |
539 |
| - bit = f.metadata["new_feature_str_bit"] |
540 |
| - kwargs[f.name] = cls._is_new_feature_str_support(bit, new_feature_set) |
541 |
| - |
542 |
| - return cls(**kwargs) |
543 |
| - |
544 |
| - |
545 | 314 | @dataclass
|
546 | 315 | class HomeDataRoom(RoborockBase):
|
547 | 316 | id: int
|
@@ -990,13 +759,7 @@ class DeviceData(RoborockBase):
|
990 | 759 | device_features: DeviceFeatures | None = None
|
991 | 760 |
|
992 | 761 | def __post_init__(self):
|
993 |
| - self.product_nickname = short_model_to_enum.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS) |
994 |
| - robot_new_features = int(self.device.feature_set) if self.device.feature_set else 0 |
995 |
| - self.device_features = DeviceFeatures.from_feature_flags( |
996 |
| - robot_new_features, |
997 |
| - self.device.new_feature_set if self.device.new_feature_set is not None else "00000000", |
998 |
| - self.product_nickname, |
999 |
| - ) |
| 762 | + self.product_nickname = SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS) |
1000 | 763 |
|
1001 | 764 |
|
1002 | 765 | @dataclass
|
|
0 commit comments