feat: live Q7 (B01) map updates from unsolicited map pushes - #912
Conversation
05bcb82 to
06c7845
Compare
There 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.
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
2584105
Compare
| 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