Skip to content

feat: add Q10 zone, position, and goto support - #908

Open
hmmbob wants to merge 2 commits into
Python-roborock:mainfrom
hmmbob:agent/q10-zone-position-support
Open

feat: add Q10 zone, position, and goto support#908
hmmbob wants to merge 2 commits into
Python-roborock:mainfrom
hmmbob:agent/q10-zone-position-support

Conversation

@hmmbob

@hmmbob hmmbob commented Jul 31, 2026

Copy link
Copy Markdown

Summary

  • add Q10 conversion helpers for the common Roborock coordinate space
  • expose the latest Q10 trace position and cleaning-session sequence
  • add native VacuumTrait.clean_zone() support
  • add VacuumTrait.goto_position() as one managed 40 x 40 cm mini-zone operation
  • retry transient arrival-pause failures and stop an owned task after five minutes
  • stop monitoring without controlling a task replaced by the official app

Why

Q10/B01 devices use two dock-relative coordinate encodings: trace points use
2.5 mm units and zone vectors use 5 mm units. Home Assistant's existing
Roborock actions use the common millimetre coordinate space with the dock at
(25500, 25500).

Q10 firmware does not expose the V1 app_goto_target command. Goto is therefore
emulated with the official app's minimum 40 x 40 cm zone. The lifecycle belongs
in python-roborock so callers do not need to encode device protocol details or
implement their own polling state machine. The trace session counter identifies
the task started by goto; a newer session is treated as a replacement and is
never paused or stopped by the old monitor.

Hardware validation

Validated on a Roborock Q10 (roborock.vacuum.ss07) running firmware 03.11.24.

  • native task type 3 started the requested 40 x 40 cm zone
  • live trace conversion reported movement in the common coordinate space
  • target (29900, 28650) paused at (30020, 28705), 13.2 cm away
  • the robot remained at the destination instead of returning to the dock
  • the payload matches an independently constructed source-verified fixture

Tests

  • 82 focused Q10 trait/map tests passed
  • Ruff check and format passed for the complete roborock and tests trees
  • mypy passed for all changed Q10 trait modules

@hmmbob hmmbob changed the title Add Q10 zone cleaning and position coordinates feat: add Q10 zone cleaning and position coordinates Jul 31, 2026
@hmmbob
hmmbob force-pushed the agent/q10-zone-position-support branch from 09d3295 to 0ada4c3 Compare July 31, 2026 14:42
@hmmbob
hmmbob marked this pull request as ready for review July 31, 2026 14:45
@hmmbob
hmmbob marked this pull request as draft July 31, 2026 14:56
@hmmbob hmmbob changed the title feat: add Q10 zone cleaning and position coordinates feat: add Q10 zone, position, and goto support Jul 31, 2026
@hmmbob
hmmbob marked this pull request as ready for review July 31, 2026 17:17

@allenporter allenporter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this great contribution, see my comments below.


@property
def roborock_position(self) -> Q10Point | None:
"""Current position in the common Roborock millimetre coordinate space."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have information written in the PR, but it should also be here (e.g. Whats the difference between roborock_positiion and roborock_position from an end user perspective? how should I be using either of these?)

But really, I would advocate for taking a more opinionated stance: Should we make a breaking change here and always use the common coordinate system? We can consider the Q10 specific thing an implementation.


async def clean_zone(
self,
x1: int,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should:

  • Define a new variation of Q10Point that is in roborock coordinate space (and we say Q10 is in trace space) e.g. Q10RoborockPoint (or some better name). This can be used for input here and output (e.g. exposed current position)
  • Accept Q10RoborockPoint (or new name here).
  • Q10Point and Q10RoborockPoint can have functions for converting between each other (e.g. handling the roborock to vector coordinate functions)
  • Then for our internal start command params add a CleanParams dataclass object that holds the roborock points or q10 points, and handles sorting them.
  • Move the struct packing in roborock/protocols/b01_q10_protocol.py e.g. def encode_clean_params(params: CleanParams) -> str


async def goto_position(self, x: int, y: int) -> None:
"""Move to a coordinate using an owned 40 cm zone-clean task."""
if (position := self._map.roborock_position) is not None and hypot(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will _async_monitor_goto_target effectively do the same thing?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not entirely, as I understand it. The monitor pauses the goto it already owns when the robot reaches that goto’s original target. This early check also avoids starting a new zone when the robot is already at the requested position. It additionally handles a new goto request for the robot’s current position while another owned goto is still active, even if the previous target was different.

I think the comment below addresses this as well, right?

self._goto_monitor_task = None
self._goto_trace_sequence = None

async def _async_monitor_goto_target(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of the logic in this method is related to understanding if the current task is allowed to change state -- and it's intermixed with the other business logic for trying to coordinate the vacuum move. I think we can simplify this by having a separate standalone object that manages a single goto lifecycle (e.g. GotoAction. The action can handle the logic for the next step to take given the information it has (e.g. checking current status of the vaccum, and deciding when to stop) but keep the actions handled by vacuum.py which manages the lifecycle of GotoAction -- it would register listeners/callbacks on the GotoAction then do the work to call the "pause" or "stop" command.

self._goto_monitor_task.cancel()
self._goto_monitor_task = None
self._goto_trace_sequence = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I like this approach of listening for map and status events. Are you finding that results are streamed back reliably such that it works without additional polling?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, at least on my ss07 running firmware 03.11.24. During the live goto tests, the trace and status updates arrived reliably enough to detect arrival and pause the robot. We did not use periodic polling or call refresh() from the goto monitor.

@hmmbob

hmmbob commented Aug 1, 2026

Copy link
Copy Markdown
Author

@allenporter Thanks for the detailed review. I used Codex to help build this, so I want to be transparent about that. I do understand the functional side of the implementation (it actually developed this based on my questions regarding robot positioning and if we could use the zoned cleaning as alternative for the goto) and have tested it on my own Q10, but I’m happy to have Codex rework the internals along the lines you suggested. It told me already it understood your ideas 😉

Codex:

My understanding is that you’d like the public API to use one common Roborock coordinate space, with typed points and the Q10-specific conversions treated as implementation details. The clean parameters and binary encoding should move to the protocol layer, while the goto lifecycle should be separated into something like a GotoAction, leaving VacuumTrait responsible for listeners and sending the actual commands. Please correct me if I’ve misunderstood any part of that.

Regarding the push updates: on my ss07 running firmware 03.11.24, the map/trace and status updates were streamed reliably during the live goto tests. The robot reached the target and was paused based solely on those updates; we did not use any additional polling or periodic refresh() calls. I was actually quite happy we got this approach to work 😃

There is also a downstream Home Assistant PR using this API: home-assistant/core#177780. You may want to review that as well, particularly to make sure the public API introduced here fits the way Home Assistant consumes it.

If the above matches what you have in mind, I’ll have Codex work through the individual comments and update the PR accordingly. Also, if you prefer to take over and implement it yourself, I'd be ok with that as well. As said, I am not a dev, but a Q10 (ss07) owner happy to have my tokens do some useful work so don't hold back on commenting. I'm actually having quite some fun guiding Codex doing this.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants