feat: expand Q10 (B01/ss07) status support and add device info#846
feat: expand Q10 (B01/ss07) status support and add device info#846tubededentifrice wants to merge 11 commits into
Conversation
Verified against two physical Roborock Q10 (roborock.vacuum.ss07) devices: - Add the roborock.vacuum.ss07 schema to device_info.yaml (captured via `roborock get-device-info`), per CONTRIBUTING. - Expand Q10Status to capture the ~25 additional fields the device reports in its full status dump (volume, child_lock, DND, net_info, time_zone, etc.). - Fix dpNetInfo/dpTimeZone container field names so the decamelized device keys actually parse (they previously failed to construct). - Add a CLI `status` path for B01 Q10 devices, which refreshes and prints the decoded status (previously `status` only worked for V1 devices). - Add a second real (scrubbed) full-status protocol fixture and extend the status trait tests to cover the new fields.
Adds a SettingsTrait so Q10 settings the status trait already reads can also be
written: volume, child lock, Do Not Disturb, indicator LED, and dust collection.
Q10 setting writes were reverse-engineered + validated against a live ss07
robot: the bare data point write ({"dps": {"26": v}}) is silently ignored; the
value must be wrapped in the dpCommon (101) data point
({"dps": {"101": {"26": v}}}). Verified live by setting volume and child lock and
reading the new value back from the status trait, then restoring.
- traits/b01/q10/settings.py: SettingsTrait wired into Q10PropertiesApi.
- cli: q10-set-volume / -child-lock / -dnd / -led / -dust-collection.
- Unit tests assert the exact dpCommon-wrapped payload for each setter.
|
Added Q10 settings writers for the values this PR now reads: volume, child lock, Do Not Disturb, button light, and dust collection. Q10 setting writes need the I checked volume and child lock by writing a new value, reading status back, and restoring the original value. The session CLI now has |
Adds SettingsTrait.set_dust_collection_frequency (dpDustSetting 50 via the dpCommon wrapper) accepting YXDeviceDustCollectionFrequency or its int code (0=daily, 15/30/45/60 cleans), plus a q10-set-dust-frequency CLI command. Validated live on an R1: dust_setting 0 -> 15 -> restored to 0.
|
Added The method accepts either Live check: changed a Q10 from daily ( |
|
@Lash-L @allenporter This is ready for review when you get a chance — part of the Q10 series (#846 → #847 → #848, stacked in that order). Thanks! |
allenporter
left a comment
There was a problem hiding this comment.
Very nice, thank you for this contribution!
As always, the hard part here is naming and grouping, so curious to get your thoughts on some of my questions.
Address PR Python-roborock#846 review feedback: - Split the monolithic Q10Status read-model + catch-all SettingsTrait into per-concern read/write traits mirroring the v1 layout: SoundVolumeTrait, ChildLockTrait, DoNotDisturbTrait, DustCollectionTrait, ButtonLightTrait, NetworkInfoTrait, ConsumableTrait. Each owns its read-model and setters. - Add an UpdatableTrait base (q10/common.py) that wires a RoborockBase read-model to the DPS push stream; Q10PropertiesApi fans each decoded message out to every read-model trait. - Convert clear on/off flags to bool (child_lock, not_disturb, dust_switch, auto_boost, multi_map_switch, line_laser_obstacle_avoidance) and map dust_setting to YXDeviceDustCollectionFrequency. - set_frequency now takes YXDeviceDustCollectionFrequency only (no int union). - Drop redundant decamelize comments on dpNetInfo/dpTimeZone; keep the note documenting the device's "ipAdress" typo. - Update CLI Q10 setters and tests for the new trait layout.
Reverse-engineered against live Q10 hardware (toggling each setting in the
app and diffing the status dump):
- area_unit -> YXAreaUnit (square_meter=0, square_feet=1)
- carpet_clean_type -> YXCarpetCleanType (rise=0, avoid=1, ignore=2, cross=3)
- mop_state -> bool (mop module attached)
- ground_clean -> bool ("clean along floor direction")
- Rename YXDeviceDustCollectionFrequency.DAILY -> REGULAR to match the app
("regular" vs "frequent" 15/30/45/60); codes unchanged.
breakpoint_clean / add_clean_state / timer_type / user_plan / robot_type
remain int (no app control found, runtime-only, or constant).
Observed on live hardware: dpAddCleanState (96) pulses 0->1 while the app's "re cleaning" (draw-a-rectangle / add-area) request is in progress, then back to 0 once the robot has the area. Model it as a bool.
…rnings ss07 hardware pushes data points this library does not model (confirmed live: DPs 112 and 113, which stay 0 across docked/charging, segment cleaning, lifted-off-ground fault, returning to dock and dustbin-removed states, and are absent from the official app's vacuum plugin). Decoding every status push ran them through `from_code_optional`, which routed through `from_code` and logged a WARNING before returning None -- so each unknown DP spammed "<code> is not a valid code for B01_Q10_DP". - Make `from_code_optional` (and the int path of `from_any_optional`) do their own silent lookup instead of going through `from_code`. `from_code` stays strict (warns + raises) for callers that require a known code. - Remove the duplicate-coded legacy entries `JUMP_SCAN` (101) and `CLIFF_RESTRICTED_AREA` (102): they collided with the confirmed `COMMON` (101) / `REQUEST_DPS` (102) codes, shadowing them in `from_code`, and are unused with unverifiable codes. Also drop the orphaned `CLIFF_RESTRICTED_AREA_UP` (103). - Document DPs 112/113 as observed-but-unidentified. - Tests: assert `from_code_optional` is silent, `from_code` still warns, and the B01 decode drops 112/113 without logging.
The new per-concern Q10 read-models `Consumable` and `NetworkInfo` were star-exported into the `roborock.data` namespace where v1 also exports those names, so `from roborock.data import Consumable` silently resolved to the v1 class (and mypy flagged the incompatible re-import). Rename the Q10 read-models to `Q10Consumable` / `Q10NetworkInfo`; the traits already import them by fully qualified path.
|
I've managed to get and decompile the plugin fetched from the app, I need small adjustments |
While removing the duplicate-coded legacy entries I also dropped DP 103, but live capture shows ss07 hardware does push data point 103 (an empty list when no cliff-restricted areas are configured). Restore CLIFF_RESTRICTED_AREA_UP at 103; only the genuinely-colliding dpJumpScan (101) and dpCliffRestrictedArea (102) stay removed.
The VacuumTrait movement commands were unverified guesses (with TODO notes) and
sent payload shapes the device ignores. Reverse-engineered the correct format
from the official app's vacuum plugin and confirmed each one live against an
ss07 robot (watching the DP stream react):
- start_clean: {"201": 1} (was {"201": {"cmd": 1}}) -> clean_task_type 1
- pause_clean: {"204": 0} (was {"204": {}})
- resume_clean: {"205": 0} (was {"205": {}})
- stop_clean: {"206": 0} (was {"206": {}})
- return_to_dock: {"202": 5} (was {"203": {}}; dpStartBack=charge, and DP 203
is not the dock-return command)
- empty_dustbin: {"203": 2} (already correct; confirmed -> status emptying)
Also add spot_clean ({"201": 5} -> clean_task_type 5), confirmed live, plus a
`q10-vacuum-spot` CLI command. set_fan_level / set_clean_mode were confirmed to
work as bare DP writes (no dpCommon wrapper needed, unlike the settings).
Segment ({"201": 2}), zone ({"201": 3}) and build-map ({"201": 4}) clean are
documented but not exposed: they need a room/zone selection payload whose shape
could not be verified (the broker rejects subscribing to the app's command
topic, so the app's exact request can't be captured).
The Q10 status command only printed properties.status, omitting the other read-model traits that refresh() populates (volume, child lock, do-not-disturb, dust collection, network info, consumables). It also gated on properties.status.status is not None, which the persistent subscribe loop can satisfy with stale data pushed before the command ran. Register update listeners on all refreshed traits, fire refresh(), and wait for a fresh update (with a short settle window for multi-packet pushes) before dumping every trait. Verified live against ss07 (R1).
|
Alright @allenporter this one should be gtg 😓 |
|
Just put together also #851 for room cleaning -- had to do packet capture in the emulator for this one! It's stacked on top of this one, so I kept it as draft for the moment, I'll properly rebase it once this one merges |
Summary
Adds Q10 (
roborock.vacuum.ss07, B01 protocol) device metadata, expands the decoded Q10 status model, and adds Q10 settings writers for the values now exposed by status.This was checked against two physical Q10 devices.
Changes
roborock.vacuum.ss07device schema todevice_info.yaml.dpRequestDpsresponse, including volume, child lock, Do Not Disturb, mop state, dust settings, map switches, cleaning line, time zone, country code, and network info.dpNetInfoanddpTimeZonecontainer field names so decamelized device payloads construct correctly.SettingsTraitwriters for volume, child lock, Do Not Disturb, button light, dust collection, and dust collection frequency. Q10 setting writes are wrapped throughdpCommon(101), which is what the device accepts.statuscommand to refresh and display decoded status for B01 Q10 devices.Testing
uv run pytest— 507 passed on this branchuv run pre-commit run --all-files