diff --git a/roborock/data/v1/v1_containers.py b/roborock/data/v1/v1_containers.py index 5efc379ab..4c5ca74d0 100644 --- a/roborock/data/v1/v1_containers.py +++ b/roborock/data/v1/v1_containers.py @@ -137,6 +137,8 @@ class StatusV2(RoborockBase): repeat: int | None = None kct: int | None = None subdivision_sets: int | None = None + seq_type: int | None = field(default=None, metadata={"feature": "is_clean_then_mop_mode_supported"}) + """Cleaning sequence of the current run; 1 means vacuum the room, then mop it.""" @property def square_meter_clean_area(self) -> float | None: diff --git a/roborock/devices/traits/v1/status.py b/roborock/devices/traits/v1/status.py index 9bed4e41e..b3a309961 100644 --- a/roborock/devices/traits/v1/status.py +++ b/roborock/devices/traits/v1/status.py @@ -28,6 +28,8 @@ _DPS_CONVERTER = DpsDataConverter.from_dataclass(StatusV2) +_SEQ_TYPE_CLEAN_THEN_MOP = 1 + class StatusTrait(StatusV2, common.V1TraitMixin, TraitUpdateListener): """Trait for managing the status of Roborock devices. @@ -111,6 +113,17 @@ def mop_route_name(self) -> str | None: return None return self.mop_route_mapping.get(self.mop_mode) + @property + def clean_then_mop(self) -> bool | None: + """Whether the current run vacuums each room fully before mopping it. + + Reported by the device as ``seq_type``. It describes the run in + progress; it is not a persisted setting and cannot be set directly. + """ + if self.seq_type is None: + return None + return self.seq_type == _SEQ_TYPE_CLEAN_THEN_MOP + @property def current_cleaning_mode(self) -> CleaningMode | None: return get_current_cleaning_mode( diff --git a/tests/devices/__snapshots__/test_v1_device.ambr b/tests/devices/__snapshots__/test_v1_device.ambr index 0e5c3f5c5..14b165982 100644 --- a/tests/devices/__snapshots__/test_v1_device.ambr +++ b/tests/devices/__snapshots__/test_v1_device.ambr @@ -876,7 +876,7 @@ }) # --- # name: test_device_trait_command_parsing[status] - StatusTrait(adbumper_status=None, auto_dust_collection=None, avoid_count=None, back_type=None, battery=100, camera_status=None, charge_status=None, clean_area=91287500, clean_fluid_status=None, clean_percent=None, clean_time=5405, cleaning_mode_options=[, , , ], clear_water_box_status=None, collision_avoid_status=None, command=, common_status=None, converter=DefaultConverter, corner_clean_mode=None, current_cleaning_mode=, current_cleaning_mode_name='custom', current_map=0, debug_mode=None, dirty_water_box_status=None, distance_off=0, dnd_enabled=1, dock_cool_fan_status=None, dock_error_status=None, dock_state=, dock_type=None, dry_status=None, dss=None, dust_bag_status=None, dust_collection_status=None, error_code=, error_code_name='none', fan_power=106, fan_speed_mapping={101: 'quiet', 102: 'balanced', 103: 'turbo', 104: 'max', 108: 'max_plus', 105: 'off', 106: 'custom'}, fan_speed_name='custom', fan_speed_options=[, , , , , , ], has_am=None, hatch_door_status=None, home_sec_enable_password=None, home_sec_status=None, in_cleaning=, in_fresh_state=1, in_returning=0, in_warmup=None, is_exploring=None, is_locating=0, kct=None, lab_status=1, last_clean_t=None, lock_status=0, map_present=1, map_status=3, mop_forbidden_enable=0, mop_mode=None, mop_route_mapping={300: 'standard', 301: 'deep', 302: 'custom'}, mop_route_name=None, mop_route_options=[, , ], msg_seq=515, msg_ver=2, rdt=None, repeat=None, replenish_mode=None, rss=None, square_meter_clean_area=91.3, state=, state_name='charging', subdivision_sets=None, switch_map_mode=None, unsave_map_flag=0, unsave_map_reason=4, wash_phase=None, wash_ready=None, wash_status=None, water_box_carriage_status=0, water_box_filter_status=None, water_box_mode=204, water_box_status=0, water_mode_mapping={200: 'off', 201: 'mild', 202: 'standard', 203: 'intense', 207: 'custom_water_flow', 204: 'custom'}, water_mode_name='custom', water_mode_options=[, , , , , ], water_shortage_status=None) + StatusTrait(adbumper_status=None, auto_dust_collection=None, avoid_count=None, back_type=None, battery=100, camera_status=None, charge_status=None, clean_area=91287500, clean_fluid_status=None, clean_percent=None, clean_then_mop=None, clean_time=5405, cleaning_mode_options=[, , , ], clear_water_box_status=None, collision_avoid_status=None, command=, common_status=None, converter=DefaultConverter, corner_clean_mode=None, current_cleaning_mode=, current_cleaning_mode_name='custom', current_map=0, debug_mode=None, dirty_water_box_status=None, distance_off=0, dnd_enabled=1, dock_cool_fan_status=None, dock_error_status=None, dock_state=, dock_type=None, dry_status=None, dss=None, dust_bag_status=None, dust_collection_status=None, error_code=, error_code_name='none', fan_power=106, fan_speed_mapping={101: 'quiet', 102: 'balanced', 103: 'turbo', 104: 'max', 108: 'max_plus', 105: 'off', 106: 'custom'}, fan_speed_name='custom', fan_speed_options=[, , , , , , ], has_am=None, hatch_door_status=None, home_sec_enable_password=None, home_sec_status=None, in_cleaning=, in_fresh_state=1, in_returning=0, in_warmup=None, is_exploring=None, is_locating=0, kct=None, lab_status=1, last_clean_t=None, lock_status=0, map_present=1, map_status=3, mop_forbidden_enable=0, mop_mode=None, mop_route_mapping={300: 'standard', 301: 'deep', 302: 'custom'}, mop_route_name=None, mop_route_options=[, , ], msg_seq=515, msg_ver=2, rdt=None, repeat=None, replenish_mode=None, rss=None, seq_type=None, square_meter_clean_area=91.3, state=, state_name='charging', subdivision_sets=None, switch_map_mode=None, unsave_map_flag=0, unsave_map_reason=4, wash_phase=None, wash_ready=None, wash_status=None, water_box_carriage_status=0, water_box_filter_status=None, water_box_mode=204, water_box_status=0, water_mode_mapping={200: 'off', 201: 'mild', 202: 'standard', 203: 'intense', 207: 'custom_water_flow', 204: 'custom'}, water_mode_name='custom', water_mode_options=[, , , , , ], water_shortage_status=None) # --- # name: test_device_trait_command_parsing[status].1 dict({ diff --git a/tests/devices/traits/v1/test_status.py b/tests/devices/traits/v1/test_status.py index 6fb0284c8..cadf8901f 100644 --- a/tests/devices/traits/v1/test_status.py +++ b/tests/devices/traits/v1/test_status.py @@ -18,6 +18,7 @@ from roborock.data import SHORT_MODEL_TO_ENUM, RoborockProductNickname from roborock.data.v1 import ( RoborockStateCode, + StatusV2, ) from roborock.device_features import DeviceFeatures from roborock.devices.device import RoborockDevice @@ -610,3 +611,25 @@ def test_update_listener_ignores_unrelated(status_trait: StatusTrait) -> None: assert not event.is_set() unsubscribe() + + +@pytest.mark.parametrize( + ("seq_type", "expected"), + [(1, True), (0, False), (None, None)], +) +def test_clean_then_mop(status_trait: StatusTrait, seq_type: int | None, expected: bool | None) -> None: + """seq_type reports whether the current run vacuums before mopping.""" + status_trait.seq_type = seq_type + + assert status_trait.clean_then_mop is expected + + +def test_status_parses_seq_type() -> None: + """seq_type is read from the device status payload. + + The device reports it in get_status; before it was modelled here it was + silently dropped along with every other unmatched key. + """ + status = StatusV2.from_dict({**STATUS, "seq_type": 1}) + + assert status.seq_type == 1