Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
58 changes: 58 additions & 0 deletions roborock/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be simpler/ avoid maintenance if we just added a iterator in the roborockbase, checking if each element is a property and if so we add it to _attr_repr?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More computational overhead though

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to combine with the existing logic so it won't be more complex overall. The only thing i can't seem to make better is just sticking this in the base class without additional copies in the subclasses. dir in the parent doesn't find the child properties for some reason, but maybe something we can figure out in a future pr



@dataclass
class Reference(RoborockBase):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions tests/devices/__snapshots__/test_v1_device.ambr
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# serializer version: 1
# name: test_device_trait_command_parsing[payload0-<lambda>]
StatusTrait(msg_ver=2, msg_seq=515, state=<RoborockStateCode.charging: 8>, battery=100, clean_time=5405, clean_area=91287500, error_code=<RoborockErrorCode.none: 0>, map_present=1, in_cleaning=<RoborockInCleaning.complete: 0>, 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=<RoborockFanSpeedS7MaxV.custom: 106>, dnd_enabled=1, map_status=3, is_locating=0, lock_status=0, water_box_mode=<RoborockMopIntensityS7.custom: 204>, 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=<RoborockStateCode.charging: 8>, battery=100, clean_time=5405, clean_area=91287500, error_code=<RoborockErrorCode.none: 0>, map_present=1, in_cleaning=<RoborockInCleaning.complete: 0>, in_returning=0, in_fresh_state=1, lab_status=1, water_box_status=0, fan_power=<RoborockFanSpeedS7MaxV.custom: 106>, dnd_enabled=1, map_status=3, is_locating=0, lock_status=0, water_box_mode=<RoborockMopIntensityS7.custom: 204>, 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-<lambda>]
DoNotDisturbTrait(start_hour=22, start_minute=0, end_hour=8, end_minute=0, enabled=1)
# ---
# name: test_device_trait_command_parsing[payload2-<lambda>]
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-<lambda>]
SoundVolumeTrait(volume=90)
Expand Down