|
| 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, |
@@ -205,3 +209,82 @@ def wind_name(self) -> str | None: |
205 | 209 | def work_mode_name(self) -> str | None: |
206 | 210 | """Returns the name of the current work mode.""" |
207 | 211 | return self.work_mode.value if self.work_mode is not None else None |
| 212 | + |
| 213 | + |
| 214 | +@dataclass |
| 215 | +class CleanRecordDetail(RoborockBase): |
| 216 | + """Represents a single clean record detail (from `record_list[].detail`).""" |
| 217 | + |
| 218 | + record_start_time: int | None = None |
| 219 | + method: int | None = None |
| 220 | + record_use_time: int | None = None |
| 221 | + clean_count: int | None = None |
| 222 | + # This is seemingly returned in meters (non-squared) |
| 223 | + record_clean_area: int | None = None |
| 224 | + record_clean_mode: int | None = None |
| 225 | + record_clean_way: int | None = None |
| 226 | + record_task_status: int | None = None |
| 227 | + record_faultcode: int | None = None |
| 228 | + record_dust_num: int | None = None |
| 229 | + clean_current_map: int | None = None |
| 230 | + record_map_url: str | None = None |
| 231 | + |
| 232 | + @property |
| 233 | + def start_datetime(self) -> datetime.datetime | None: |
| 234 | + """Convert the start datetime into a datetime object.""" |
| 235 | + if self.record_start_time is not None: |
| 236 | + return datetime.datetime.fromtimestamp(self.record_start_time).astimezone(datetime.UTC) |
| 237 | + return None |
| 238 | + |
| 239 | + @property |
| 240 | + def square_meters_area_cleaned(self) -> float | None: |
| 241 | + """Returns the area cleaned in square meters.""" |
| 242 | + if self.record_clean_area is not None: |
| 243 | + return self.record_clean_area / 100 |
| 244 | + return None |
| 245 | + |
| 246 | + |
| 247 | +@dataclass |
| 248 | +class CleanRecordListItem(RoborockBase): |
| 249 | + """Represents an entry in the clean record list returned by `service.get_record_list`.""" |
| 250 | + |
| 251 | + url: str | None = None |
| 252 | + detail: str | None = None |
| 253 | + |
| 254 | + @cached_property |
| 255 | + def detail_parsed(self) -> CleanRecordDetail | None: |
| 256 | + """Parse and return the detail as a CleanRecordDetail object.""" |
| 257 | + if self.detail is None: |
| 258 | + return None |
| 259 | + try: |
| 260 | + parsed = json.loads(self.detail) |
| 261 | + except json.JSONDecodeError as ex: |
| 262 | + raise RoborockException(f"Invalid B01 record detail JSON: {self.detail!r}") from ex |
| 263 | + return CleanRecordDetail.from_dict(parsed) |
| 264 | + |
| 265 | + |
| 266 | +@dataclass |
| 267 | +class CleanRecordList(RoborockBase): |
| 268 | + """Represents the clean record list response from `service.get_record_list`.""" |
| 269 | + |
| 270 | + total_area: int | None = None |
| 271 | + total_time: int | None = None # stored in seconds |
| 272 | + total_count: int | None = None |
| 273 | + record_list: list[CleanRecordListItem] = field(default_factory=list) |
| 274 | + |
| 275 | + @property |
| 276 | + def square_meters_area_cleaned(self) -> float | None: |
| 277 | + """Returns the area cleaned in square meters.""" |
| 278 | + if self.total_area is not None: |
| 279 | + return self.total_area / 100 |
| 280 | + return None |
| 281 | + |
| 282 | + |
| 283 | +@dataclass |
| 284 | +class CleanRecordSummary(RoborockBase): |
| 285 | + """Represents clean record totals for B01/Q7 devices.""" |
| 286 | + |
| 287 | + total_time: int | None = None |
| 288 | + total_area: int | None = None |
| 289 | + total_count: int | None = None |
| 290 | + last_record_detail: CleanRecordDetail | None = None |
0 commit comments