|
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