From cc36704738cf9ca0cabc56e96d68ea9961cccd17 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 27 Sep 2025 23:16:42 -0700 Subject: [PATCH 1/5] fix: Update containers that __post_init__ to use properties This is done since with the new v1 api we reuse attributes and mutate them on update, so tis allows the properties to reamin fresh. --- roborock/containers.py | 155 ++++++++++-------- .../devices/__snapshots__/test_v1_device.ambr | 4 +- 2 files changed, 87 insertions(+), 72 deletions(-) diff --git a/roborock/containers.py b/roborock/containers.py index 1cb10034..0373adf6 100644 --- a/roborock/containers.py +++ b/roborock/containers.py @@ -345,7 +345,6 @@ class Status(RoborockBase): battery: int | None = None clean_time: int | None = None clean_area: int | None = None - square_meter_clean_area: float | None = None error_code: RoborockErrorCode | None = None map_present: int | None = None in_cleaning: RoborockInCleaning | None = None @@ -392,26 +391,36 @@ class Status(RoborockBase): dss: int | None = None common_status: int | None = None corner_clean_mode: int | None = None - error_code_name: str | None = None - state_name: str | None = None - water_box_mode_name: str | None = None - fan_power_options: list[str] = field(default_factory=list) - fan_power_name: str | None = None - mop_mode_name: str | None = None - - def __post_init__(self) -> None: - self.square_meter_clean_area = round(self.clean_area / 1000000, 1) if self.clean_area is not None else None - if self.error_code is not None: - self.error_code_name = self.error_code.name - if self.state is not None: - self.state_name = self.state.name - if self.water_box_mode is not None: - self.water_box_mode_name = self.water_box_mode.name - if self.fan_power is not None: - self.fan_power_options = self.fan_power.keys() - self.fan_power_name = self.fan_power.name - if self.mop_mode is not None: - self.mop_mode_name = self.mop_mode.name + + @property + def square_meter_clean_area(self) -> float | None: + return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None + + @property + def error_code_name(self) -> str | None: + return self.error_code.name if self.error_code else None + + @property + def state_name(self) -> str | None: + return self.state.name if self.state else None + + @property + def water_box_mode_name(self) -> str | None: + return self.water_box_mode.name if self.water_box_mode else None + + @property + def fan_power_options(self) -> list[str]: + if self.fan_power is None: + return [] + return list(self.fan_power.keys()) + + @property + def fan_power_name(self) -> str | None: + return self.fan_power.name if self.fan_power else None + + @property + def mop_mode_name(self) -> str | None: + return self.mop_mode.name if self.mop_mode else None def get_fan_speed_code(self, fan_speed: str) -> int: if self.fan_power is None: @@ -585,28 +594,26 @@ class ValleyElectricityTimer(RoborockBaseTimer): class CleanSummary(RoborockBase): clean_time: int | None = None clean_area: int | None = None - square_meter_clean_area: float | None = None clean_count: int | None = None dust_collection_count: int | None = None records: list[int] | None = None last_clean_t: int | None = None - def __post_init__(self) -> None: + @property + def square_meter_clean_area(self) -> float | None: + """Returns the clean area in square meters.""" if isinstance(self.clean_area, list | str): _LOGGER.warning(f"Clean area is a unexpected type! Please give the following in a issue: {self.clean_area}") - else: - self.square_meter_clean_area = round(self.clean_area / 1000000, 1) if self.clean_area is not None else None + return None + return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None @dataclass class CleanRecord(RoborockBase): begin: int | None = None - begin_datetime: datetime.datetime | None = None end: int | None = None - end_datetime: datetime.datetime | None = None duration: int | None = None area: int | None = None - square_meter_area: float | None = None error: int | None = None complete: int | None = None start_type: RoborockStartType | None = None @@ -617,12 +624,17 @@ class CleanRecord(RoborockBase): wash_count: int | None = None map_flag: int | None = None - def __post_init__(self) -> None: - self.square_meter_area = round(self.area / 1000000, 1) if self.area is not None else None - self.begin_datetime = ( - datetime.datetime.fromtimestamp(self.begin).astimezone(timezone.utc) if self.begin else None - ) - self.end_datetime = datetime.datetime.fromtimestamp(self.end).astimezone(timezone.utc) if self.end else None + @property + def sqaure_meter_area(self) -> float | None: + return round(self.area / 1000000, 1) if self.area is not None else None + + @property + def begin_datetime(self) -> datetime.datetime | None: + return datetime.datetime.fromtimestamp(self.begin).astimezone(timezone.utc) if self.begin else None + + @property + def end_datetime(self) -> datetime.datetime | None: + return datetime.datetime.fromtimestamp(self.end).astimezone(timezone.utc) if self.end else None @dataclass @@ -636,44 +648,46 @@ class Consumable(RoborockBase): dust_collection_work_times: int | None = None cleaning_brush_work_times: int | None = None moproller_work_time: int | None = None - main_brush_time_left: int | None = None - side_brush_time_left: int | None = None - filter_time_left: int | None = None - sensor_time_left: int | None = None - strainer_time_left: int | None = None - dust_collection_time_left: int | None = None - cleaning_brush_time_left: int | None = None - mop_roller_time_left: int | None = None - - def __post_init__(self) -> None: - self.main_brush_time_left = ( - MAIN_BRUSH_REPLACE_TIME - self.main_brush_work_time if self.main_brush_work_time is not None else None - ) - self.side_brush_time_left = ( - SIDE_BRUSH_REPLACE_TIME - self.side_brush_work_time if self.side_brush_work_time is not None else None - ) - self.filter_time_left = ( - FILTER_REPLACE_TIME - self.filter_work_time if self.filter_work_time is not None else None - ) - self.sensor_time_left = ( - SENSOR_DIRTY_REPLACE_TIME - self.sensor_dirty_time if self.sensor_dirty_time is not None else None - ) - self.strainer_time_left = ( - STRAINER_REPLACE_TIME - self.strainer_work_times if self.strainer_work_times is not None else None - ) - self.dust_collection_time_left = ( + + @property + def main_brush_time_left(self) -> int | None: + return MAIN_BRUSH_REPLACE_TIME - self.main_brush_work_time if self.main_brush_work_time is not None else None + + @property + def side_brush_time_left(self) -> int | None: + return SIDE_BRUSH_REPLACE_TIME - self.side_brush_work_time if self.side_brush_work_time is not None else None + + @property + def filter_time_left(self) -> int | None: + return FILTER_REPLACE_TIME - self.filter_work_time if self.filter_work_time is not None else None + + @property + def sensor_time_left(self) -> int | None: + return SENSOR_DIRTY_REPLACE_TIME - self.sensor_dirty_time if self.sensor_dirty_time is not None else None + + @property + def strainer_time_left(self) -> int | None: + return STRAINER_REPLACE_TIME - self.strainer_work_times if self.strainer_work_times is not None else None + + @property + def dust_collection_time_left(self) -> int | None: + return ( DUST_COLLECTION_REPLACE_TIME - self.dust_collection_work_times if self.dust_collection_work_times is not None else None ) - self.cleaning_brush_time_left = ( + + @property + def cleaning_brush_time_left(self) -> int | None: + return ( CLEANING_BRUSH_REPLACE_TIME - self.cleaning_brush_work_times if self.cleaning_brush_work_times is not None else None ) - self.mop_roller_time_left = ( - MOP_ROLLER_REPLACE_TIME - self.moproller_work_time if self.moproller_work_time is not None else None - ) + + @property + def mop_roller_time_left(self) -> int | None: + return MOP_ROLLER_REPLACE_TIME - self.moproller_work_time if self.moproller_work_time is not None else None @dataclass @@ -757,11 +771,11 @@ class DeviceData(RoborockBase): device: HomeDataDevice model: str host: str | None = None - product_nickname: RoborockProductNickname | None = None device_features: DeviceFeatures | None = None - def __post_init__(self): - self.product_nickname = SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS) + @property + def product_nickname(self) -> RoborockProductNickname: + return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS) @dataclass @@ -846,11 +860,12 @@ class RoborockProduct(RoborockBase): agreements: list | None = None cardspec: str | None = None plugin_pic_url: str | None = None - products_specification: RoborockProductSpec | None = None - def __post_init__(self): + @property + def product_nickname(self) -> RoborockProductNickname | None: if self.cardspec: - self.products_specification = RoborockProductSpec.from_dict(json.loads(self.cardspec).get("data")) + return RoborockProductSpec.from_dict(json.loads(self.cardspec).get("data")) + return None @dataclass diff --git a/tests/devices/__snapshots__/test_v1_device.ambr b/tests/devices/__snapshots__/test_v1_device.ambr index 2987718f..2adcd547 100644 --- a/tests/devices/__snapshots__/test_v1_device.ambr +++ b/tests/devices/__snapshots__/test_v1_device.ambr @@ -1,12 +1,12 @@ # serializer version: 1 # name: test_device_trait_command_parsing[payload0-] - StatusTrait(msg_ver=2, msg_seq=515, state=, battery=100, clean_time=5405, clean_area=91287500, square_meter_clean_area=91.3, error_code=, map_present=1, in_cleaning=, in_returning=0, in_fresh_state=1, lab_status=1, water_box_status=0, back_type=None, wash_phase=None, wash_ready=None, fan_power=, dnd_enabled=1, map_status=3, is_locating=0, lock_status=0, water_box_mode=, water_box_carriage_status=0, mop_forbidden_enable=0, camera_status=None, is_exploring=None, home_sec_status=None, home_sec_enable_password=None, adbumper_status=None, water_shortage_status=None, dock_type=None, dust_collection_status=None, auto_dust_collection=None, avoid_count=None, mop_mode=None, debug_mode=None, collision_avoid_status=None, switch_map_mode=None, dock_error_status=None, charge_status=None, unsave_map_reason=4, unsave_map_flag=0, wash_status=None, distance_off=0, in_warmup=None, dry_status=None, rdt=None, clean_percent=None, rss=None, dss=None, common_status=None, corner_clean_mode=None, error_code_name='none', state_name='charging', water_box_mode_name='custom', fan_power_options=['off', 'quiet', 'balanced', 'turbo', 'max', 'custom', 'max_plus'], fan_power_name='custom', mop_mode_name=None) + StatusTrait(msg_ver=2, msg_seq=515, state=, battery=100, clean_time=5405, clean_area=91287500, error_code=, map_present=1, in_cleaning=, in_returning=0, in_fresh_state=1, lab_status=1, water_box_status=0, back_type=None, wash_phase=None, wash_ready=None, fan_power=, dnd_enabled=1, map_status=3, is_locating=0, lock_status=0, water_box_mode=, water_box_carriage_status=0, mop_forbidden_enable=0, camera_status=None, is_exploring=None, home_sec_status=None, home_sec_enable_password=None, adbumper_status=None, water_shortage_status=None, dock_type=None, dust_collection_status=None, auto_dust_collection=None, avoid_count=None, mop_mode=None, debug_mode=None, collision_avoid_status=None, switch_map_mode=None, dock_error_status=None, charge_status=None, unsave_map_reason=4, unsave_map_flag=0, wash_status=None, distance_off=0, in_warmup=None, dry_status=None, rdt=None, clean_percent=None, rss=None, dss=None, common_status=None, corner_clean_mode=None) # --- # name: test_device_trait_command_parsing[payload1-] DoNotDisturbTrait(start_hour=22, start_minute=0, end_hour=8, end_minute=0, enabled=1) # --- # name: test_device_trait_command_parsing[payload2-] - CleanSummaryTrait(clean_time=1442559, clean_area=24258125000, square_meter_clean_area=24258.1, clean_count=296, dust_collection_count=None, records=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], last_clean_t=None) + CleanSummaryTrait(clean_time=1442559, clean_area=24258125000, clean_count=296, dust_collection_count=None, records=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], last_clean_t=None) # --- # name: test_device_trait_command_parsing[payload3-] SoundVolumeTrait(volume=90) From 7944ef83d03906e16b2c146de9f4158a5f1f5738 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 28 Sep 2025 06:19:45 -0700 Subject: [PATCH 2/5] chore: fix typo --- roborock/containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roborock/containers.py b/roborock/containers.py index 0373adf6..c66b8ffc 100644 --- a/roborock/containers.py +++ b/roborock/containers.py @@ -625,7 +625,7 @@ class CleanRecord(RoborockBase): map_flag: int | None = None @property - def sqaure_meter_area(self) -> float | None: + def square_meter_area(self) -> float | None: return round(self.area / 1000000, 1) if self.area is not None else None @property From 1bc58241c1a9c81e82d6372bafb45765a64f3f3b Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 28 Sep 2025 18:21:36 -0700 Subject: [PATCH 3/5] chore: Include atributes in repr computation --- roborock/containers.py | 58 +++++++++++++++++++ .../devices/__snapshots__/test_v1_device.ambr | 4 +- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/roborock/containers.py b/roborock/containers.py index c66b8ffc..3de6bb29 100644 --- a/roborock/containers.py +++ b/roborock/containers.py @@ -107,6 +107,20 @@ def _decamelize(s: str): return re.sub("([A-Z]+)", "_\\1", s).lower() +def _attr_repr(obj: Any, attrs: list[str]) -> str: + """Return a string representation of the object including specified attributes.""" + # Reproduce default repr behavior + items = (f"{k}={v!r}" for k, v in obj.__dict__.items() if not k.startswith("_")) + default_repr = "{}({})".format(type(obj).__name__, ", ".join(items)) + # Append additional attributes + parts = [default_repr[:-1]] + for attr in attrs: + value = getattr(obj, attr, None) + parts.append(f", {attr}={repr(value)}") + parts.append(")") + return "".join(parts) + + @dataclass class RoborockBase: @staticmethod @@ -193,6 +207,9 @@ def end_time(self) -> datetime.time | None: else None ) + def __repr__(self) -> str: + return _attr_repr(self, ["start_time", "end_time"]) + @dataclass class Reference(RoborockBase): @@ -444,6 +461,20 @@ def current_map(self) -> int | None: return (self.map_status - 3) // 4 return None + def __repr__(self) -> str: + return _attr_repr( + self, + [ + "square_meter_clean_area", + "error_code_name", + "state_name", + "water_box_mode_name", + "fan_power_name", + "mop_mode_name", + "current_map", + ], + ) + @dataclass class S4MaxStatus(Status): @@ -607,6 +638,9 @@ def square_meter_clean_area(self) -> float | None: return None return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None + def __repr__(self): + return _attr_repr(self, ["square_meter_clean_area"]) + @dataclass class CleanRecord(RoborockBase): @@ -636,6 +670,9 @@ def begin_datetime(self) -> datetime.datetime | None: def end_datetime(self) -> datetime.datetime | None: return datetime.datetime.fromtimestamp(self.end).astimezone(timezone.utc) if self.end else None + def __repr__(self) -> str: + return _attr_repr(self, ["square_meter_area", "begin_datetime", "end_datetime"]) + @dataclass class Consumable(RoborockBase): @@ -689,6 +726,21 @@ def cleaning_brush_time_left(self) -> int | None: def mop_roller_time_left(self) -> int | None: return MOP_ROLLER_REPLACE_TIME - self.moproller_work_time if self.moproller_work_time is not None else None + def __repr__(self) -> str: + return _attr_repr( + self, + [ + "main_brush_time_left", + "side_brush_time_left", + "filter_time_left", + "sensor_time_left", + "strainer_time_left", + "dust_collection_time_left", + "cleaning_brush_time_left", + "mop_roller_time_left", + ], + ) + @dataclass class MultiMapsListMapInfoBakMaps(RoborockBase): @@ -777,6 +829,9 @@ class DeviceData(RoborockBase): def product_nickname(self) -> RoborockProductNickname: return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS) + def __repr__(self) -> str: + return _attr_repr(self, ["product_nickname"]) + @dataclass class RoomMapping(RoborockBase): @@ -867,6 +922,9 @@ def product_nickname(self) -> RoborockProductNickname | None: return RoborockProductSpec.from_dict(json.loads(self.cardspec).get("data")) return None + def __repr__(self) -> str: + return _attr_repr(self, ["product_nickname"]) + @dataclass class RoborockProductCategory(RoborockBase): diff --git a/tests/devices/__snapshots__/test_v1_device.ambr b/tests/devices/__snapshots__/test_v1_device.ambr index 2adcd547..ec9badda 100644 --- a/tests/devices/__snapshots__/test_v1_device.ambr +++ b/tests/devices/__snapshots__/test_v1_device.ambr @@ -1,12 +1,12 @@ # serializer version: 1 # name: test_device_trait_command_parsing[payload0-] - StatusTrait(msg_ver=2, msg_seq=515, state=, battery=100, clean_time=5405, clean_area=91287500, error_code=, map_present=1, in_cleaning=, in_returning=0, in_fresh_state=1, lab_status=1, water_box_status=0, back_type=None, wash_phase=None, wash_ready=None, fan_power=, dnd_enabled=1, map_status=3, is_locating=0, lock_status=0, water_box_mode=, water_box_carriage_status=0, mop_forbidden_enable=0, camera_status=None, is_exploring=None, home_sec_status=None, home_sec_enable_password=None, adbumper_status=None, water_shortage_status=None, dock_type=None, dust_collection_status=None, auto_dust_collection=None, avoid_count=None, mop_mode=None, debug_mode=None, collision_avoid_status=None, switch_map_mode=None, dock_error_status=None, charge_status=None, unsave_map_reason=4, unsave_map_flag=0, wash_status=None, distance_off=0, in_warmup=None, dry_status=None, rdt=None, clean_percent=None, rss=None, dss=None, common_status=None, corner_clean_mode=None) + StatusTrait(msg_ver=2, msg_seq=515, state=, battery=100, clean_time=5405, clean_area=91287500, error_code=, map_present=1, in_cleaning=, in_returning=0, in_fresh_state=1, lab_status=1, water_box_status=0, fan_power=, dnd_enabled=1, map_status=3, is_locating=0, lock_status=0, water_box_mode=, water_box_carriage_status=0, mop_forbidden_enable=0, unsave_map_reason=4, unsave_map_flag=0, distance_off=0, square_meter_clean_area=91.3, error_code_name=None, state_name='charging', water_box_mode_name='custom', fan_power_name='custom', mop_mode_name=None, current_map=0) # --- # name: test_device_trait_command_parsing[payload1-] DoNotDisturbTrait(start_hour=22, start_minute=0, end_hour=8, end_minute=0, enabled=1) # --- # name: test_device_trait_command_parsing[payload2-] - CleanSummaryTrait(clean_time=1442559, clean_area=24258125000, clean_count=296, dust_collection_count=None, records=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], last_clean_t=None) + CleanSummaryTrait(clean_time=1442559, clean_area=24258125000, clean_count=296, dust_collection_count=None, records=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], last_clean_t=None, square_meter_clean_area=24258.1) # --- # name: test_device_trait_command_parsing[payload3-] SoundVolumeTrait(volume=90) From 9a1cda38407bf2ac1260c88112b61d52db360d05 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 3 Oct 2025 07:33:48 -0700 Subject: [PATCH 4/5] chore: update to get all properties at runtime --- roborock/containers.py | 79 ++++++++----------- .../devices/__snapshots__/test_v1_device.ambr | 4 +- 2 files changed, 35 insertions(+), 48 deletions(-) diff --git a/roborock/containers.py b/roborock/containers.py index 3de6bb29..cb47233d 100644 --- a/roborock/containers.py +++ b/roborock/containers.py @@ -107,22 +107,32 @@ def _decamelize(s: str): return re.sub("([A-Z]+)", "_\\1", s).lower() -def _attr_repr(obj: Any, attrs: list[str]) -> str: - """Return a string representation of the object including specified attributes.""" +def _attr_repr(obj: Any) -> str: + """Return a string representation of the object including specified attributes. + + This reproduces the default repr behavior of dataclasses, but also includes + properties. This must be called by the child class's __repr__ method since + the parent RoborockBase class does not know about the child class's attributes. + """ # Reproduce default repr behavior - items = (f"{k}={v!r}" for k, v in obj.__dict__.items() if not k.startswith("_")) - default_repr = "{}({})".format(type(obj).__name__, ", ".join(items)) - # Append additional attributes - parts = [default_repr[:-1]] - for attr in attrs: - value = getattr(obj, attr, None) - parts.append(f", {attr}={repr(value)}") - parts.append(")") - return "".join(parts) - - -@dataclass + parts = [] + for k in dir(obj): + if k.startswith("_"): + continue + try: + v = getattr(obj, k) + except (RuntimeError, Exception): + continue + if callable(v): + continue + parts.append(f"{k}={v!r}") + return f"{type(obj).__name__}({', '.join(parts)})" + + +@dataclass(repr=False) class RoborockBase: + """Base class for all Roborock data classes.""" + @staticmethod def _convert_to_class_obj(class_type: type, value): if get_origin(class_type) is list: @@ -208,7 +218,7 @@ def end_time(self) -> datetime.time | None: ) def __repr__(self) -> str: - return _attr_repr(self, ["start_time", "end_time"]) + return _attr_repr(self) @dataclass @@ -462,18 +472,7 @@ def current_map(self) -> int | None: return None def __repr__(self) -> str: - return _attr_repr( - self, - [ - "square_meter_clean_area", - "error_code_name", - "state_name", - "water_box_mode_name", - "fan_power_name", - "mop_mode_name", - "current_map", - ], - ) + return _attr_repr(self) @dataclass @@ -638,8 +637,9 @@ def square_meter_clean_area(self) -> float | None: return None return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None - def __repr__(self): - return _attr_repr(self, ["square_meter_clean_area"]) + def __repr__(self) -> str: + """Return a string representation of the object including all attributes.""" + return _attr_repr(self) @dataclass @@ -671,7 +671,7 @@ def end_datetime(self) -> datetime.datetime | None: return datetime.datetime.fromtimestamp(self.end).astimezone(timezone.utc) if self.end else None def __repr__(self) -> str: - return _attr_repr(self, ["square_meter_area", "begin_datetime", "end_datetime"]) + return _attr_repr(self) @dataclass @@ -727,20 +727,7 @@ def mop_roller_time_left(self) -> int | None: return MOP_ROLLER_REPLACE_TIME - self.moproller_work_time if self.moproller_work_time is not None else None def __repr__(self) -> str: - return _attr_repr( - self, - [ - "main_brush_time_left", - "side_brush_time_left", - "filter_time_left", - "sensor_time_left", - "strainer_time_left", - "dust_collection_time_left", - "cleaning_brush_time_left", - "mop_roller_time_left", - ], - ) - + return _attr_repr(self) @dataclass class MultiMapsListMapInfoBakMaps(RoborockBase): @@ -830,7 +817,7 @@ def product_nickname(self) -> RoborockProductNickname: return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS) def __repr__(self) -> str: - return _attr_repr(self, ["product_nickname"]) + return _attr_repr(self) @dataclass @@ -923,7 +910,7 @@ def product_nickname(self) -> RoborockProductNickname | None: return None def __repr__(self) -> str: - return _attr_repr(self, ["product_nickname"]) + return _attr_repr(self) @dataclass diff --git a/tests/devices/__snapshots__/test_v1_device.ambr b/tests/devices/__snapshots__/test_v1_device.ambr index ec9badda..3115e692 100644 --- a/tests/devices/__snapshots__/test_v1_device.ambr +++ b/tests/devices/__snapshots__/test_v1_device.ambr @@ -1,12 +1,12 @@ # serializer version: 1 # name: test_device_trait_command_parsing[payload0-] - StatusTrait(msg_ver=2, msg_seq=515, state=, battery=100, clean_time=5405, clean_area=91287500, error_code=, map_present=1, in_cleaning=, in_returning=0, in_fresh_state=1, lab_status=1, water_box_status=0, fan_power=, dnd_enabled=1, map_status=3, is_locating=0, lock_status=0, water_box_mode=, water_box_carriage_status=0, mop_forbidden_enable=0, unsave_map_reason=4, unsave_map_flag=0, distance_off=0, square_meter_clean_area=91.3, error_code_name=None, state_name='charging', water_box_mode_name='custom', fan_power_name='custom', mop_mode_name=None, current_map=0) + StatusTrait(adbumper_status=None, auto_dust_collection=None, avoid_count=None, back_type=None, battery=100, camera_status=None, charge_status=None, clean_area=91287500, clean_percent=None, clean_time=5405, collision_avoid_status=None, command=, common_status=None, corner_clean_mode=None, current_map=0, debug_mode=None, distance_off=0, dnd_enabled=1, dock_error_status=None, dock_type=None, dry_status=None, dss=None, dust_collection_status=None, error_code=, error_code_name=None, fan_power=, fan_power_name='custom', fan_power_options=['off', 'quiet', 'balanced', 'turbo', 'max', 'custom', 'max_plus'], home_sec_enable_password=None, home_sec_status=None, in_cleaning=, in_fresh_state=1, in_returning=0, in_warmup=None, is_exploring=None, is_locating=0, lab_status=1, lock_status=0, map_present=1, map_status=3, mop_forbidden_enable=0, mop_mode=None, mop_mode_name=None, msg_seq=515, msg_ver=2, rdt=None, rss=None, square_meter_clean_area=91.3, state=, state_name='charging', switch_map_mode=None, unsave_map_flag=0, unsave_map_reason=4, wash_phase=None, wash_ready=None, wash_status=None, water_box_carriage_status=0, water_box_mode=, water_box_mode_name='custom', water_box_status=0, water_shortage_status=None) # --- # name: test_device_trait_command_parsing[payload1-] DoNotDisturbTrait(start_hour=22, start_minute=0, end_hour=8, end_minute=0, enabled=1) # --- # name: test_device_trait_command_parsing[payload2-] - CleanSummaryTrait(clean_time=1442559, clean_area=24258125000, clean_count=296, dust_collection_count=None, records=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], last_clean_t=None, square_meter_clean_area=24258.1) + CleanSummaryTrait(clean_area=24258125000, clean_count=296, clean_time=1442559, command=, dust_collection_count=None, last_clean_t=None, records=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], square_meter_clean_area=24258.1) # --- # name: test_device_trait_command_parsing[payload3-] SoundVolumeTrait(volume=90) From 81723262102fb5ed43c073768fb2a7ccc1d05c75 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 3 Oct 2025 07:37:45 -0700 Subject: [PATCH 5/5] chore: fix lint errors --- roborock/containers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/roborock/containers.py b/roborock/containers.py index cb47233d..6eaf0196 100644 --- a/roborock/containers.py +++ b/roborock/containers.py @@ -109,7 +109,7 @@ def _decamelize(s: str): def _attr_repr(obj: Any) -> str: """Return a string representation of the object including specified attributes. - + This reproduces the default repr behavior of dataclasses, but also includes properties. This must be called by the child class's __repr__ method since the parent RoborockBase class does not know about the child class's attributes. @@ -729,6 +729,7 @@ def mop_roller_time_left(self) -> int | None: def __repr__(self) -> str: return _attr_repr(self) + @dataclass class MultiMapsListMapInfoBakMaps(RoborockBase): mapflag: Any | None = None