Skip to content

feat(v1): expose clean-then-mop status from the device - #959

Open
klowdo wants to merge 2 commits into
Python-roborock:mainfrom
klowdo:feat/status-seq-type
Open

klowdo wants to merge 2 commits into
Python-roborock:mainfrom
klowdo:feat/status-seq-type

Conversation

@klowdo

@klowdo klowdo commented Sep 15, 2026 •

Copy link
Copy Markdown

Summary

The device reports seq_type in its get_status payload, but StatusV2 did not model it, so RoborockBase.from_dict discarded it along with every other unmatched key.

This adds seq_type to StatusV2, a clean_then_mop property on StatusTrait and a set_clean_then_mop setter, reporting whether the current configuration vacuums each room fully before mopping it ("clean then mop" / "vacuum then mop").

Why it is get_status

The vendor Android app derives its own clean-then-mop state from the same field on the same payload. In the cloud-downloaded device plugin for roborock.vacuum.a288, parseCleanModeStatus does:

r6 = r1.seq_type;
r6 = 1 == r6;
r2['cleanThenMop'] = r6;

and its caller passes the status object — the same one it parses charge_status, wash_ready and kct from.

Verification

Tested against a Roborock Saros 20 (roborock.vacuum.a288). Raw get_status excerpt:

{"state": 8, "fan_power": 102, "water_box_mode": 235, "mop_mode": 300,
 "repeat": 1, "kct": 0, "subdivision_sets": 0, "seq_type": 0, ...}

Toggling "vacuum then mop" in the Roborock app, with the robot docked and idle (state: 8, in_cleaning: 0), flips the field and the new property follows it:

app setting seq_type clean_then_mop
vacuum and mop 0 False
vacuum then mop 1 True

So this is a persisted device setting that tracks the app, not merely a per-run artifact.

uv run pytest (1037 passed) and uv run pre-commit run --all-files both pass. One syrupy snapshot updated for the new field.

Notes and open questions

  • Setter. set_clean_then_mop(enabled) sends app_set_clean_sequence_type (as pointed out by Lash-L, and matching the app plugin's setCleanThenMopType). The firmware requires the current fan_power / water_box_mode / mop_mode in the request, so the trait echoes its own values back; repeat: 1 is added when enabling on devices with is_ctm_with_repeat_supported. mop_type / mop_power are omitted because the library does not model those fields or the isSupportChangeMop / isSupportVibrateMop feature bits. Verified on the Saros 20 (idle, undocked):

    step seq_type
    before 0
    set_clean_then_mop(True) 1
    set_clean_motor_mode with the same motor values 1 (unchanged)
    set_clean_then_mop(False) 0
  • Relationship to CleaningMode. Documented on the property. CleaningMode is derived from fan_power / water_box_mode / mop_mode and says what the robot does in a room; clean_then_mop is the order when it does both. Writing either one leaves the other unchanged (table above, and toggling in the app earlier). A consumer exposes them as two independent controls, e.g. a select plus a switch.

  • There is also a per-room layer. get_customize_clean_mode returns seq_type and repeat per segment, e.g. [{"segment": 4, "fan_power": 102, "water_box_mode": 235, "mop_mode": 300, "repeat": 1, "seq_type": 0}]. This PR models only the global value from get_status; the per-room layer is left alone.

  • Single device. Verified on one Saros 20 only. The field is gated for reporting on is_clean_then_mop_mode_supported, so devices without the feature are unaffected, but I cannot confirm behaviour on other models.

  • get_status carries several other keys this library still drops (distance_off, cleaning_info, extra_time, monitor_status, exit_dock, pet_reminding, sub_error_code, sub_zone). Out of scope here, but happy to follow up if wanted.

Motivation

Downstream this lets Home Assistant expose clean-then-mop as real device state rather than guessing. Deliberately not folded into CleaningMode: the axes are independent — toggling clean-then-mop on the test device left current_cleaning_mode at vac_and_mop — and with no setter available a CleaningMode member would be readable but not selectable.

@Lash-L

Lash-L commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Hey! Thanks for this.

I think the setter can be used via app_set_clean_sequence_type

{
  "method": "app_set_clean_sequence_type",
  "params": {
    "type": 1,
    "fan_power": 102,
    "water_box_mode": 235,
    "mop_mode": 300
  }
}

type: 1 enables clean then mop.
type: 0 disables clean then mop.

mop_type Included when its value is greater than zero and isSupportChangeMop() is true.

mop_power Included when its value is nonnegative and isSupportVibrateMop() is true

repeat Set to 1 when type == 1 and isCtmWithRepeatSupported() is true.

@allenporter allenporter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see in the comments this is totally separate from CleaningMode -- but just reading the code it's not clear how these work together and how a consumer like home assistant should know the difference here.

Perhaps we can incorporate the setter info discussed by Lash-L and also clarify how these things work together or don't and why its fine.

klowdo added 2 commits October 6, 2026 10:36
The device reports seq_type in its get_status payload, but StatusV2 did not
model it, so RoborockBase.from_dict discarded it along with every other
unmatched key. The vendor app reads the same field off the same payload to
derive its "clean then mop" state.

Add seq_type to StatusV2 and a clean_then_mop property on StatusTrait that
reports whether the current run vacuums each room fully before mopping it.
It describes the run in progress rather than a persisted setting, and the
device offers no setter, so it is read only here; the value travels outbound
as a parameter of the cleaning command instead.

Gated for reporting on is_clean_then_mop_mode_supported.

Verified against a Roborock Saros 20 (roborock.vacuum.a288).
@klowdo
klowdo force-pushed the feat/status-seq-type branch from 8d8c9b0 to ff36835 Compare October 6, 2026 08:40
@klowdo

klowdo commented Oct 6, 2026

Copy link
Copy Markdown
Author

@Lash-L confirmed against the a288 plugin bundle (setCleanThenMopType): {type, fan_power, water_box_mode, mop_mode}, mop_type only when >0 && isSupportChangeMop, mop_power only when >=0 && isSupportVibrateMop, repeat: 1 only when type == 1 && isCtmWithRepeatSupported. Added as StatusTrait.set_clean_then_mop(enabled) in ff36835. mop_type / mop_power left out since the library models neither the fields nor those two feature bits.

Verified on the Saros 20: seq_type goes 0 → 1 → 0 through the setter, and a set_clean_motor_mode in between leaves it at 1.

@allenporter the clean_then_mop docstring now spells out the relationship: CleaningMode is derived from fan_power / water_box_mode / mop_mode and never reads seq_type, so they are two independent axes (what vs. order). Writing one does not change the other on the device. The old docstring also wrongly said the value could not be set; fixed. PR description updated with the on-device table.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants