@@ -108,8 +108,32 @@ def _decamelize(s: str):
108108 return re .sub ("([A-Z]+)" , "_\\ 1" , s ).lower ()
109109
110110
111- @dataclass
111+ def _attr_repr (obj : Any ) -> str :
112+ """Return a string representation of the object including specified attributes.
113+
114+ This reproduces the default repr behavior of dataclasses, but also includes
115+ properties. This must be called by the child class's __repr__ method since
116+ the parent RoborockBase class does not know about the child class's attributes.
117+ """
118+ # Reproduce default repr behavior
119+ parts = []
120+ for k in dir (obj ):
121+ if k .startswith ("_" ):
122+ continue
123+ try :
124+ v = getattr (obj , k )
125+ except (RuntimeError , Exception ):
126+ continue
127+ if callable (v ):
128+ continue
129+ parts .append (f"{ k } ={ v !r} " )
130+ return f"{ type (obj ).__name__ } ({ ', ' .join (parts )} )"
131+
132+
133+ @dataclass (repr = False )
112134class RoborockBase :
135+ """Base class for all Roborock data classes."""
136+
113137 @staticmethod
114138 def _convert_to_class_obj (class_type : type , value ):
115139 if get_origin (class_type ) is list :
@@ -194,6 +218,9 @@ def end_time(self) -> datetime.time | None:
194218 else None
195219 )
196220
221+ def __repr__ (self ) -> str :
222+ return _attr_repr (self )
223+
197224
198225@dataclass
199226class Reference (RoborockBase ):
@@ -346,7 +373,6 @@ class Status(RoborockBase):
346373 battery : int | None = None
347374 clean_time : int | None = None
348375 clean_area : int | None = None
349- square_meter_clean_area : float | None = None
350376 error_code : RoborockErrorCode | None = None
351377 map_present : int | None = None
352378 in_cleaning : RoborockInCleaning | None = None
@@ -393,26 +419,36 @@ class Status(RoborockBase):
393419 dss : int | None = None
394420 common_status : int | None = None
395421 corner_clean_mode : int | None = None
396- error_code_name : str | None = None
397- state_name : str | None = None
398- water_box_mode_name : str | None = None
399- fan_power_options : list [str ] = field (default_factory = list )
400- fan_power_name : str | None = None
401- mop_mode_name : str | None = None
402-
403- def __post_init__ (self ) -> None :
404- self .square_meter_clean_area = round (self .clean_area / 1000000 , 1 ) if self .clean_area is not None else None
405- if self .error_code is not None :
406- self .error_code_name = self .error_code .name
407- if self .state is not None :
408- self .state_name = self .state .name
409- if self .water_box_mode is not None :
410- self .water_box_mode_name = self .water_box_mode .name
411- if self .fan_power is not None :
412- self .fan_power_options = self .fan_power .keys ()
413- self .fan_power_name = self .fan_power .name
414- if self .mop_mode is not None :
415- self .mop_mode_name = self .mop_mode .name
422+
423+ @property
424+ def square_meter_clean_area (self ) -> float | None :
425+ return round (self .clean_area / 1000000 , 1 ) if self .clean_area is not None else None
426+
427+ @property
428+ def error_code_name (self ) -> str | None :
429+ return self .error_code .name if self .error_code else None
430+
431+ @property
432+ def state_name (self ) -> str | None :
433+ return self .state .name if self .state else None
434+
435+ @property
436+ def water_box_mode_name (self ) -> str | None :
437+ return self .water_box_mode .name if self .water_box_mode else None
438+
439+ @property
440+ def fan_power_options (self ) -> list [str ]:
441+ if self .fan_power is None :
442+ return []
443+ return list (self .fan_power .keys ())
444+
445+ @property
446+ def fan_power_name (self ) -> str | None :
447+ return self .fan_power .name if self .fan_power else None
448+
449+ @property
450+ def mop_mode_name (self ) -> str | None :
451+ return self .mop_mode .name if self .mop_mode else None
416452
417453 def get_fan_speed_code (self , fan_speed : str ) -> int :
418454 if self .fan_power is None :
@@ -438,6 +474,9 @@ def current_map(self) -> int | None:
438474 return map_flag
439475 return None
440476
477+ def __repr__ (self ) -> str :
478+ return _attr_repr (self )
479+
441480
442481@dataclass
443482class S4MaxStatus (Status ):
@@ -588,28 +627,30 @@ class ValleyElectricityTimer(RoborockBaseTimer):
588627class CleanSummary (RoborockBase ):
589628 clean_time : int | None = None
590629 clean_area : int | None = None
591- square_meter_clean_area : float | None = None
592630 clean_count : int | None = None
593631 dust_collection_count : int | None = None
594632 records : list [int ] | None = None
595633 last_clean_t : int | None = None
596634
597- def __post_init__ (self ) -> None :
635+ @property
636+ def square_meter_clean_area (self ) -> float | None :
637+ """Returns the clean area in square meters."""
598638 if isinstance (self .clean_area , list | str ):
599639 _LOGGER .warning (f"Clean area is a unexpected type! Please give the following in a issue: { self .clean_area } " )
600- else :
601- self .square_meter_clean_area = round (self .clean_area / 1000000 , 1 ) if self .clean_area is not None else None
640+ return None
641+ return round (self .clean_area / 1000000 , 1 ) if self .clean_area is not None else None
642+
643+ def __repr__ (self ) -> str :
644+ """Return a string representation of the object including all attributes."""
645+ return _attr_repr (self )
602646
603647
604648@dataclass
605649class CleanRecord (RoborockBase ):
606650 begin : int | None = None
607- begin_datetime : datetime .datetime | None = None
608651 end : int | None = None
609- end_datetime : datetime .datetime | None = None
610652 duration : int | None = None
611653 area : int | None = None
612- square_meter_area : float | None = None
613654 error : int | None = None
614655 complete : int | None = None
615656 start_type : RoborockStartType | None = None
@@ -620,12 +661,20 @@ class CleanRecord(RoborockBase):
620661 wash_count : int | None = None
621662 map_flag : int | None = None
622663
623- def __post_init__ (self ) -> None :
624- self .square_meter_area = round (self .area / 1000000 , 1 ) if self .area is not None else None
625- self .begin_datetime = (
626- datetime .datetime .fromtimestamp (self .begin ).astimezone (timezone .utc ) if self .begin else None
627- )
628- self .end_datetime = datetime .datetime .fromtimestamp (self .end ).astimezone (timezone .utc ) if self .end else None
664+ @property
665+ def square_meter_area (self ) -> float | None :
666+ return round (self .area / 1000000 , 1 ) if self .area is not None else None
667+
668+ @property
669+ def begin_datetime (self ) -> datetime .datetime | None :
670+ return datetime .datetime .fromtimestamp (self .begin ).astimezone (timezone .utc ) if self .begin else None
671+
672+ @property
673+ def end_datetime (self ) -> datetime .datetime | None :
674+ return datetime .datetime .fromtimestamp (self .end ).astimezone (timezone .utc ) if self .end else None
675+
676+ def __repr__ (self ) -> str :
677+ return _attr_repr (self )
629678
630679
631680@dataclass
@@ -639,44 +688,49 @@ class Consumable(RoborockBase):
639688 dust_collection_work_times : int | None = None
640689 cleaning_brush_work_times : int | None = None
641690 moproller_work_time : int | None = None
642- main_brush_time_left : int | None = None
643- side_brush_time_left : int | None = None
644- filter_time_left : int | None = None
645- sensor_time_left : int | None = None
646- strainer_time_left : int | None = None
647- dust_collection_time_left : int | None = None
648- cleaning_brush_time_left : int | None = None
649- mop_roller_time_left : int | None = None
650-
651- def __post_init__ (self ) -> None :
652- self .main_brush_time_left = (
653- MAIN_BRUSH_REPLACE_TIME - self .main_brush_work_time if self .main_brush_work_time is not None else None
654- )
655- self .side_brush_time_left = (
656- SIDE_BRUSH_REPLACE_TIME - self .side_brush_work_time if self .side_brush_work_time is not None else None
657- )
658- self .filter_time_left = (
659- FILTER_REPLACE_TIME - self .filter_work_time if self .filter_work_time is not None else None
660- )
661- self .sensor_time_left = (
662- SENSOR_DIRTY_REPLACE_TIME - self .sensor_dirty_time if self .sensor_dirty_time is not None else None
663- )
664- self .strainer_time_left = (
665- STRAINER_REPLACE_TIME - self .strainer_work_times if self .strainer_work_times is not None else None
666- )
667- self .dust_collection_time_left = (
691+
692+ @property
693+ def main_brush_time_left (self ) -> int | None :
694+ return MAIN_BRUSH_REPLACE_TIME - self .main_brush_work_time if self .main_brush_work_time is not None else None
695+
696+ @property
697+ def side_brush_time_left (self ) -> int | None :
698+ return SIDE_BRUSH_REPLACE_TIME - self .side_brush_work_time if self .side_brush_work_time is not None else None
699+
700+ @property
701+ def filter_time_left (self ) -> int | None :
702+ return FILTER_REPLACE_TIME - self .filter_work_time if self .filter_work_time is not None else None
703+
704+ @property
705+ def sensor_time_left (self ) -> int | None :
706+ return SENSOR_DIRTY_REPLACE_TIME - self .sensor_dirty_time if self .sensor_dirty_time is not None else None
707+
708+ @property
709+ def strainer_time_left (self ) -> int | None :
710+ return STRAINER_REPLACE_TIME - self .strainer_work_times if self .strainer_work_times is not None else None
711+
712+ @property
713+ def dust_collection_time_left (self ) -> int | None :
714+ return (
668715 DUST_COLLECTION_REPLACE_TIME - self .dust_collection_work_times
669716 if self .dust_collection_work_times is not None
670717 else None
671718 )
672- self .cleaning_brush_time_left = (
719+
720+ @property
721+ def cleaning_brush_time_left (self ) -> int | None :
722+ return (
673723 CLEANING_BRUSH_REPLACE_TIME - self .cleaning_brush_work_times
674724 if self .cleaning_brush_work_times is not None
675725 else None
676726 )
677- self .mop_roller_time_left = (
678- MOP_ROLLER_REPLACE_TIME - self .moproller_work_time if self .moproller_work_time is not None else None
679- )
727+
728+ @property
729+ def mop_roller_time_left (self ) -> int | None :
730+ return MOP_ROLLER_REPLACE_TIME - self .moproller_work_time if self .moproller_work_time is not None else None
731+
732+ def __repr__ (self ) -> str :
733+ return _attr_repr (self )
680734
681735
682736@dataclass
@@ -760,11 +814,14 @@ class DeviceData(RoborockBase):
760814 device : HomeDataDevice
761815 model : str
762816 host : str | None = None
763- product_nickname : RoborockProductNickname | None = None
764817 device_features : DeviceFeatures | None = None
765818
766- def __post_init__ (self ):
767- self .product_nickname = SHORT_MODEL_TO_ENUM .get (self .model .split ("." )[- 1 ], RoborockProductNickname .PEARLPLUS )
819+ @property
820+ def product_nickname (self ) -> RoborockProductNickname :
821+ return SHORT_MODEL_TO_ENUM .get (self .model .split ("." )[- 1 ], RoborockProductNickname .PEARLPLUS )
822+
823+ def __repr__ (self ) -> str :
824+ return _attr_repr (self )
768825
769826
770827@dataclass
@@ -849,11 +906,15 @@ class RoborockProduct(RoborockBase):
849906 agreements : list | None = None
850907 cardspec : str | None = None
851908 plugin_pic_url : str | None = None
852- products_specification : RoborockProductSpec | None = None
853909
854- def __post_init__ (self ):
910+ @property
911+ def product_nickname (self ) -> RoborockProductNickname | None :
855912 if self .cardspec :
856- self .products_specification = RoborockProductSpec .from_dict (json .loads (self .cardspec ).get ("data" ))
913+ return RoborockProductSpec .from_dict (json .loads (self .cardspec ).get ("data" ))
914+ return None
915+
916+ def __repr__ (self ) -> str :
917+ return _attr_repr (self )
857918
858919
859920@dataclass
0 commit comments