Skip to content

Commit 2ccb776

Browse files
authored
Merge branch 'main' into pr/zeo-push
2 parents 169a6a9 + e8d5466 commit 2ccb776

21 files changed

Lines changed: 835 additions & 222 deletions

CHANGELOG.md

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

33
<!-- version list -->
44

5+
## v6.2.1 (2026-08-02)
6+
7+
### Bug Fixes
8+
9+
- Show docked Q10 robot on map ([#904](https://github.com/Python-roborock/python-roborock/pull/904),
10+
[`df75659`](https://github.com/Python-roborock/python-roborock/commit/df75659f4ee87d1c04f241bc0b1fa7b6d1f70715))
11+
12+
### Refactoring
13+
14+
- Render Q10 overlays with V1 ([#903](https://github.com/Python-roborock/python-roborock/pull/903),
15+
[`347a8f6`](https://github.com/Python-roborock/python-roborock/commit/347a8f6190cb8b7b8b607f3ca561ae025be956b5))
16+
17+
18+
## v6.2.0 (2026-07-30)
19+
20+
### Features
21+
22+
- Align Q10 map colors with V1 ([#902](https://github.com/Python-roborock/python-roborock/pull/902),
23+
[`dbb6c46`](https://github.com/Python-roborock/python-roborock/commit/dbb6c4663bcd5a0560d7438b49272ab53b91c4f4))
24+
25+
### Refactoring
26+
27+
- Clarify room palette cache handling
28+
([#902](https://github.com/Python-roborock/python-roborock/pull/902),
29+
[`dbb6c46`](https://github.com/Python-roborock/python-roborock/commit/dbb6c4663bcd5a0560d7438b49272ab53b91c4f4))
30+
31+
32+
## v6.1.0 (2026-07-30)
33+
34+
### Features
35+
36+
- Use display name to fix duplicate code mappings
37+
([#870](https://github.com/Python-roborock/python-roborock/pull/870),
38+
[`d0a99c2`](https://github.com/Python-roborock/python-roborock/commit/d0a99c28e65d6c1bc6bc00852b261d4a82b3feb7))
39+
40+
541
## v6.0.0 (2026-07-27)
642

743
### Features

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "python-roborock"
3-
version = "6.0.0"
3+
version = "6.2.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"

roborock/data/code_mappings.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,22 @@
1010
class RoborockEnum(IntEnum):
1111
"""Roborock Enum for codes with int values"""
1212

13+
_display_name_: str | None
14+
15+
def __new__(cls, value: int, display_name: str | None = None) -> Self:
16+
member = int.__new__(cls, value)
17+
member._value_ = value
18+
member._display_name_ = display_name
19+
return member
20+
1321
@property
1422
def name(self) -> str:
1523
return super().name.lower()
1624

25+
@property
26+
def display_name(self) -> str:
27+
return self._display_name_ or self.name
28+
1729
@classmethod
1830
def _missing_(cls: type[Self], key) -> Self:
1931
if hasattr(cls, "unknown"):
@@ -30,8 +42,13 @@ def _missing_(cls: type[Self], key) -> Self:
3042
return default_value
3143

3244
@classmethod
33-
def as_dict(cls: type[Self]):
34-
return {i.name: i.value for i in cls if i.name != "missing"}
45+
def as_dict(cls: type[Self]) -> dict[str, int]:
46+
result: dict[str, int] = {}
47+
for item in cls:
48+
if item.name == "missing":
49+
continue
50+
result.setdefault(item.display_name, item.value)
51+
return result
3552

3653
@classmethod
3754
def as_enum_dict(cls: type[Self]):

roborock/data/dyad/dyad_code_mappings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class DyadError(RoborockEnum):
9595
battery_temperature_protection = (
9696
20007 # Battery temperature protection. Wait for the temperature to return to a normal range.
9797
)
98-
battery_temperature_protection_2 = 20008
98+
battery_temperature_protection_2 = (20008, "battery_temperature_protection")
9999
power_adapter_error = 20009 # Check if the power adapter is working properly.
100100
dirty_charging_contacts = 10007 # Disconnection between the device and dock. Wipe charging contacts.
101101
low_battery = 20017 # Low battery level. Charge before starting self-cleaning.

roborock/data/v1/v1_code_mappings.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,40 @@ class RoborockFinishReason(RoborockEnum):
88
manual_interrupt = 21 # Cleaning interrupted by user
99
cleanup_interrupted = 24 # Cleanup interrupted
1010
manual_interrupt_2 = 21
11-
manual_interrupt_12 = 29
11+
manual_interrupt_12 = (29, "manual_interrupt")
1212
breakpoint = 32 # Could not continue cleaning
13-
breakpoint_2 = 33
14-
cleanup_interrupted_2 = 34
15-
manual_interrupt_3 = 35
16-
manual_interrupt_4 = 36
17-
manual_interrupt_5 = 37
18-
manual_interrupt_6 = 43
13+
breakpoint_2 = (33, "breakpoint")
14+
cleanup_interrupted_2 = (34, "cleanup_interrupted")
15+
manual_interrupt_3 = (35, "manual_interrupt")
16+
manual_interrupt_4 = (36, "manual_interrupt")
17+
manual_interrupt_5 = (37, "manual_interrupt")
18+
manual_interrupt_6 = (43, "manual_interrupt")
1919
locate_fail = 45 # Positioning Failed
20-
cleanup_interrupted_3 = 64
21-
locate_fail_2 = 65
22-
manual_interrupt_7 = 48
23-
manual_interrupt_8 = 49
24-
manual_interrupt_9 = 50
25-
cleanup_interrupted_4 = 51
20+
cleanup_interrupted_3 = (64, "cleanup_interrupted")
21+
locate_fail_2 = (65, "locate_fail")
22+
manual_interrupt_7 = (48, "manual_interrupt")
23+
manual_interrupt_8 = (49, "manual_interrupt")
24+
manual_interrupt_9 = (50, "manual_interrupt")
25+
cleanup_interrupted_4 = (51, "cleanup_interrupted")
2626
finished_cleaning = 52 # Finished cleaning
27-
finished_cleaning_2 = 54
28-
finished_cleaning_3 = 55
29-
finished_cleaning_4 = 56
30-
finished_clenaing_5 = 57
31-
manual_interrupt_10 = 60
27+
finished_cleaning_2 = (54, "finished_cleaning")
28+
finished_cleaning_3 = (55, "finished_cleaning")
29+
finished_cleaning_4 = (56, "finished_cleaning")
30+
finished_clenaing_5 = (57, "finished_cleaning")
31+
manual_interrupt_10 = (60, "manual_interrupt")
3232
area_unreachable = 61 # Area unreachable
33-
area_unreachable_2 = 62
33+
area_unreachable_2 = (62, "area_unreachable")
3434
washing_error = 67 # Washing error
3535
back_to_wash_failure = 68 # Failed to return to the dock
36-
cleanup_interrupted_5 = 101
37-
breakpoint_4 = 102
38-
manual_interrupt_11 = 103
39-
cleanup_interrupted_6 = 104
40-
cleanup_interrupted_7 = 105
41-
cleanup_interrupted_8 = 106
42-
cleanup_interrupted_9 = 107
43-
cleanup_interrupted_10 = 109
44-
cleanup_interrupted_11 = 110
36+
cleanup_interrupted_5 = (101, "cleanup_interrupted")
37+
breakpoint_4 = (102, "breakpoint")
38+
manual_interrupt_11 = (103, "manual_interrupt")
39+
cleanup_interrupted_6 = (104, "cleanup_interrupted")
40+
cleanup_interrupted_7 = (105, "cleanup_interrupted")
41+
cleanup_interrupted_8 = (106, "cleanup_interrupted")
42+
cleanup_interrupted_9 = (107, "cleanup_interrupted")
43+
cleanup_interrupted_10 = (109, "cleanup_interrupted")
44+
cleanup_interrupted_11 = (110, "cleanup_interrupted")
4545
patrol_success = 114 # Cruise completed
4646
patrol_fail = 115 # Cruise failed
4747
pet_patrol_success = 116 # Pet found
@@ -159,7 +159,7 @@ class ClearWaterBoxStatus(RoborockDssCodes):
159159

160160
okay = 0
161161
out_of_water = 1
162-
out_of_water_2 = 38
162+
out_of_water_2 = (38, "out_of_water")
163163
refill_error = 48
164164

165165

@@ -168,7 +168,7 @@ class DirtyWaterBoxStatus(RoborockDssCodes):
168168

169169
okay = 0
170170
full_not_installed = 1
171-
full_not_installed_2 = 39
171+
full_not_installed_2 = (39, "full_not_installed")
172172
drain_error = 49
173173

174174

@@ -232,7 +232,7 @@ class RoborockErrorCode(RoborockEnum):
232232
clear_brush_exception = 42 # Check that the water filter has been correctly installed
233233
clear_brush_exception_2 = 43 # Positioning button error
234234
filter_screen_exception = 44 # Clean the dock water filter
235-
mopping_roller_2 = 45 # Wash roller may be jammed
235+
mopping_roller_2 = (45, "mopping_roller_1") # Wash roller may be jammed
236236
up_water_exception = 48
237237
drain_water_exception = 49
238238
temperature_protection = 51 # Unit temperature protection
@@ -421,7 +421,7 @@ class RoborockStateCode(RoborockEnum):
421421
segment_cleaning = 18
422422
emptying_the_bin = 22 # on s7+
423423
washing_the_mop = 23 # on a46
424-
washing_the_mop_2 = 25
424+
washing_the_mop_2 = (25, "washing_the_mop")
425425
going_to_wash_the_mop = 26 # on a46
426426
in_call = 28
427427
mapping = 29

roborock/data/v1/v1_containers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,11 @@ def square_meter_clean_area(self) -> float | None:
144144

145145
@property
146146
def error_code_name(self) -> str | None:
147-
return self.error_code.name if self.error_code is not None else None
147+
return self.error_code.display_name if self.error_code is not None else None
148148

149149
@property
150150
def state_name(self) -> str | None:
151-
return self.state.name if self.state is not None else None
151+
return self.state.display_name if self.state is not None else None
152152

153153
@property
154154
def current_map(self) -> int | None:

roborock/devices/traits/b01/q10/map.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
55
* map packets are decoded from map-protocol responses;
66
* trace packets are decoded from trace-protocol responses;
7-
* restricted zones and virtual walls arrive as ordinary DPS values.
7+
* restricted zones, virtual walls and dock state arrive as ordinary DPS values.
88
9-
``MapDpsTrait`` owns the low-level DPS read model. ``MapContentTrait`` depends
10-
on it and combines that state with the latest map/trace packets through the pure
11-
functions in :mod:`roborock.map.b01_q10_render`. The high-level trait keeps only
12-
the latest value from each source and one replace-whole rendered image;
9+
``MapDpsTrait`` owns the low-level map-specific DPS read model.
10+
``MapContentTrait`` combines that state with the latest map/trace packets
11+
through the pure functions in :mod:`roborock.map.b01_q10_render`. The high-level
12+
trait keeps only the latest value from each source and one replace-whole image;
1313
calibration, path placement and overlay placement remain inside the renderer.
1414
"""
1515

@@ -18,7 +18,7 @@
1818
from typing import Any
1919

2020
from roborock.data import RoborockBase
21-
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
21+
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP, YXDeviceState
2222
from roborock.devices.traits.common import DpsDataConverter, TraitUpdateListener
2323
from roborock.exceptions import RoborockException
2424
from roborock.map.b01_q10_map_parser import (
@@ -34,12 +34,14 @@
3434
from .common import UpdatableTrait
3535

3636
_LOGGER = logging.getLogger(__name__)
37+
_DOCKED_STATES = {YXDeviceState.CHARGING, YXDeviceState.EMPTYING_THE_BIN}
3738

3839

3940
@dataclass
4041
class MapDps(RoborockBase):
4142
"""Low-level map values delivered in the Q10 DPS stream."""
4243

44+
status: YXDeviceState | None = field(default=None, metadata={"dps": B01_Q10_DP.STATUS})
4345
restricted_zone_up: str | None = field(default=None, metadata={"dps": B01_Q10_DP.RESTRICTED_ZONE_UP})
4446
virtual_wall_up: str | None = field(default=None, metadata={"dps": B01_Q10_DP.VIRTUAL_WALL_UP})
4547

@@ -59,8 +61,13 @@ def overlays(self) -> Q10MapOverlays:
5961
"""Overlays decoded once from the latest relevant DPS update."""
6062
return self._overlays
6163

64+
@property
65+
def robot_at_dock(self) -> bool:
66+
"""Whether status places the idle robot at the saved dock."""
67+
return self.status in _DOCKED_STATES
68+
6269
def update_from_dps(self, decoded_dps: dict[B01_Q10_DP, Any]) -> None:
63-
"""Decode overlay blobs when they arrive, then notify dependents."""
70+
"""Update one coherent snapshot of the DPS inputs used by the map."""
6471
if not self._CONVERTER.update_from_dps(self, decoded_dps):
6572
return
6673
self._overlays = Q10MapOverlays(
@@ -73,8 +80,8 @@ def update_from_dps(self, decoded_dps: dict[B01_Q10_DP, Any]) -> None:
7380
class MapContentTrait(TraitUpdateListener):
7481
"""High-level composed Q10 map view.
7582
76-
The latest map and trace packets are combined with the injected
77-
:class:`MapDpsTrait` whenever any of those three sources changes.
83+
The latest map and trace packets are combined with the injected map DPS
84+
whenever any source changes.
7885
"""
7986

8087
def __init__(
@@ -129,7 +136,7 @@ def update_from_trace_packet(self, packet: Q10TracePacket) -> None:
129136
self._notify_update()
130137

131138
def _map_dps_updated(self) -> None:
132-
"""Render after the low-level DPS source changes."""
139+
"""Render after the low-level map DPS source changes."""
133140
if self._map_packet is None:
134141
return
135142
self._render()
@@ -142,9 +149,10 @@ def _render(self) -> None:
142149
try:
143150
self._image_content = render_q10_map(
144151
self._map_packet,
145-
self._trace_packet,
152+
self._trace_packet if not self._map_dps.robot_at_dock else None,
146153
self._map_dps.overlays,
147154
config=self._config,
155+
robot_at_dock=self._map_dps.robot_at_dock,
148156
)
149157
except RoborockException as ex:
150158
_LOGGER.debug("Failed to render Q10 map packet: %s", ex)

roborock/devices/traits/common.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,14 @@ def update_from_dps(self, target: RoborockBase, decoded_dps: dict[TDps, Any]) ->
105105
decoded_dps: The decoded DPS data to convert.
106106
107107
Returns:
108-
True if any values were updated, False otherwise.
108+
True if any values changed, False otherwise.
109109
"""
110110
conversions = RoborockBase.convert_dict(self._dps_type_map, decoded_dps)
111+
changed = False
111112
for dps_id, value in conversions.items():
112113
field_name = self._dps_field_map[dps_id]
114+
if getattr(target, field_name) == value:
115+
continue
113116
setattr(target, field_name, value)
114-
return bool(conversions)
117+
changed = True
118+
return changed

0 commit comments

Comments
 (0)