Skip to content

Commit 3a63163

Browse files
Merge branch 'main' into agent/q7-map-layers
2 parents 0886033 + 031b9e2 commit 3a63163

48 files changed

Lines changed: 3224 additions & 503 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,229 @@
22

33
<!-- version list -->
44

5+
## v5.31.1 (2026-07-18)
6+
7+
### Bug Fixes
8+
9+
- Correct reset consumable parameters for strainer and cleaning brush
10+
([#879](https://github.com/Python-roborock/python-roborock/pull/879),
11+
[`b11fc49`](https://github.com/Python-roborock/python-roborock/commit/b11fc4984445daa9a1837cef366d8543c3d1b7c8))
12+
13+
### Chores
14+
15+
- Add note about apache-II in CONTRIBUTING.md
16+
([#877](https://github.com/Python-roborock/python-roborock/pull/877),
17+
[`a8a5821`](https://github.com/Python-roborock/python-roborock/commit/a8a582192b3ec1c74f9b56c83bda026bd6ec194b))
18+
19+
20+
## v5.31.0 (2026-07-15)
21+
22+
### Features
23+
24+
- Add Q10 integration test and support stream streaming on FakeChannel
25+
([#869](https://github.com/Python-roborock/python-roborock/pull/869),
26+
[`502fa42`](https://github.com/Python-roborock/python-roborock/commit/502fa424c4a8fd36b759de8c8557c8665e4d0d13))
27+
28+
- Implement Q10VacuumSimulator and export it
29+
([#869](https://github.com/Python-roborock/python-roborock/pull/869),
30+
[`502fa42`](https://github.com/Python-roborock/python-roborock/commit/502fa424c4a8fd36b759de8c8557c8665e4d0d13))
31+
32+
- Implement stateful Q10 device simulator and integration tests
33+
([#869](https://github.com/Python-roborock/python-roborock/pull/869),
34+
[`502fa42`](https://github.com/Python-roborock/python-roborock/commit/502fa424c4a8fd36b759de8c8557c8665e4d0d13))
35+
36+
37+
## v5.30.0 (2026-07-13)
38+
39+
### Bug Fixes
40+
41+
- Gate dock_error_status on has_dock rather than is_collectable
42+
([#875](https://github.com/Python-roborock/python-roborock/pull/875),
43+
[`86acd87`](https://github.com/Python-roborock/python-roborock/commit/86acd874e74c391d85c63483bee02a88b8a2debb))
44+
45+
### Documentation
46+
47+
- Document metadata keys in field_metadata and is_field_supported
48+
([#875](https://github.com/Python-roborock/python-roborock/pull/875),
49+
[`86acd87`](https://github.com/Python-roborock/python-roborock/commit/86acd874e74c391d85c63483bee02a88b8a2debb))
50+
51+
### Features
52+
53+
- Implement device capability metadata for status computed property fields
54+
([#875](https://github.com/Python-roborock/python-roborock/pull/875),
55+
[`86acd87`](https://github.com/Python-roborock/python-roborock/commit/86acd874e74c391d85c63483bee02a88b8a2debb))
56+
57+
58+
## v5.29.0 (2026-07-12)
59+
60+
### Features
61+
62+
- Truncate large payloads in RoborockMessage __repr__
63+
([#873](https://github.com/Python-roborock/python-roborock/pull/873),
64+
[`95460d7`](https://github.com/Python-roborock/python-roborock/commit/95460d77a599613a0eddab3f4db7a36904f894d6))
65+
66+
67+
## v5.28.1 (2026-07-12)
68+
69+
### Bug Fixes
70+
71+
- Correct some dock mapping ([#872](https://github.com/Python-roborock/python-roborock/pull/872),
72+
[`9e95992`](https://github.com/Python-roborock/python-roborock/commit/9e95992fcfceb70a0e73c86dc819cecc6dc572d1))
73+
74+
### Refactoring
75+
76+
- Use stateful fakes and fixtures in integration tests
77+
([#868](https://github.com/Python-roborock/python-roborock/pull/868),
78+
[`89c8074`](https://github.com/Python-roborock/python-roborock/commit/89c80743e2c560f36f56e0c17c85c0d14544f406))
79+
80+
81+
## v5.28.0 (2026-07-08)
82+
83+
### Features
84+
85+
- Add better dock feature control
86+
([#867](https://github.com/Python-roborock/python-roborock/pull/867),
87+
[`73ba2bf`](https://github.com/Python-roborock/python-roborock/commit/73ba2bf152d1c43b4d46d1e7f397043dd195d046))
88+
89+
90+
## v5.27.0 (2026-07-07)
91+
92+
### Documentation
93+
94+
- Add comment explaining dss field in v1_simulator
95+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
96+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
97+
98+
### Features
99+
100+
- Add roborock.testing module for stateful integration testing
101+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
102+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
103+
104+
- Expose dock_type configuration in V1VacuumSimulator
105+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
106+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
107+
108+
- Expose in_cleaning, in_returning, charge_status, and dss on V1VacuumSimulator
109+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
110+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
111+
112+
- Honor publish_side_effect in simulator and raise NotImplementedError in base _handle_publish
113+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
114+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
115+
116+
### Refactoring
117+
118+
- Decouple default product id from device duid and share DEFAULT_PRODUCT_ID constant
119+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
120+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
121+
122+
- Delete redundant properties from V1VacuumSimulator and update trigger_push_update
123+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
124+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
125+
126+
- Export and define all simulator state default constants at package level
127+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
128+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
129+
130+
- Expose raw status and app_init dataclasses on simulator for direct test manipulation
131+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
132+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
133+
134+
- Inherit FakeChannel from Channel Protocol
135+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
136+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
137+
138+
- Rename self.servers to self.simulated_devices and restore hardcoded qiCNieZa in mock_data
139+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
140+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
141+
142+
- Replace FakeUserState with library UserData dataclass on FakeRoborockCloud
143+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
144+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
145+
146+
- Type simulator consumables state using Consumable dataclass
147+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
148+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
149+
150+
- Type simulator dnd, clean summary and record using real dataclasses
151+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
152+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
153+
154+
- Type simulator status, network info and init status using real dataclasses
155+
([#860](https://github.com/Python-roborock/python-roborock/pull/860),
156+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
157+
158+
- Update _handle_app_get_init_status to serialize self.app_init and dynamically resolve status_info
159+
fields from self.status ([#860](https://github.com/Python-roborock/python-roborock/pull/860),
160+
[`c963ad7`](https://github.com/Python-roborock/python-roborock/commit/c963ad79b7d7206dd1b97cd7f377305a67f90364))
161+
162+
163+
## v5.26.0 (2026-07-06)
164+
165+
### Documentation
166+
167+
- Add 569 back to the docstring's Q7-divergence list
168+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
169+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
170+
171+
- Mark fault 12 hw-confirmed (fired at 14% battery, corpus recount)
172+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
173+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
174+
175+
- Mark fault 2 hw-confirmed (bumper held during commanded motion)
176+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
177+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
178+
179+
- Mark fault 407 hw-confirmed (deterministically reproduced: due schedule ignored mid-clean)
180+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
181+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
182+
183+
- Mark fault 46 hw-confirmed (docked with dust bag removed)
184+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
185+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
186+
187+
- Mark fault 5 hw-confirmed (physically ground-truthed brush jams)
188+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
189+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
190+
191+
- Note fault 501 fires per completed task
192+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
193+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
194+
195+
- Note the error state can carry no fault code (observed live)
196+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
197+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
198+
199+
- Refer to the ss07 fault spec, not the app (review feedback)
200+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
201+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
202+
203+
### Features
204+
205+
- Decode Q10 (B01/ss07) dpFault codes into a YXFault map
206+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
207+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
208+
209+
- Decode Q10 (B01/ss07) dpFault codes with a YXFault enum
210+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
211+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
212+
213+
- Re-add fault 569 as too_frequent_emptying, hw-confirmed (dock refuses a too-soon empty)
214+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
215+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
216+
217+
### Refactoring
218+
219+
- Decouple B01 (Q7/Q10) protocol layer from transport layer
220+
([#859](https://github.com/Python-roborock/python-roborock/pull/859),
221+
[`b00d60b`](https://github.com/Python-roborock/python-roborock/commit/b00d60b9f70d6a5da9089e470db60b04437c0b14))
222+
223+
- Type Q10Status.fault as YXFault, mirroring the Q7 fault field
224+
([#863](https://github.com/Python-roborock/python-roborock/pull/863),
225+
[`32f9747`](https://github.com/Python-roborock/python-roborock/commit/32f9747b368c4d58a78691a0106a8c9676a07e2c))
226+
227+
5228
## v5.25.0 (2026-07-03)
6229

7230
### Chores

CONTRIBUTING.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
Thank you for your interest in contributing to `python-roborock`! We welcome contributions from the community.
44

5+
## Licensing of Contributions
6+
7+
By submitting a contribution to this repository, you agree that your contribution is
8+
licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0),
9+
and you certify that you have the right to submit it under that license.
10+
11+
Note: the project is currently distributed under GPL-3.0 while we complete a planned
12+
migration to Apache 2.0. Apache 2.0 is GPL-compatible, so contributions made under
13+
Apache 2.0 can be included today and will carry over unchanged after the migration.
14+
515
## Getting Started
616

717
1. **Fork the repository** on GitHub.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "python-roborock"
3-
version = "5.25.0"
3+
version = "5.31.1"
44
description = "A package to control Roborock vacuums."
55
authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
66
requires-python = ">=3.11, <4"
@@ -52,6 +52,8 @@ dev = [
5252
# Pull in the CLI dependencies so maintainers can run the `roborock`
5353
# command and pdoc can import roborock.cli for docs generation.
5454
"python-roborock[cli]",
55+
# aioresponses is not compatible with aiohttp 3.14 yet. https://github.com/pnuckowski/aioresponses/issues/289
56+
"aiohttp>=3.8.2,<3.14",
5557
"pytest-asyncio>=1.1.0",
5658
"pytest",
5759
"pre-commit>=3.5,<5.0",

roborock/data/b01_q10/b01_q10_code_mappings.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,70 @@ class YXCarpetCleanType(RoborockModeEnum):
289289
CROSS = "cross", 3
290290

291291

292+
class YXFault(RoborockModeEnum):
293+
"""Q10 (B01/ss07) ``dpFault`` (90) codes, from the ss07 fault spec.
294+
295+
``dpFault`` is *overloaded*: several values are lifecycle/status rather than
296+
errors (e.g. 400 = scheduled clean starting, 501 = returning to dock,
297+
502 = recharge). A non-zero fault is not necessarily a blocking error.
298+
The converse also holds: the device can sit in its error state (``dpStatus``
299+
12) with ``dpFault`` still 0 (observed live with the dust-bin module
300+
removed), so a ``None``/``NONE`` fault does not imply the absence of an
301+
error condition.
302+
303+
These labels differ from the Q7 ``B01Fault`` for several shared numbers
304+
(500, 501, 503, 569, 570) -- so this is a Q10-specific map, not a reuse of
305+
``B01Fault``. Codes marked "hw-confirmed" were observed firing live on a
306+
physical ss07 in a context matching the label; the rest are from the spec
307+
only, not yet observed live (single device).
308+
"""
309+
310+
UNKNOWN = "unknown", -1
311+
NONE = "none", 0
312+
LIDAR_BLOCKED = "lidar_blocked", 1
313+
BUMPER_STUCK = "bumper_stuck", 2 # hw-confirmed (bumper held in during commanded motion)
314+
ROBOT_SUSPENDED = "robot_suspended", 3 # hw-confirmed (wheels lifted off the floor mid-task)
315+
CLIFF_SENSOR_ERROR = "cliff_sensor_error", 4 # hw-confirmed (one side lifted mid-task)
316+
MAIN_BRUSH_STUCK = "main_brush_stuck", 5 # hw-confirmed (two physical brush jams)
317+
MAIN_WHEELS_STUCK = "main_wheels_stuck", 7
318+
ROBOT_TRAPPED = "robot_trapped", 8 # hw-confirmed
319+
CHECK_DUSTBIN_FILTER = "check_dustbin_filter", 9
320+
LOW_BATTERY = "low_battery", 12 # hw-confirmed (fired at 14% mid clean)
321+
TEMPERATURE_THRESHOLD = "temperature_threshold", 14
322+
ROBOT_TILTED = "robot_tilted", 16
323+
LIDAR_COVER_OBSTRUCTED = "lidar_cover_obstructed", 21
324+
NO_GO_ZONE_DETECTED = "no_go_zone_detected", 24
325+
MOPPING_MODULE_STUCK = "mopping_module_stuck", 27
326+
CARPET_AVOIDANCE = "carpet_avoidance", 28
327+
CANNOT_CROSS_CARPET = "cannot_cross_carpet", 29
328+
INSTALL_DUST_BAG = "install_dust_bag", 46 # hw-confirmed (docked with bag removed; fires at dock contact)
329+
MOP_MOUNT_FELL_OFF = "mop_mount_fell_off", 54
330+
LIDAR_DIRTY = "lidar_dirty", 58
331+
FILTER_SERVICE_LIFE = "filter_service_life", 301
332+
MAIN_BRUSH_SERVICE_LIFE = "main_brush_service_life", 302
333+
SIDE_BRUSH_SERVICE_LIFE = "side_brush_service_life", 303
334+
SENSOR_NEEDS_CLEANING = "sensor_needs_cleaning", 304 # hw-confirmed (fired during auto-empty)
335+
DUST_BAG_FULL = "dust_bag_full", 310 # inferred from auto-empty context; not hw-confirmed
336+
STARTING_SCHEDULED_CLEAN = "starting_scheduled_clean", 400 # hw-confirmed x3; lifecycle, not an error
337+
# hw-confirmed (a due scheduled clean fired mid-clean and was ignored); lifecycle, not an error
338+
CLEANING_IN_PROGRESS = "cleaning_in_progress", 407
339+
EMPTY_DUSTBIN = "empty_dustbin", 500 # ss07 != Q7 B01Fault (lidar_blocked); spec-only
340+
# hw-confirmed, fires per completed task; ss07 != Q7 (robot_suspended)
341+
CLEANING_COMPLETED_RETURNING = "cleaning_completed_returning", 501
342+
LOW_BATTERY_RESUME = "low_battery_resume", 502 # hw-confirmed; lifecycle
343+
DOCKING_ERROR = "docking_error", 503 # hw-confirmed; ss07 != Q7 (dustbin_not_installed)
344+
POSITIONING_FAILED = "positioning_failed", 556 # hw-confirmed; relocalization
345+
# hw-confirmed (3rd auto-empty in ~15 min: dock refuses to run the cycle); ss07 != Q7 (main_wheels_entangled)
346+
TOO_FREQUENT_EMPTYING = "too_frequent_emptying", 569
347+
CANNOT_REACH_TARGET = "cannot_reach_target", 570 # hw-confirmed; ss07 != Q7 (main_brush_entangled)
348+
OFFLINE_WARNING_ASLEEP = "offline_warning_asleep", 588
349+
OFFLINE_WARNING_LOW_BATTERY = "offline_warning_low_battery", 589
350+
DND_AUTO_TOPUP_DISABLED = "dnd_auto_topup_disabled", 591
351+
CLEAN_CARPET_ULTRASONIC_SENSORS = "clean_carpet_ultrasonic_sensors", 707
352+
ROBOT_ERROR_RESET = "robot_error_reset", 1002
353+
VOICE_PACK_UPDATE_AVAILABLE = "voice_pack_update_available", 3001
354+
355+
292356
class RemoteCommand(IntEnum):
293357
FORWARD = 0
294358
LEFT = 2

roborock/data/b01_q10/b01_q10_containers.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
YXDeviceDustCollectionFrequency,
2424
YXDeviceState,
2525
YXFanLevel,
26+
YXFault,
2627
YXStartMethod,
2728
YXWaterLevel,
2829
)
@@ -165,7 +166,7 @@ class Q10Status(RoborockBase):
165166
clean_task_type: YXDeviceCleanTask | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_TASK_TYPE})
166167
back_type: YXBackType | None = field(default=None, metadata={"dps": B01_Q10_DP.BACK_TYPE})
167168
cleaning_progress: int | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_PROGRESS})
168-
fault: int | None = field(default=None, metadata={"dps": B01_Q10_DP.FAULT})
169+
fault: YXFault | None = field(default=None, metadata={"dps": B01_Q10_DP.FAULT})
169170

170171
# Additional state reported in the device's full status dump.
171172
clean_line: YXCleanLine | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_LINE})
@@ -203,6 +204,11 @@ class Q10Status(RoborockBase):
203204
filter_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.FILTER_LIFE})
204205
sensor_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.SENSOR_LIFE})
205206

207+
@property
208+
def fault_name(self) -> str | None:
209+
"""Returns the name of the current fault."""
210+
return self.fault.value if self.fault is not None else None
211+
206212

207213
@dataclass
208214
class SoundVolume(RoborockBase):

roborock/data/containers.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,25 @@ def _attr_repr(obj: Any) -> str:
6060
return f"{type(obj).__name__}({', '.join(parts)})"
6161

6262

63+
def field_metadata(**kwargs):
64+
"""Decorator to attach capability check metadata to a property.
65+
66+
This attaches a `_field_metadata` dictionary to the underlying getter function,
67+
which is then preserved when decorated with `@property`.
68+
69+
Supported metadata keys:
70+
- `feature` (str): Name of a capability property on `DeviceFeaturesTrait`.
71+
- `dock_feature` (str): Name of a capability property on `RoborockDockFeatures`.
72+
- `dps` (str/int): RoborockDataProtocol ID to check against supported schema IDs.
73+
"""
74+
75+
def decorator(func):
76+
func._field_metadata = kwargs
77+
return func
78+
79+
return decorator
80+
81+
6382
@dataclass(repr=False)
6483
class RoborockBase:
6584
"""Base class for all Roborock data classes."""

0 commit comments

Comments
 (0)