Skip to content

Commit 4cf323b

Browse files
committed
feat: use display name to fix duplicate code mappings
1 parent a5e9cbf commit 4cf323b

6 files changed

Lines changed: 101 additions & 45 deletions

File tree

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
@@ -701,7 +701,7 @@ class RoborockStateCode(RoborockEnum):
701701
segment_cleaning = 18
702702
emptying_the_bin = 22 # on s7+
703703
washing_the_mop = 23 # on a46
704-
washing_the_mop_2 = 25
704+
washing_the_mop_2 = (25, "washing_the_mop")
705705
going_to_wash_the_mop = 26 # on a46
706706
in_call = 28
707707
mapping = 29

roborock/data/v1/v1_containers.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,15 @@ def square_meter_clean_area(self) -> float | None:
191191

192192
@property
193193
def error_code_name(self) -> str | None:
194-
return self.error_code.name if self.error_code is not None else None
194+
return self.error_code.display_name if self.error_code is not None else None
195195

196196
@property
197197
def state_name(self) -> str | None:
198-
return self.state.name if self.state is not None else None
198+
return self.state.display_name if self.state is not None else None
199199

200200
@property
201201
def water_box_mode_name(self) -> str | None:
202-
return self.water_box_mode.name if self.water_box_mode is not None else None
202+
return self.water_box_mode.display_name if self.water_box_mode is not None else None
203203

204204
@property
205205
def fan_power_options(self) -> list[str]:
@@ -209,23 +209,23 @@ def fan_power_options(self) -> list[str]:
209209

210210
@property
211211
def fan_power_name(self) -> str | None:
212-
return self.fan_power.name if self.fan_power is not None else None
212+
return self.fan_power.display_name if self.fan_power is not None else None
213213

214214
@property
215215
def mop_mode_name(self) -> str | None:
216-
return self.mop_mode.name if self.mop_mode is not None else None
216+
return self.mop_mode.display_name if self.mop_mode is not None else None
217217

218-
def get_fan_speed_code(self, fan_speed: str) -> int:
218+
def get_fan_speed_code(self, fan_speed: str) -> int | None:
219219
if self.fan_power is None:
220220
raise RoborockException("Attempted to get fan speed before status has been updated.")
221221
return self.fan_power.as_dict().get(fan_speed)
222222

223-
def get_mop_intensity_code(self, mop_intensity: str) -> int:
223+
def get_mop_intensity_code(self, mop_intensity: str) -> int | None:
224224
if self.water_box_mode is None:
225225
raise RoborockException("Attempted to get mop_intensity before status has been updated.")
226226
return self.water_box_mode.as_dict().get(mop_intensity)
227227

228-
def get_mop_mode_code(self, mop_mode: str) -> int:
228+
def get_mop_mode_code(self, mop_mode: str) -> int | None:
229229
if self.mop_mode is None:
230230
raise RoborockException("Attempted to get mop_mode before status has been updated.")
231231
return self.mop_mode.as_dict().get(mop_mode)
@@ -361,11 +361,11 @@ def square_meter_clean_area(self) -> float | None:
361361

362362
@property
363363
def error_code_name(self) -> str | None:
364-
return self.error_code.name if self.error_code is not None else None
364+
return self.error_code.display_name if self.error_code is not None else None
365365

366366
@property
367367
def state_name(self) -> str | None:
368-
return self.state.name if self.state is not None else None
368+
return self.state.display_name if self.state is not None else None
369369

370370
@property
371371
def current_map(self) -> int | None:

tests/data/test_code_mappings.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
from roborock import HomeDataProduct, RoborockCategory
1010
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP, YXCleanType
1111
from roborock.data.code_mappings import completed_warnings
12+
from roborock.data.dyad.dyad_code_mappings import DyadError
13+
from roborock.data.v1.v1_code_mappings import (
14+
RoborockErrorCode,
15+
RoborockFinishReason,
16+
RoborockStateCode,
17+
)
1218

1319

1420
def test_from_code() -> None:
@@ -135,3 +141,22 @@ def test_yx_clean_type_from_value_readable_values(readable_value: str, expected_
135141
def test_yx_clean_type_from_code_customized() -> None:
136142
"""Test YXCleanType accepts custom mode code used by Q10 status updates."""
137143
assert YXCleanType.from_code(4) is YXCleanType.CUSTOMIZED
144+
145+
146+
def test_roborock_enum_display_names_group_duplicate_protocol_codes() -> None:
147+
"""Duplicate protocol meanings keep raw codes but expose one user-facing name."""
148+
assert RoborockStateCode(25) is RoborockStateCode.washing_the_mop_2
149+
assert RoborockStateCode(25).value == 25
150+
assert RoborockStateCode(25).display_name == "washing_the_mop"
151+
assert RoborockStateCode.keys().count("washing_the_mop") == 1
152+
assert "washing_the_mop_2" not in RoborockStateCode.keys()
153+
154+
assert RoborockErrorCode(45).display_name == "mopping_roller_1"
155+
assert RoborockErrorCode.keys().count("mopping_roller_1") == 1
156+
assert "mopping_roller_2" not in RoborockErrorCode.keys()
157+
158+
assert RoborockFinishReason(56).display_name == "finished_cleaning"
159+
assert "finished_cleaning_4" not in RoborockFinishReason.keys()
160+
161+
assert DyadError(20008).display_name == "battery_temperature_protection"
162+
assert "battery_temperature_protection_2" not in DyadError.keys()

tests/data/v1/test_v1_containers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,20 @@ def test_status_v2() -> None:
199199
assert s.current_map == 0
200200

201201

202+
def test_status_v2_uses_canonical_state_and_error_names() -> None:
203+
"""Status names should expose canonical user-facing strings."""
204+
modified_status = STATUS.copy()
205+
modified_status["state"] = RoborockStateCode.washing_the_mop_2.value
206+
modified_status["error_code"] = RoborockErrorCode.mopping_roller_2.value
207+
208+
s = StatusV2.from_dict(modified_status)
209+
210+
assert s.state == RoborockStateCode.washing_the_mop_2
211+
assert s.state_name == "washing_the_mop"
212+
assert s.error_code == RoborockErrorCode.mopping_roller_2
213+
assert s.error_code_name == "mopping_roller_1"
214+
215+
202216
def test_status_v2_current_map() -> None:
203217
"""Test the current map logic based on map status for StatusV2."""
204218
s = StatusV2.from_dict(STATUS)

0 commit comments

Comments
 (0)