|
| 1 | +import datetime |
| 2 | +import json |
1 | 3 | from dataclasses import dataclass, field |
| 4 | +from functools import cached_property |
2 | 5 |
|
| 6 | +from ...exceptions import RoborockException |
3 | 7 | from ..containers import RoborockBase |
4 | 8 | from .b01_q7_code_mappings import ( |
5 | 9 | B01Fault, |
| 10 | + CleanPathPreferenceMapping, |
| 11 | + CleanRepeatMapping, |
6 | 12 | CleanTypeMapping, |
7 | 13 | SCWindMapping, |
8 | 14 | WaterLevelMapping, |
@@ -90,10 +96,10 @@ class B01Props(RoborockBase): |
90 | 96 | mop_life: int | None = None |
91 | 97 | main_sensor: int | None = None |
92 | 98 | net_status: NetStatus | None = None |
93 | | - repeat_state: int | None = None |
| 99 | + repeat_state: CleanRepeatMapping | None = None |
94 | 100 | tank_state: int | None = None |
95 | 101 | sweep_type: int | None = None |
96 | | - clean_path_preference: int | None = None |
| 102 | + clean_path_preference: CleanPathPreferenceMapping | None = None |
97 | 103 | cloth_state: int | None = None |
98 | 104 | time_zone: int | None = None |
99 | 105 | time_zone_info: str | None = None |
@@ -205,3 +211,92 @@ def wind_name(self) -> str | None: |
205 | 211 | def work_mode_name(self) -> str | None: |
206 | 212 | """Returns the name of the current work mode.""" |
207 | 213 | return self.work_mode.value if self.work_mode is not None else None |
| 214 | + |
| 215 | + @property |
| 216 | + def repeat_state_name(self) -> str | None: |
| 217 | + """Returns the name of the current repeat state.""" |
| 218 | + return self.repeat_state.value if self.repeat_state is not None else None |
| 219 | + |
| 220 | + @property |
| 221 | + def clean_path_preference_name(self) -> str | None: |
| 222 | + """Returns the name of the current clean path preference.""" |
| 223 | + return self.clean_path_preference.value if self.clean_path_preference is not None else None |
| 224 | + |
| 225 | + |
| 226 | +@dataclass |
| 227 | +class CleanRecordDetail(RoborockBase): |
| 228 | + """Represents a single clean record detail (from `record_list[].detail`).""" |
| 229 | + |
| 230 | + record_start_time: int | None = None |
| 231 | + method: int | None = None |
| 232 | + record_use_time: int | None = None |
| 233 | + clean_count: int | None = None |
| 234 | + # This is seemingly returned in meters (non-squared) |
| 235 | + record_clean_area: int | None = None |
| 236 | + record_clean_mode: int | None = None |
| 237 | + record_clean_way: int | None = None |
| 238 | + record_task_status: int | None = None |
| 239 | + record_faultcode: int | None = None |
| 240 | + record_dust_num: int | None = None |
| 241 | + clean_current_map: int | None = None |
| 242 | + record_map_url: str | None = None |
| 243 | + |
| 244 | + @property |
| 245 | + def start_datetime(self) -> datetime.datetime | None: |
| 246 | + """Convert the start datetime into a datetime object.""" |
| 247 | + if self.record_start_time is not None: |
| 248 | + return datetime.datetime.fromtimestamp(self.record_start_time).astimezone(datetime.UTC) |
| 249 | + return None |
| 250 | + |
| 251 | + @property |
| 252 | + def square_meters_area_cleaned(self) -> float | None: |
| 253 | + """Returns the area cleaned in square meters.""" |
| 254 | + if self.record_clean_area is not None: |
| 255 | + return self.record_clean_area / 100 |
| 256 | + return None |
| 257 | + |
| 258 | + |
| 259 | +@dataclass |
| 260 | +class CleanRecordListItem(RoborockBase): |
| 261 | + """Represents an entry in the clean record list returned by `service.get_record_list`.""" |
| 262 | + |
| 263 | + url: str | None = None |
| 264 | + detail: str | None = None |
| 265 | + |
| 266 | + @cached_property |
| 267 | + def detail_parsed(self) -> CleanRecordDetail | None: |
| 268 | + """Parse and return the detail as a CleanRecordDetail object.""" |
| 269 | + if self.detail is None: |
| 270 | + return None |
| 271 | + try: |
| 272 | + parsed = json.loads(self.detail) |
| 273 | + except json.JSONDecodeError as ex: |
| 274 | + raise RoborockException(f"Invalid B01 record detail JSON: {self.detail!r}") from ex |
| 275 | + return CleanRecordDetail.from_dict(parsed) |
| 276 | + |
| 277 | + |
| 278 | +@dataclass |
| 279 | +class CleanRecordList(RoborockBase): |
| 280 | + """Represents the clean record list response from `service.get_record_list`.""" |
| 281 | + |
| 282 | + total_area: int | None = None |
| 283 | + total_time: int | None = None # stored in seconds |
| 284 | + total_count: int | None = None |
| 285 | + record_list: list[CleanRecordListItem] = field(default_factory=list) |
| 286 | + |
| 287 | + @property |
| 288 | + def square_meters_area_cleaned(self) -> float | None: |
| 289 | + """Returns the area cleaned in square meters.""" |
| 290 | + if self.total_area is not None: |
| 291 | + return self.total_area / 100 |
| 292 | + return None |
| 293 | + |
| 294 | + |
| 295 | +@dataclass |
| 296 | +class CleanRecordSummary(RoborockBase): |
| 297 | + """Represents clean record totals for B01/Q7 devices.""" |
| 298 | + |
| 299 | + total_time: int | None = None |
| 300 | + total_area: int | None = None |
| 301 | + total_count: int | None = None |
| 302 | + last_record_detail: CleanRecordDetail | None = None |
0 commit comments