From ee5b1a03da4b1555ae059c1261bcfa756faaee87 Mon Sep 17 00:00:00 2001 From: Thomas Rupprecht Date: Thu, 13 Aug 2026 14:02:22 +0200 Subject: [PATCH] docs(q7): document live map pushes and cover them in tests The map content trait's module docstring described a pure pull contract -- `refresh()` does I/O, `parse_map_content()` reparses, fields "are then readable" -- which no longer holds: `update_from_push()` mutates the same cached fields from the MQTT receive callback and notifies listeners, so they can change without the reader calling anything. Spell that out. `docs/DEVICES.md` only mentioned `device.b01_q7_properties` for Q7, so the push path was undiscoverable and consumers would build a polling loop for something the device streams by itself. Name the listener entry point and the fields to read. The push test checked `image_content` and `raw_api_response` but not `map_data`, the field carrying the live pose and cleaning path -- dropping its assignment kept the test green. It also never asserted that no RPC was published, which is the whole point of the push path: unlike `refresh()`, it needs no map id and no round trips. Co-Authored-By: Claude Opus 5 (1M context) --- docs/DEVICES.md | 1 + roborock/devices/traits/b01/q7/map_content.py | 4 ++++ tests/devices/traits/b01/q7/test_map_content.py | 3 +++ 3 files changed, 8 insertions(+) diff --git a/docs/DEVICES.md b/docs/DEVICES.md index 2b89df08..184c2366 100644 --- a/docs/DEVICES.md +++ b/docs/DEVICES.md @@ -19,6 +19,7 @@ Cloud and Network. * Use `device.b01_q10_properties.vacuum` to access vacuum commands (start, pause, stop, dock, empty dustbin, set clean mode, set fan level). * Use `device.b01_q10_properties.command.send()` for raw DP commands. * **Vacuums (B01 Q7)**: Use `device.b01_q7_properties` for Q7 series devices. + * The device streams full map frames on its own while cleaning, so the rendered map stays current without polling or a heartbeat. Register `device.b01_q7_properties.map_content.add_update_listener(cb)` and read `image_content` / `map_data` when notified. ## Background: Understanding Device Protocols diff --git a/roborock/devices/traits/b01/q7/map_content.py b/roborock/devices/traits/b01/q7/map_content.py index 6c55aeed..56c180ce 100644 --- a/roborock/devices/traits/b01/q7/map_content.py +++ b/roborock/devices/traits/b01/q7/map_content.py @@ -6,6 +6,10 @@ - fields `image_content`, `map_data`, and `raw_api_response` are then readable For B01/Q7 devices, the underlying raw map payload is retrieved via `MapTrait`. + +Q7 devices additionally stream map frames unprompted while cleaning; those arrive +through `update_from_push()`, which keeps the same cached fields current and +notifies registered update listeners. """ import asyncio diff --git a/tests/devices/traits/b01/q7/test_map_content.py b/tests/devices/traits/b01/q7/test_map_content.py index 7582580d..e07e7720 100644 --- a/tests/devices/traits/b01/q7/test_map_content.py +++ b/tests/devices/traits/b01/q7/test_map_content.py @@ -119,8 +119,11 @@ async def test_q7_map_content_updates_from_push( fake_channel.map_push_callback(pushed_payload) assert q7_api.map_content.image_content == b"pngbytes" + assert q7_api.map_content.map_data is dummy_map_data assert q7_api.map_content.raw_api_response == pushed_payload assert updates == [True] + # No RPC was needed to get here. + assert fake_channel.published_commands == [] await q7_api.close() assert fake_channel.map_push_callback is None