Skip to content

Initial room configuration support for Q10 devices#837

Open
lboue wants to merge 6 commits into
Python-roborock:mainfrom
lboue:q10_rooms
Open

Initial room configuration support for Q10 devices#837
lboue wants to merge 6 commits into
Python-roborock:mainfrom
lboue:q10_rooms

Conversation

@lboue
Copy link
Copy Markdown
Contributor

@lboue lboue commented Jun 3, 2026

Summary

This PR adds initial room configuration support for Q10 devices using the B01 protocol.

It introduces parsing for the CUSTOMER_CLEAN payload, exposes a dedicated Q10 rooms trait, and wires that trait into Q10PropertiesApi so room configuration can be refreshed and accessed through high-level helpers. It also adds focused test coverage for room parsing, streaming updates, and the new API helpers.

What Changed

  • Added Q10 room payload parsing in b01_q10_containers.py
  • Added a new Q10 rooms trait in rooms.py
  • Exposed room helpers on init.py:
    • refresh_room_config()
    • room_map
    • room_names
    • get_room()
    • get_room_name()
  • Added tests for room parsing and Q10 rooms API behavior in test_rooms.py

Behavior

  • Q10 devices can now request and decode the current room configuration from the robot
  • rr_* firmware room names are normalized into user-friendly labels such as Living Room

Validation

  • Focused Q10 rooms tests pass locally: 4 passed
  • Local smoke test with a real Roborock account succeeded via local script

lboue and others added 5 commits June 3, 2026 20:18
… helpers

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
@lboue lboue marked this pull request as ready for review June 3, 2026 18:45
Copilot AI review requested due to automatic review settings June 3, 2026 18:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for reading and refreshing room configuration for Q10 (B01) devices, including parsing the CUSTOMER_CLEAN payload and exposing room helpers on the properties API.

Changes:

  • Introduces RoomsTrait and wires it into Q10PropertiesApi subscribe loop and helper methods.
  • Adds room-config parsing/container models for the CUSTOMER_CLEAN base64 payload.
  • Adds tests covering streaming updates, listener notifications, and refresh requests for room config.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
tests/devices/traits/b01/q10/test_rooms.py Adds integration-style tests for rooms streaming, listener behavior, and refresh request payload.
roborock/devices/traits/b01/q10/rooms.py Implements RoomsTrait to refresh and parse room configuration updates and notify listeners.
roborock/devices/traits/b01/q10/init.py Wires RoomsTrait into Q10PropertiesApi and adds convenience room helper APIs.
roborock/data/b01_q10/b01_q10_containers.py Adds dataclasses and parsing/normalization for the room configuration payload.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread roborock/devices/traits/b01/q10/rooms.py Outdated
Comment thread roborock/data/b01_q10/b01_q10_containers.py
Comment thread roborock/data/b01_q10/b01_q10_containers.py Outdated
Comment thread roborock/data/b01_q10/b01_q10_containers.py Outdated
Comment thread tests/devices/traits/b01/q10/test_rooms.py Outdated
Comment thread tests/devices/traits/b01/q10/test_rooms.py Outdated
Comment thread roborock/data/b01_q10/b01_q10_containers.py Outdated
Comment thread roborock/data/b01_q10/b01_q10_containers.py

assert parsed.declared_count == 1
assert parsed.parsed_count == 1
room = parsed.rooms[0]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you add a test for an instance with multiple rooms?


for index in range(count):
if offset + _Q10_CUSTOMER_CLEAN_ROOM_BLOCK_SIZE > len(raw):
break
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So I'm not sure about all of theses breaks. if one step fails, we are returning partial data, which I think can be problematic.

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.

Also we could just check them all at once. Seems like the total length be the count * (26+20+1] then read vertex count then check its vertex_count * _Q10_CUSTOMER_CLEAN_VERTEX_SIZE, then we don't ned to check again.

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.

Wonder if using construct would be helpful like how some of the other packages are parsed in protocol.py...

rooms: RoomsTrait
"""Trait for reading room configuration from Q10 devices."""

@property
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.

Seems like these should just be on RoomsTrait and not here

# to the device. Updates will be received by the subscribe loop below.
await self.command.send(B01_Q10_DP.REQUEST_DPS, params={})

async def refresh_room_config(self) -> 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.

Similar here, these should all be on the rooms trait

return {room.room_id: room for room in self.rooms}


def parse_customer_clean_payload(payload_b64: str) -> Q10RoomsConfig:
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.

This seems to me like it fits better next to RoomsTrait.



def normalize_q10_room_name(room_name: str) -> str:
"""Normalize room names reported by the Q10 firmware."""
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.

Maybe would be good to give some examples in the pydoc of what this is doing.

return Q10RoomsConfig(raw_length=len(raw), declared_count=count, rooms=rooms)


def normalize_q10_room_name(room_name: str) -> str:
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.

Could also consider making this a property on Q10RoomConfig which exposes the name (e.g.raw_name and name as a property.)


for index in range(count):
if offset + _Q10_CUSTOMER_CLEAN_ROOM_BLOCK_SIZE > len(raw):
break
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.

Also we could just check them all at once. Seems like the total length be the count * (26+20+1] then read vertex count then check its vertex_count * _Q10_CUSTOMER_CLEAN_VERTEX_SIZE, then we don't ned to check again.


for index in range(count):
if offset + _Q10_CUSTOMER_CLEAN_ROOM_BLOCK_SIZE > len(raw):
break
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.

Wonder if using construct would be helpful like how some of the other packages are parsed in protocol.py...

class Q10RoomConfig(RoborockBase):
index: int
room_id: int
room_type: 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.

Are these all some kind of enum value? trying to reason about what "-1" will mean here and wondering if int | None would be better or RoomTypeEnum | None if we can figure those out.

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.

4 participants