Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions roborock/data/code_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@
class RoborockEnum(IntEnum):
"""Roborock Enum for codes with int values"""

_display_name_: str | None

def __new__(cls, value: int, display_name: str | None = None) -> Self:
member = int.__new__(cls, value)
member._value_ = value
member._display_name_ = display_name
return member

@property
def name(self) -> str:
return super().name.lower()

@property
def display_name(self) -> str:
return self._display_name_ or self.name

@classmethod
def _missing_(cls: type[Self], key) -> Self:
if hasattr(cls, "unknown"):
Expand All @@ -30,8 +42,13 @@ def _missing_(cls: type[Self], key) -> Self:
return default_value

@classmethod
def as_dict(cls: type[Self]):
return {i.name: i.value for i in cls if i.name != "missing"}
def as_dict(cls: type[Self]) -> dict[str, int]:
result: dict[str, int] = {}
for item in cls:
if item.name == "missing":
continue
result.setdefault(item.display_name, item.value)
return result

@classmethod
def as_enum_dict(cls: type[Self]):
Expand Down
2 changes: 1 addition & 1 deletion roborock/data/dyad/dyad_code_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class DyadError(RoborockEnum):
battery_temperature_protection = (
20007 # Battery temperature protection. Wait for the temperature to return to a normal range.
)
battery_temperature_protection_2 = 20008
battery_temperature_protection_2 = (20008, "battery_temperature_protection")
power_adapter_error = 20009 # Check if the power adapter is working properly.
dirty_charging_contacts = 10007 # Disconnection between the device and dock. Wipe charging contacts.
low_battery = 20017 # Low battery level. Charge before starting self-cleaning.
Expand Down
64 changes: 32 additions & 32 deletions roborock/data/v1/v1_code_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,40 @@ class RoborockFinishReason(RoborockEnum):
manual_interrupt = 21 # Cleaning interrupted by user
cleanup_interrupted = 24 # Cleanup interrupted
manual_interrupt_2 = 21
manual_interrupt_12 = 29
manual_interrupt_12 = (29, "manual_interrupt")
breakpoint = 32 # Could not continue cleaning
breakpoint_2 = 33
cleanup_interrupted_2 = 34
manual_interrupt_3 = 35
manual_interrupt_4 = 36
manual_interrupt_5 = 37
manual_interrupt_6 = 43
breakpoint_2 = (33, "breakpoint")
cleanup_interrupted_2 = (34, "cleanup_interrupted")
manual_interrupt_3 = (35, "manual_interrupt")
manual_interrupt_4 = (36, "manual_interrupt")
manual_interrupt_5 = (37, "manual_interrupt")
manual_interrupt_6 = (43, "manual_interrupt")
locate_fail = 45 # Positioning Failed
cleanup_interrupted_3 = 64
locate_fail_2 = 65
manual_interrupt_7 = 48
manual_interrupt_8 = 49
manual_interrupt_9 = 50
cleanup_interrupted_4 = 51
cleanup_interrupted_3 = (64, "cleanup_interrupted")
locate_fail_2 = (65, "locate_fail")
manual_interrupt_7 = (48, "manual_interrupt")
manual_interrupt_8 = (49, "manual_interrupt")
manual_interrupt_9 = (50, "manual_interrupt")
cleanup_interrupted_4 = (51, "cleanup_interrupted")
finished_cleaning = 52 # Finished cleaning
finished_cleaning_2 = 54
finished_cleaning_3 = 55
finished_cleaning_4 = 56
finished_clenaing_5 = 57
manual_interrupt_10 = 60
finished_cleaning_2 = (54, "finished_cleaning")
finished_cleaning_3 = (55, "finished_cleaning")
finished_cleaning_4 = (56, "finished_cleaning")
finished_clenaing_5 = (57, "finished_cleaning")
manual_interrupt_10 = (60, "manual_interrupt")
area_unreachable = 61 # Area unreachable
area_unreachable_2 = 62
area_unreachable_2 = (62, "area_unreachable")
washing_error = 67 # Washing error
back_to_wash_failure = 68 # Failed to return to the dock
cleanup_interrupted_5 = 101
breakpoint_4 = 102
manual_interrupt_11 = 103
cleanup_interrupted_6 = 104
cleanup_interrupted_7 = 105
cleanup_interrupted_8 = 106
cleanup_interrupted_9 = 107
cleanup_interrupted_10 = 109
cleanup_interrupted_11 = 110
cleanup_interrupted_5 = (101, "cleanup_interrupted")
breakpoint_4 = (102, "breakpoint")
manual_interrupt_11 = (103, "manual_interrupt")
cleanup_interrupted_6 = (104, "cleanup_interrupted")
cleanup_interrupted_7 = (105, "cleanup_interrupted")
cleanup_interrupted_8 = (106, "cleanup_interrupted")
cleanup_interrupted_9 = (107, "cleanup_interrupted")
cleanup_interrupted_10 = (109, "cleanup_interrupted")
cleanup_interrupted_11 = (110, "cleanup_interrupted")
patrol_success = 114 # Cruise completed
patrol_fail = 115 # Cruise failed
pet_patrol_success = 116 # Pet found
Expand Down Expand Up @@ -159,7 +159,7 @@ class ClearWaterBoxStatus(RoborockDssCodes):

okay = 0
out_of_water = 1
out_of_water_2 = 38
out_of_water_2 = (38, "out_of_water")
refill_error = 48


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

okay = 0
full_not_installed = 1
full_not_installed_2 = 39
full_not_installed_2 = (39, "full_not_installed")
drain_error = 49


Expand Down Expand Up @@ -232,7 +232,7 @@ class RoborockErrorCode(RoborockEnum):
clear_brush_exception = 42 # Check that the water filter has been correctly installed
clear_brush_exception_2 = 43 # Positioning button error
filter_screen_exception = 44 # Clean the dock water filter
mopping_roller_2 = 45 # Wash roller may be jammed
mopping_roller_2 = (45, "mopping_roller_1") # Wash roller may be jammed
up_water_exception = 48
drain_water_exception = 49
temperature_protection = 51 # Unit temperature protection
Expand Down Expand Up @@ -701,7 +701,7 @@ class RoborockStateCode(RoborockEnum):
segment_cleaning = 18
emptying_the_bin = 22 # on s7+
washing_the_mop = 23 # on a46
washing_the_mop_2 = 25
washing_the_mop_2 = (25, "washing_the_mop")
going_to_wash_the_mop = 26 # on a46
in_call = 28
mapping = 29
Expand Down
20 changes: 10 additions & 10 deletions roborock/data/v1/v1_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,15 @@ def square_meter_clean_area(self) -> float | None:

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

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

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

@property
def fan_power_options(self) -> list[str]:
Expand All @@ -209,23 +209,23 @@ def fan_power_options(self) -> list[str]:

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

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

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

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

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

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

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

@property
def current_map(self) -> int | None:
Expand Down
25 changes: 25 additions & 0 deletions tests/data/test_code_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
from roborock import HomeDataProduct, RoborockCategory
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP, YXCleanType
from roborock.data.code_mappings import completed_warnings
from roborock.data.dyad.dyad_code_mappings import DyadError
from roborock.data.v1.v1_code_mappings import (
RoborockErrorCode,
RoborockFinishReason,
RoborockStateCode,
)


def test_from_code() -> None:
Expand Down Expand Up @@ -135,3 +141,22 @@ def test_yx_clean_type_from_value_readable_values(readable_value: str, expected_
def test_yx_clean_type_from_code_customized() -> None:
"""Test YXCleanType accepts custom mode code used by Q10 status updates."""
assert YXCleanType.from_code(4) is YXCleanType.CUSTOMIZED


def test_roborock_enum_display_names_group_duplicate_protocol_codes() -> None:
"""Duplicate protocol meanings keep raw codes but expose one user-facing name."""
assert RoborockStateCode(25) is RoborockStateCode.washing_the_mop_2
assert RoborockStateCode(25).value == 25
assert RoborockStateCode(25).display_name == "washing_the_mop"
assert RoborockStateCode.keys().count("washing_the_mop") == 1
assert "washing_the_mop_2" not in RoborockStateCode.keys()

assert RoborockErrorCode(45).display_name == "mopping_roller_1"
assert RoborockErrorCode.keys().count("mopping_roller_1") == 1
assert "mopping_roller_2" not in RoborockErrorCode.keys()

assert RoborockFinishReason(56).display_name == "finished_cleaning"
assert "finished_cleaning_4" not in RoborockFinishReason.keys()

assert DyadError(20008).display_name == "battery_temperature_protection"
assert "battery_temperature_protection_2" not in DyadError.keys()
14 changes: 14 additions & 0 deletions tests/data/v1/test_v1_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,20 @@ def test_status_v2() -> None:
assert s.current_map == 0


def test_status_v2_uses_canonical_state_and_error_names() -> None:
"""Status names should expose canonical user-facing strings."""
modified_status = STATUS.copy()
modified_status["state"] = RoborockStateCode.washing_the_mop_2.value
modified_status["error_code"] = RoborockErrorCode.mopping_roller_2.value

s = StatusV2.from_dict(modified_status)

assert s.state == RoborockStateCode.washing_the_mop_2
assert s.state_name == "washing_the_mop"
assert s.error_code == RoborockErrorCode.mopping_roller_2
assert s.error_code_name == "mopping_roller_1"


def test_status_v2_current_map() -> None:
"""Test the current map logic based on map status for StatusV2."""
s = StatusV2.from_dict(STATUS)
Expand Down
Loading