Uh oh!
There was an error while loading. Please reload this page.
feat: live Q7 (B01) map updates from unsolicited map pushes - #912
Conversation
05bcb82 to
06c7845CompareThere was a problem hiding this comment.
Pull request overview
This PR adds support for Roborock Q7 (B01 / sc-class) devices to keep map rendering up-to-date by subscribing to unsolicited protocol-301 MAP_RESPONSE pushes and flowing those frames into the Q7 MapContentTrait cache + update listeners for the lifetime of a connected device.
Changes:
- Add
Q7MapRpcChannel.subscribe_map_pushes()and wire it intoB01Q7Channelto decode pushedMAP_RESPONSEframes. - Extend Q7
MapContentTraitwithupdate_from_push()+ listener notifications to update cached map/image from pushed frames. - Start/stop Q7 map-push subscription via
Q7PropertiesApi.start()/close()and hook those intoRoborockDevice.connect()/close(), with tests covering push updates and parse-failure behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/devices/traits/b01/q7/test_map_content.py | Adds unit tests for push-driven map cache updates and parse-failure behavior. |
| tests/devices/traits/b01/q7/conftest.py | Extends FakeQ7Channel with a subscribe_map_pushes() test hook. |
| roborock/devices/traits/b01/q7/map_content.py | Adds push update handling, caching, and update listener notifications for Q7 map content. |
| roborock/devices/traits/b01/q7/init.py | Adds start()/close() lifecycle methods to manage the map-push subscription. |
| roborock/devices/rpc/b01_q7_channel.py | Introduces subscribe_map_pushes() to decode unsolicited MAP_RESPONSE frames via the map key. |
| roborock/devices/device.py | Wires Q7 properties start()/close() into device connect/close lifecycle. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Q7 devices stream full SCMap frames (protocol 301) on their own during cleaning — no polling or request is needed. Verified against a live Q7 Series (roborock.vacuum.sc05): a short clean produced a pushed frame roughly every 10 seconds. - B01Q7Channel.subscribe_map_pushes() decodes unsolicited MAP_RESPONSE frames with the device map key. - MapContentTrait.update_from_push() re-parses pushed frames and notifies update listeners; malformed frames are dropped without clearing the cached map. - Q7PropertiesApi.start()/close() subscribe for the device lifetime, wired up in RoborockDevice.connect()/close() like V1 and Q10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
06c7845 to
2584105Compare| rendered image stays current without polling. | ||
| """ | ||
| try: | ||
| self._parse_and_store(raw_payload) |
There was a problem hiding this comment.
I think we may need to check:
RobotMap.mapType == 0 here?
I think there's a chance this could update us for a non-live map and cause confusion and bad updates
There was a problem hiding this comment.
Good catch — fixed in 60aeaae.
update_from_push() now reads the frame's mapType before doing anything else and drops everything that is not the live map (type 0), so a pushed historic or saved map can no longer overwrite the current one. The check happens before parsing, so a non-live frame costs nothing beyond the protobuf header parse — no render, no cache write, no listener notification.
Added parse_map_type() to b01_map_parser.py for that, plus a test asserting a mapType: 1 push leaves the cached map and listeners untouched. The existing push tests now use real serialized RobotMap frames instead of placeholder bytes, since the type check needs a parseable payload.
🤖 Generated with Claude Code
# Conflicts: # roborock/devices/device.py
Summary
Q7 (B01) devices stream full SCMap frames (protocol 301
MAP_RESPONSE) on their own during cleaning — no polling, request or heartbeat needed. This wires those pushes intoMapContentTraitso the rendered map stays current for the device lifetime.Relevant to #827: the DP-110 heartbeat discussion there applies to Q10/ss-class devices. Verified from a plain MQTT subscription that sc-class Q7s (
roborock.vacuum.sc05, fw 03.01.80) push a ~25 KBmapType: 0frame roughly every 10 s during a clean (alongsideprop.postDPS updates), so Q7 needs no heartbeat at all. Also the Q7 side of #739.Changes
B01Q7Channel.subscribe_map_pushes(): decodes unsolicitedMAP_RESPONSEframes with the device map key; undecodable frames are logged and skipped.MapContentTrait.update_from_push(): re-parses pushed frames, updates the cached image/map data and notifies update listeners (TraitUpdateListener); malformed frames are dropped without clearing the cached map.Q7PropertiesApi.start()/close(): subscribe for the device lifetime, wired intoRoborockDevice.connect()/close()like V1 and Q10.Validation
ruff check/formatclean.sc05: a 45 s clean delivered 4 pushed frames through the new path, with the robot pose moving across frames and returning to the dock.Independent of (but designed together with) the companion map-geometry PR #911 — with both, the pushed frames carry live robot pose and path, giving a live map without polling.
🤖 Generated with Claude Code