Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions roborock/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
ROBOROCK_S8_PRO_ULTRA = "roborock.vacuum.a70"
ROBOROCK_S8 = "roborock.vacuum.a51"
ROBOROCK_P10 = "roborock.vacuum.a75" # also known as q_revo
ROBOROCK_P30_PRO = "roborock.vacuum.a306"
ROBOROCK_S8_MAXV_ULTRA = "roborock.vacuum.a97"
ROBOROCK_QREVO_S = "roborock.vacuum.a104"
ROBOROCK_QREVO_PRO = "roborock.vacuum.a101"
Expand Down Expand Up @@ -77,6 +78,7 @@
ROBOROCK_S4_MAX,
ROBOROCK_S7,
ROBOROCK_P10,
ROBOROCK_P30_PRO,
ROBOROCK_Q10,
ROCKROBO_G10_SG,
]
Expand Down
3 changes: 3 additions & 0 deletions roborock/data/v1/v1_clean_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class WaterModes(RoborockModeEnum):
CUSTOM = ("custom_water_flow", 207)
EXTREME = ("extreme", 208)
SMART_MODE = ("smart_mode", 209)
SMART_WATER = ("smart_water", 210)
PURE_WATER_FLOW_START = ("slight", 221)
PURE_WATER_FLOW_SMALL = ("low", 225)
PURE_WATER_FLOW_GENTLE = ("gentle", 230)
Expand Down Expand Up @@ -207,6 +208,8 @@ def _resolve_water_slide_mode(code: int) -> WaterModes | None:
"""Resolve a raw slider code to the water mode for its app range."""
if code == 200:
return WaterModes.OFF
if code == WaterModes.SMART_WATER.code:
return WaterModes.SMART_WATER
position = code - 220
if position < 1:
return None
Expand Down
1 change: 1 addition & 0 deletions roborock/data/v1/v1_code_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ class RoborockDockTypeCode(RoborockEnum):
f1s_dock = 44
o7h_dock = 45
f1c_dock = 46
type_49_dock = 49


class RoborockDockDustCollectionModeCode(RoborockEnum):
Expand Down
179 changes: 179 additions & 0 deletions tests/__snapshots__/test_supported_features.ambr
Original file line number Diff line number Diff line change
@@ -1,4 +1,183 @@
# serializer version: 1
# name: test_device_features_from_home_data[home_data_device_p30_pro.json]
dict({
'is_activate_video_charging_and_standby_supported': True,
'is_ai_recognition_obstacle_supported': True,
'is_ai_recognition_setting_supported': True,
'is_analysis_supported': False,
'is_any_state_transit_goto_supported': True,
'is_auto_collection_2_supported': True,
'is_auto_delivery_field_in_global_status_supported': True,
'is_auto_tear_down_mop_supported': False,
'is_avoid_collision_mode_supported': True,
'is_avoid_collision_supported': True,
'is_back_charge_auto_wash_supported': True,
'is_back_wash_new_smart_supported': True,
'is_careful_slow_mop_supported': True,
'is_carpet_custom_clean_supported': True,
'is_carpet_deep_clean_supported': True,
'is_carpet_long_haired_ex_supported': False,
'is_carpet_long_haired_supported': False,
'is_carpet_pressure_use_origin_paras_supported': False,
'is_carpet_shape_type_supported': True,
'is_carpet_show_on_map': True,
'is_carpet_supported': True,
'is_ces_2022_supported': False,
'is_clean_count_setting_supported': True,
'is_clean_direct_status_supported': True,
'is_clean_efficiency_supported': True,
'is_clean_fluid_delivery_supported': True,
'is_clean_history_time_line_supported': True,
'is_clean_route_deep_slow_plus_supported': False,
'is_clean_route_fast_mode_supported': True,
'is_clean_route_setting_supported': True,
'is_clean_then_mop_mode_supported': True,
'is_clean_time_line_supported': True,
'is_collect_dust_count_show_supported': True,
'is_collect_dust_mode_supported': True,
'is_corner_clean_mode_supported': False,
'is_corner_mop_stretch_supported': True,
'is_ctm_with_repeat_supported': True,
'is_current_map_restore_enabled': True,
'is_custom_clean_mode_count_supported': True,
'is_custom_mode_supported': True,
'is_custom_water_box_distance_supported': True,
'is_customized_clean_supported': True,
'is_detect_wire_carpet_supported': True,
'is_dirty_object_detect_supported': True,
'is_dirty_replenish_clean_supported': True,
'is_dry_interval_timer_supported': True,
'is_dss_believable': True,
'is_dual_band_wi_fi_supported': True,
'is_dust_collection_setting_supported': True,
'is_dynamically_add_clean_zones_supported': True,
'is_dynamically_skip_clean_zone_supported': True,
'is_egg_dance_mode_supported': True,
'is_egg_mode_supported_from_new_features': False,
'is_exact_custom_mode_supported': True,
'is_exhibition_function_supported': False,
'is_floor_dir_clean_any_time_supported': True,
'is_flow_led_setting_supported': False,
'is_follow_low_obs_supported': False,
'is_full_duples_switch_supported': True,
'is_fw_filter_obstacle_supported': True,
'is_gap_deep_clean_supported': False,
'is_goto_pure_clean_path_supported': True,
'is_hot_wash_towel_supported': True,
'is_identify_room_supported': True,
'is_ignore_unknown_map_object_supported': True,
'is_lds_lifting_supported': True,
'is_led_status_switch_supported': False,
'is_left_water_drain_supported': True,
'is_low_area_access_supported': True,
'is_main_brush_up_down_supported_from_str': True,
'is_map_beautify_internal_debug_supported': True,
'is_map_carpet_add_support': True,
'is_map_eraser_supported': True,
'is_matter_supported': True,
'is_max_plus_mode_supported': True,
'is_max_zone_opened_supported': True,
'is_mechanical_arm_mode_supported': False,
'is_midway_back_to_dock_supported': True,
'is_min_battery_15_to_clean_task_supported': True,
'is_mop_forbidden_supported': False,
'is_mop_path_supported': True,
'is_mop_shake_module_supported': False,
'is_mop_shake_water_max_supported': True,
'is_multi_floor_supported': False,
'is_multi_map_segment_timer_supported': True,
'is_new_ai_recognition_supported': True,
'is_new_data_for_clean_history': True,
'is_new_data_for_clean_history_detail': True,
'is_new_endpoint_supported': True,
'is_new_remote_view_supported': True,
'is_no_need_carpet_press_set_supported': True,
'is_none_pure_clean_mop_with_max_plus': False,
'is_object_detect_check_supported': True,
'is_offline_map_supported': True,
'is_optimize_battery_supported': False,
'is_order_clean_supported': False,
'is_over_sea_ctm_supported': True,
'is_pet_snapshot_supported': True,
'is_pet_supplies_deep_clean_supported': True,
'is_program_mode_supported': False,
'is_pumping_water_supported': False,
'is_pure_clean_mop_supported': True,
'is_re_segment_supported': True,
'is_record_allowed': True,
'is_remote_supported': False,
'is_right_brush_stretch_supported': False,
'is_roller_mop_supported': True,
'is_room_name_supported': True,
'is_rpc_retry_supported': True,
'is_rubber_brush_carpet_supported': False,
'is_set_child_supported': True,
'is_setting_carpet_first_supported': True,
'is_shake_mop_set_supported': True,
'is_should_show_arm_over_load_supported': True,
'is_show_clean_finish_reason_supported': True,
'is_show_general_obstacle_supported': False,
'is_show_obstacle_photo_supported': False,
'is_side_brush_lift_carpet_supported': True,
'is_small_side_mop_supported': False,
'is_smart_clean_mode_set_supported': True,
'is_soak_and_wash_supported': True,
'is_soft_clean_mode_supported': False,
'is_sr_map_supported': False,
'is_super_deep_wash_supported': True,
'is_support_api_app_stop_grasp_supported': True,
'is_support_backup_map': True,
'is_support_clean_estimate': True,
'is_support_cliff_zone': True,
'is_support_custom_carpet': False,
'is_support_custom_dnd': True,
'is_support_custom_door_sill': True,
'is_support_custom_mode_in_cleaning': True,
'is_support_fetch_timer_summary': False,
'is_support_floor_direction': True,
'is_support_floor_edit': True,
'is_support_furniture': True,
'is_support_get_particular_status_supported': True,
'is_support_incremental_map': True,
'is_support_main_brush_up_down_supported': True,
'is_support_mop_back_pwm_set': False,
'is_support_quick_map_builder': True,
'is_support_remote_control_in_call': True,
'is_support_room_tag': True,
'is_support_set_switch_map_mode': True,
'is_support_set_volume_in_call': True,
'is_support_side_brush_up_down_supported': False,
'is_support_smart_door_sill': True,
'is_support_smart_global_clean_with_custom_mode': True,
'is_support_smart_scene': True,
'is_support_stuck_zone': True,
'is_support_voice_control_debug': False,
'is_support_water_mode': True,
'is_supported_download_test_voice': True,
'is_supported_drying': True,
'is_supported_valley_electricity': True,
'is_sync_server_name_supported': True,
'is_three_d_mapping_inner_test_supported': False,
'is_tidyup_zones_supported': False,
'is_two_gears_no_collision_supported': True,
'is_two_key_real_time_video_supported': True,
'is_two_key_rtv_in_charging_supported': True,
'is_type_identify_supported': False,
'is_unsave_map_reason_supported': True,
'is_uvc_sterilize_supported': False,
'is_video_monitor_supported': True,
'is_video_patrol_supported': True,
'is_video_setting_supported': True,
'is_voice_control_led_supported': False,
'is_voice_control_supported': True,
'is_wash_then_charge_cmd_supported': True,
'is_water_leak_check_supported': True,
'is_water_slide_mode_supported': True,
'is_water_up_down_drain_supported': True,
'is_wifi_manage_supported': True,
'is_workday_holiday_supported': True,
})
# ---
# name: test_device_features_from_home_data[home_data_device_pearls.json]
dict({
'is_activate_video_charging_and_standby_supported': False,
Expand Down
27 changes: 27 additions & 0 deletions tests/devices/traits/v1/__snapshots__/test_device_features.ambr
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
# serializer version: 1
# name: test_is_attribute_supported[home_data_device_p30_pro.json]
dict({
'battery': True,
'charge_status': True,
'clean_fluid_status': False,
'clean_percent': True,
'clear_water_box_status': True,
'dirty_water_box_status': True,
'dock_error_status': True,
'dry_status': True,
'error_code': True,
'fan_power': True,
'rdt': True,
'state': True,
'water_box_carriage_status': True,
'water_box_mode': True,
'water_box_status': True,
'water_shortage_status': True,
})
# ---
# name: test_is_attribute_supported[home_data_device_pearls.json]
dict({
'battery': True,
Expand Down Expand Up @@ -139,6 +159,13 @@
'water_shortage_status': True,
})
# ---
# name: test_is_consumable_field_supported[home_data_device_p30_pro.json]
dict({
'filter_work_time': True,
'main_brush_work_time': True,
'side_brush_work_time': True,
})
# ---
# name: test_is_consumable_field_supported[home_data_device_pearls.json]
dict({
'filter_work_time': True,
Expand Down
33 changes: 32 additions & 1 deletion tests/devices/traits/v1/test_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
get_current_cleaning_mode,
resolve_cleaning_mode,
)
from roborock.data import SHORT_MODEL_TO_ENUM, RoborockProductNickname
from roborock.data import SHORT_MODEL_TO_ENUM, HomeDataDevice, RoborockProductNickname
from roborock.data.v1 import (
RoborockChargeStatus,
RoborockDockErrorCode,
RoborockDockState,
RoborockDockTypeCode,
RoborockErrorCode,
RoborockStateCode,
)
Expand Down Expand Up @@ -87,6 +88,35 @@ async def test_refresh_status_dict_response(status_trait: StatusTrait, mock_rpc_
mock_rpc_channel.send_command.assert_called_once_with(RoborockCommand.GET_STATUS)


@pytest.mark.parametrize("device_info", [HomeDataDevice.from_dict(mock_data.P30_PRO_DEVICE_DATA)])
@pytest.mark.parametrize(
("water_box_mode", "expected_name"),
[(210, "smart_water"), (230, "gentle")],
)
async def test_refresh_status_p30_pro(
device_info: HomeDataDevice,
discover_features_fixture: None,
status_trait: StatusTrait,
mock_rpc_channel: AsyncMock,
water_box_mode: int,
expected_name: str,
) -> None:
"""Test refreshing P30 Pro status with smart water or a manual slider value."""
mock_rpc_channel.send_command.return_value = [
{**STATUS, "fan_power": 103, "water_box_mode": water_box_mode, "mop_mode": 300, "dock_type": 49}
]

await status_trait.refresh()

assert status_trait.fan_speed_name == "turbo"
assert status_trait.water_box_mode == water_box_mode
assert status_trait.water_mode_name == expected_name
assert status_trait.mop_route_name == "standard"
assert status_trait.current_cleaning_mode == CleaningMode.VAC_AND_MOP
assert status_trait.dock_type == RoborockDockTypeCode.type_49_dock
mock_rpc_channel.send_command.assert_awaited_once_with(RoborockCommand.GET_STATUS)


async def test_refresh_status_propagates_exception(status_trait: StatusTrait, mock_rpc_channel: AsyncMock) -> None:
"""Test that exceptions from RPC channel are propagated."""
mock_rpc_channel.send_command.side_effect = RoborockException("Communication error")
Expand Down Expand Up @@ -769,6 +799,7 @@ def test_water_slide_mode_mapping() -> None:
("water_box_mode", "expected_name"),
[
(200, "off"),
(210, "smart_water"),
(221, "slight"),
(223, "slight"),
(224, "low"),
Expand Down
2 changes: 2 additions & 0 deletions tests/mock_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
Q10_DEVICE_DATA = DEVICES["home_data_device_q10.json"]
ZEO_ONE_DEVICE_DATA = DEVICES["home_data_device_zeo_one.json"]
SAROS_10R_DEVICE_DATA = DEVICES["home_data_device_saros_10r.json"]
P30_PRO_DEVICE_DATA = DEVICES["home_data_device_p30_pro.json"]

# All testdata devices joined with their matching product (keyed by device filename).
# Devices whose productId has no corresponding product file are omitted.
Expand Down Expand Up @@ -176,6 +177,7 @@
"product-id-a123": RoborockDockTypeCode.pearl_plus_dock, # PearlS Lite / Q Revo (Wash/Empty)
"product-saros-10": RoborockDockTypeCode.o4_dock, # Saros 10 / Q Revo Pro (Wash/Empty/Auto Fluid)
"product-saros-10r": RoborockDockTypeCode.o6_dock, # JC / Q Revo Master (Wash/Empty/Hot water)
"product-id-a306": RoborockDockTypeCode.type_49_dock, # P30 Pro (Wash/Empty/Dry)
}


Expand Down
4 changes: 3 additions & 1 deletion tests/test_supported_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ async def test_from_dict_rejects_legacy_missing_capabilities(caplog: pytest.LogC
(RoborockDockTypeCode.f1s_dock, True, True),
(RoborockDockTypeCode.o7h_dock, True, True),
(RoborockDockTypeCode.f1c_dock, True, True),
(RoborockDockTypeCode.type_49_dock, True, True),
],
)
def test_dock_features(dock_type: RoborockDockTypeCode, is_collectable: bool, is_washable: bool) -> None:
Expand All @@ -318,7 +319,8 @@ def test_all_rr_api_dock_types_are_mapped() -> None:
if dock_type is not RoborockDockTypeCode.unknown
}

assert mapped_docks == RR_API_DOCK_TYPES
observed_docks = {49: "type_49_dock"} # P30 Pro; not present in the older RR_API snapshot.
assert mapped_docks == RR_API_DOCK_TYPES | observed_docks


def test_dock_feature_flags_from_rr_api() -> None:
Expand Down
17 changes: 17 additions & 0 deletions tests/testdata/home_data_device_p30_pro.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"duid": "device-id-a306",
"name": "P30 Pro",
"localKey": "key123key123key1",
"productId": "product-id-a306",
"fv": "02.23.52",
"online": true,
"pv": "1.0",
"featureSet": "4499197267967999",
"newFeatureSet": "000002637C3FBF1DCEDD7FFDA8E8B6FD5BBFAF7F7EFEFFFF",
"deviceStatus": {
"121": 8,
"122": 100,
"123": 103,
"124": 210
}
}
Loading
Loading