Skip to content

feat: decode Q10 (B01/ss07) dpFault codes with a YXFault enum#863

Merged
allenporter merged 12 commits into
Python-roborock:mainfrom
andrewlyeats:feat/q10-fault-codes
Jul 6, 2026
Merged

feat: decode Q10 (B01/ss07) dpFault codes with a YXFault enum#863
allenporter merged 12 commits into
Python-roborock:mainfrom
andrewlyeats:feat/q10-fault-codes

Conversation

@andrewlyeats

@andrewlyeats andrewlyeats commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Following through on #855 — this types Q10Status.fault (DP 90, wired as a raw int in #792) the same way the Q7 handles it: a YXFault | None field plus a fault_name accessor, mirroring B01Props.fault / fault_name.

Why a Q10-specific map (and not B01Fault)

Reusing the Q7 B01Fault — the obvious alternative — would actively mislabel this device: several shared codes mean different things on ss07. The divergent ones are hardware-confirmed on my unit (code fired live, in a context matching the label):

code Q7 B01Fault label actual ss07 meaning evidence
501 robot_suspended cleaning completed, returning to dock ~45 distinct captured events across 9+ days; every context-checked instance fires at the exact second clean progress hits 100% (per completed task) and self-clears in ~3 s
503 dustbin_not_installed docking error fired on a failed dock approach, with the matching "Could not find the dock" notification
570 main_brush_entangled cannot reach target 3 independent occurrences, each on an unreachable/confined target (0 m² result)

A user seeing 570 would be sent to untangle a brush that's fine — and when this device's brush actually was entangled (twice, two different objects jammed physically), it reported 5 (main_brush_stuck), not 570. 501 is worse in the other direction: a benign "clean finished" reads as an error.

Provenance (single ss07 unit, so all n=1)

  • 16 codes hardware-confirmed — observed firing live with matching context: 2, 3, 4, 5, 8, 12, 46, 304, 400, 407, 501, 502, 503, 556, 569, 570. Marked hw-confirmed in the enum. Four of these were confirmed by deliberately engineering the condition and catching the code at the predicted moment: a due schedule ignored mid-clean (407), the dock bag removed (46 — fires at both dock-contact and clean-start; with the bag in, docking is clean), a third auto-empty requested too soon (569 — the dock refuses to run the cycle), and the bumper held in during commanded motion (2). (5 additionally has physical ground truth: two separate real brush jams, each with a matching notification.)
  • We have not been able to find any third-party source for these mappings. I searched integrations (ioBroker/openHAB/HA/Valetudo/tuya-local/localtuya and smaller projects), Tuya's docs, and user reports in several languages: nothing we found maps B01 dpFault numbers — the interface only ever shows users translated strings, never the raw numbers, so reports quoting them may simply never occur. Treat this table as single-source; the per-code comments say which labels are hardware-backed vs spec-only.
  • The rest are from the ss07 fault spec (labels are mine to name; only display strings are exposed). One code (310) is contextual inference and commented as such.
  • 569 has a small story: I initially dropped it because its label couldn't be supported — then reproduced it deterministically (request a third auto-empty within ~15 minutes and the dock refuses the cycle and emits 569), which vindicated the spec label "too frequent emptying", so it's back in with hardware provenance. The Q7 table's main_wheels_entangled for 569 is another cross-model divergence.
  • dpFault is overloaded — some values are lifecycle, not errors (400 = scheduled clean starting, 501/502 = returning/recharge). The converse also holds and is noted in the docstring: the device can sit in its error state (dpStatus 12) with dpFault still 0 (observed live with the dust-bin module removed), so a NONE fault doesn't imply no error. Happy to also add the spec's blocking-vs-alert buckets in a follow-up if that's useful.

Tradeoff: unmapped codes decode to None

Typing the field means an unmapped code goes through from_code, fails conversion, and the field is left None (the library-wide enum-field behavior). The fault space is open-ended — a firmware update can mint new codes — so the raw number is lost exactly when it's most interesting. That's already true of every other enum field here and matches the Q7, so this follows the pattern; happy to reshape to keep the raw int reachable instead (e.g. an int field + a decoded property) if that's preferred.

Scope

Data layer only (roborock/data/b01_q10/ + tests) — no trait/transport files; rebased onto current main (post-#859). Tests cover the ss07-divergent decodes, the from_dict path, and unmapped-code behavior; the one existing assertion touched (test_status.py fault == 0) now reads fault is YXFault.NONE, consistent with its neighbors.

Closes #855.

andrewlyeats and others added 8 commits July 4, 2026 17:26
Add a Q10-specific YXFault enum (seeded from the ss07 app fault i18n) and an
additive Q10Status.fault_name property. The raw fault int is untouched, so
unknown/new codes are never lost. Addresses Python-roborock#855.
Retype the fault field from a raw int to YXFault | None and make fault_name
a str accessor, matching B01Props.fault / fault_name on the Q7. Update the
enum provenance notes from live hardware confirmation (10 codes) and drop
the unconfirmed 569 label.
allenporter
allenporter previously approved these changes Jul 5, 2026
Comment thread roborock/data/b01_q10/b01_q10_code_mappings.py Outdated
@andrewlyeats andrewlyeats marked this pull request as ready for review July 5, 2026 22:34
@allenporter allenporter merged commit 32f9747 into Python-roborock:main Jul 6, 2026
7 checks passed
@allenporter

Copy link
Copy Markdown
Contributor

Thank you for the contribution!

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

Labels

None yet

Projects

None yet

2 participants