Skip to content

feat: Q10 (B01/ss07) clean-record history trait#857

Merged
allenporter merged 3 commits into
Python-roborock:mainfrom
andrewlyeats:feat/q10-clean-history
Jul 3, 2026
Merged

feat: Q10 (B01/ss07) clean-record history trait#857
allenporter merged 3 commits into
Python-roborock:mainfrom
andrewlyeats:feat/q10-clean-history

Conversation

@andrewlyeats

@andrewlyeats andrewlyeats commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Implements #767 for B01/ss07 (Q10).

Adds a CleanHistoryTrait that fetches and decodes the Q10 clean-record history. Unlike the Q7's synchronous get_record_list RPC, the Q10 is push-driven: refresh() sends {"op":"list"} for dpCleanRecord (DP 52) over dpCommon, and the device publishes its record list back on the subscribe stream.

Shape

  • CleanRecordConverter parses a dpCleanRecord envelope — the full op:"list" reply or a single op:"notify" push — into a CleanRecordPush; the trait's _apply does the merge-or-replace, one sort, one notify.
  • Each record decodes into a Q10CleanRecord (12 underscore fields), with clean_mode/work_mode/cleaning_result/start_method typed as enums via from_code_optional (an unmapped code → None; the original stays in raw).

Validation (real ss07 Q10, fw 03.11.24)

  • op:"list" decoded 25 records live on the device's own reply topic; the end-of-clean op:"notify" push was captured on a genuine whole-apartment completion and upserted correctly.
  • 12-field layout cross-confirmed against ioBroker's Q10CleanRecordService (independent implementation).
  • Unit tests + real-payload fixtures under tests/protocols/testdata/b01_q10_protocol/.

Caveats (one device observed)

  • clean_mode=2 and start_method=0 were never seen on our unit — left unmapped rather than guessed (they surface as None; raw retains the value).
  • work_mode uses YXCleanType (matching Q10Status); it caps at 4 while the live CLEAN_MODE DP can reach 6, though records only ever show 1/2/3.
  • Per-record map download (the map_len gate) is intentionally out of scope — a natural follow-up.

@andrewlyeats andrewlyeats force-pushed the feat/q10-clean-history branch from 80994f3 to 80b0bd6 Compare June 27, 2026 06:57

@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.

Thank you @andrewlyeats this is fantastic. I have some code structure related comments, though overall look forward to merging this feature.

Comment thread roborock/data/b01_q10/b01_q10_containers.py Outdated
Comment thread roborock/data/b01_q10/b01_q10_containers.py Outdated
Comment thread roborock/data/b01_q10/b01_q10_containers.py Outdated
Comment thread roborock/devices/traits/b01/q10/clean_history.py
Comment thread roborock/data/b01_q10/b01_q10_containers.py Outdated
Q10 clean history is push-driven over dpCleanRecord (DP 52), unlike the Q7's
synchronous get_record_list RPC. Adds:
- Q10CleanRecord: parses the 12-field op:list underscore string (raw retained),
  with crash-safe enum accessors .scope/.work/.result/.started_by (IntEnums that
  return None for an unmapped code, never raising like the YX from_code path)
- CleanHistoryTrait: refresh() sends {op:list}; update_from_dps decodes both the
  full list and the single op:notify clean-finished push (upsert by record_id,
  newest-first); registered as a read-model trait in the dispatch loop

Field layout is the device app's own (setHoldData), cross-confirmed by ioBroker's
Q10CleanRecordService. Tests cover decode, enum labels + unmapped-safety, and the
list/notify paths.
@andrewlyeats andrewlyeats force-pushed the feat/q10-clean-history branch from 80b0bd6 to 8055598 Compare July 1, 2026 06:53
@andrewlyeats

Copy link
Copy Markdown
Contributor Author

Thanks — addressed all five, pushed.

Enums: work_mode now uses YXCleanType (matching Q10Status's live clean-mode). Added YXCleanScope/YXCleaningResult/YXStartMethod for the three with no existing match. Two notes:

  • clean_mode deliberately isn't YXDeviceCleanTask — the persisted record uses a different int encoding than the live task DP: a full clean records 0 (idle there), a single-room clean records 1 (smart there). Checked across ~90 records vs the live DP-138 distribution. Own enum; the docstring notes the relationship.
  • work_mode = YXCleanType for consistency with Status; one caveat — it caps at 4 and the live CLEAN_MODE DP can reach 6, though records only ever show 1/2/3. Say the word if you'd prefer YXDeviceWorkMode (covers 5/6).

Fields/converters: enums directly, property layer gone. Conversion runs in the converter via from_code_optional — records come from the string, not the DPS stream, so RoborockBase isn't in that path; unmapped → None, raw keeps the original.

Structure: CleanRecordConverter (envelope → CleanRecordPush) + _apply (one merge/replace, one sort, one notify).

Docs/tests: dropped the app mention; added dpCleanRecord_{list,notify}.json fixtures (snapshot-covered).


These "contributions" (I hope they are!) have been a collaboration between a physicist trying to learn about modern coding tools and one of those tools, Claude Code. We both hope our efforts to be gracious and follow open source software etiquette are apparent in our contributions here. If we are in fact injecting noise, please tell us and we will stop! We thank you for your work on this repo!

allenporter
allenporter previously approved these changes Jul 2, 2026

@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.

Can you explicitly address / reply / resolve each comments so we know they're completed? (or ask the AI to do it)

Comment thread roborock/devices/traits/b01/q10/clean_history.py
Comment thread roborock/devices/traits/b01/q10/clean_history.py Outdated
Comment thread roborock/devices/traits/b01/q10/clean_history.py Outdated
andrewlyeats and others added 2 commits July 2, 2026 19:35
Co-authored-by: Allen Porter <allen.porter@gmail.com>
parse_record is a @staticmethod, so call it as CleanRecordConverter.parse_record(...)
rather than self.parse_record(...).
@andrewlyeats andrewlyeats force-pushed the feat/q10-clean-history branch from ec279ef to d552028 Compare July 2, 2026 23:37
@andrewlyeats

Copy link
Copy Markdown
Contributor Author

Replied to all comments in their respective threads and closed them out. Made both changes you suggested. Two of my commit messages didn't pass commit-message lint so had to force-push to correct. That force-push should not have changed any code, just the messages. I think this should now be resolved and ready for you. Thanks for your guidance!

@allenporter

Copy link
Copy Markdown
Contributor

Thank you @andrewlyeats for the contribution!

@allenporter allenporter merged commit 79a996d into Python-roborock:main Jul 3, 2026
7 checks passed
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.

2 participants