roborock.data.b01_q7.b01_q7_containers

  1import datetime
  2import json
  3from dataclasses import dataclass, field
  4from functools import cached_property
  5
  6from ...exceptions import RoborockException
  7from ..containers import RoborockBase
  8from .b01_q7_code_mappings import (
  9    B01Fault,
 10    CleanPathPreferenceMapping,
 11    CleanRepeatMapping,
 12    CleanTypeMapping,
 13    SCWindMapping,
 14    WaterLevelMapping,
 15    WorkModeMapping,
 16    WorkStatusMapping,
 17)
 18
 19
 20@dataclass
 21class NetStatus(RoborockBase):
 22    """Represents the network status of the device."""
 23
 24    rssi: str
 25    loss: int
 26    ping: int
 27    ip: str
 28    mac: str
 29    ssid: str
 30    frequency: int
 31    bssid: str
 32
 33
 34@dataclass
 35class OrderTotal(RoborockBase):
 36    """Represents the order total information."""
 37
 38    total: int
 39    enable: int
 40
 41
 42@dataclass
 43class Privacy(RoborockBase):
 44    """Represents the privacy settings of the device."""
 45
 46    ai_recognize: int
 47    dirt_recognize: int
 48    pet_recognize: int
 49    carpet_turbo: int
 50    carpet_avoid: int
 51    carpet_show: int
 52    map_uploads: int
 53    ai_agent: int
 54    ai_avoidance: int
 55    record_uploads: int
 56    along_floor: int
 57    auto_upgrade: int
 58
 59
 60@dataclass
 61class PvCharging(RoborockBase):
 62    """Represents the photovoltaic charging status."""
 63
 64    status: int
 65    begin_time: int
 66    end_time: int
 67
 68
 69@dataclass
 70class Recommend(RoborockBase):
 71    """Represents cleaning recommendations."""
 72
 73    sill: int
 74    wall: int
 75    room_id: list[int] = field(default_factory=list)
 76
 77
 78@dataclass
 79class Q7MapListEntry(RoborockBase):
 80    """Single map list entry returned by `service.get_map_list`."""
 81
 82    id: int | None = None
 83    cur: bool | None = None
 84
 85
 86@dataclass
 87class Q7MapList(RoborockBase):
 88    """Map list response returned by `service.get_map_list`."""
 89
 90    map_list: list[Q7MapListEntry] = field(default_factory=list)
 91
 92    @property
 93    def current_map_id(self) -> int | None:
 94        """Current map id, preferring the entry marked current."""
 95        if not self.map_list:
 96            return None
 97
 98        ordered = sorted(self.map_list, key=lambda entry: entry.cur or False, reverse=True)
 99        first = next(iter(ordered), None)
100        if first is None or not isinstance(first.id, int):
101            return None
102        return first.id
103
104
105@dataclass
106class B01Props(RoborockBase):
107    """
108    Represents the complete properties and status for a Roborock B01 model.
109    This dataclass is generated based on the device's status JSON object.
110    """
111
112    status: WorkStatusMapping | None = None
113    fault: B01Fault | None = None
114    wind: SCWindMapping | None = None
115    water: WaterLevelMapping | None = None
116    mode: CleanTypeMapping | None = None
117    quantity: int | None = None  # The Q7 L5 reports its battery level as 'quantity'
118    alarm: int | None = None
119    volume: int | None = None
120    hypa: int | None = None
121    main_brush: int | None = None
122    side_brush: int | None = None
123    mop_life: int | None = None
124    main_sensor: int | None = None
125    net_status: NetStatus | None = None
126    repeat_state: CleanRepeatMapping | None = None
127    tank_state: int | None = None
128    sweep_type: int | None = None
129    clean_path_preference: CleanPathPreferenceMapping | None = None
130    cloth_state: int | None = None
131    time_zone: int | None = None
132    time_zone_info: str | None = None
133    language: int | None = None
134    cleaning_time: int | None = None
135    real_clean_time: int | None = None
136    cleaning_area: int | None = None
137    custom_type: int | None = None
138    sound: int | None = None
139    work_mode: WorkModeMapping | None = None
140    station_act: int | None = None
141    charge_state: int | None = None
142    current_map_id: int | None = None
143    map_num: int | None = None
144    dust_action: int | None = None
145    quiet_is_open: int | None = None
146    quiet_begin_time: int | None = None
147    quiet_end_time: int | None = None
148    clean_finish: int | None = None
149    voice_type: int | None = None
150    voice_type_version: int | None = None
151    order_total: OrderTotal | None = None
152    build_map: int | None = None
153    privacy: Privacy | None = None
154    dust_auto_state: int | None = None
155    dust_frequency: int | None = None
156    child_lock: int | None = None
157    multi_floor: int | None = None
158    map_save: int | None = None
159    light_mode: int | None = None
160    green_laser: int | None = None
161    dust_bag_used: int | None = None
162    order_save_mode: int | None = None
163    manufacturer: str | None = None
164    back_to_wash: int | None = None
165    charge_station_type: int | None = None
166    pv_cut_charge: int | None = None
167    pv_charging: PvCharging | None = None
168    serial_number: str | None = None
169    recommend: Recommend | None = None
170    add_sweep_status: int | None = None
171
172    @property
173    def battery(self) -> int | None:
174        """
175        Returns device battery level as a percentage.
176        """
177        return self.quantity
178
179    @property
180    def main_brush_time_left(self) -> int | None:
181        """
182        Returns estimated remaining life of the main brush in minutes.
183        Total life is 300 hours (18000 minutes).
184        """
185        if self.main_brush is None:
186            return None
187        return max(0, 18000 - self.main_brush)
188
189    @property
190    def side_brush_time_left(self) -> int | None:
191        """
192        Returns estimated remaining life of the side brush in minutes.
193        Total life is 200 hours (12000 minutes).
194        """
195        if self.side_brush is None:
196            return None
197        return max(0, 12000 - self.side_brush)
198
199    @property
200    def filter_time_left(self) -> int | None:
201        """
202        Returns estimated remaining life of the filter (hypa) in minutes.
203        Total life is 150 hours (9000 minutes).
204        """
205        if self.hypa is None:
206            return None
207        return max(0, 9000 - self.hypa)
208
209    @property
210    def mop_life_time_left(self) -> int | None:
211        """
212        Returns estimated remaining life of the mop in minutes.
213        Total life is 180 hours (10800 minutes).
214        """
215        if self.mop_life is None:
216            return None
217        return max(0, 10800 - self.mop_life)
218
219    @property
220    def sensor_dirty_time_left(self) -> int | None:
221        """
222        Returns estimated time until sensors need cleaning in minutes.
223        Maintenance interval is typically 30 hours (1800 minutes).
224        """
225        if self.main_sensor is None:
226            return None
227        return max(0, 1800 - self.main_sensor)
228
229    @property
230    def status_name(self) -> str | None:
231        """Returns the name of the current status."""
232        return self.status.value if self.status is not None else None
233
234    @property
235    def fault_name(self) -> str | None:
236        """Returns the name of the current fault."""
237        return self.fault.value if self.fault is not None else None
238
239    @property
240    def wind_name(self) -> str | None:
241        """Returns the name of the current fan speed (wind)."""
242        return self.wind.value if self.wind is not None else None
243
244    @property
245    def work_mode_name(self) -> str | None:
246        """Returns the name of the current work mode."""
247        return self.work_mode.value if self.work_mode is not None else None
248
249    @property
250    def repeat_state_name(self) -> str | None:
251        """Returns the name of the current repeat state."""
252        return self.repeat_state.value if self.repeat_state is not None else None
253
254    @property
255    def clean_path_preference_name(self) -> str | None:
256        """Returns the name of the current clean path preference."""
257        return self.clean_path_preference.value if self.clean_path_preference is not None else None
258
259
260@dataclass
261class CleanRecordDetail(RoborockBase):
262    """Represents a single clean record detail (from `record_list[].detail`)."""
263
264    record_start_time: int | None = None
265    method: int | None = None
266    record_use_time: int | None = None
267    clean_count: int | None = None
268    # This is seemingly returned in meters (non-squared)
269    record_clean_area: int | None = None
270    record_clean_mode: int | None = None
271    record_clean_way: int | None = None
272    record_task_status: int | None = None
273    record_faultcode: int | None = None
274    record_dust_num: int | None = None
275    clean_current_map: int | None = None
276    record_map_url: str | None = None
277
278    @property
279    def start_datetime(self) -> datetime.datetime | None:
280        """Convert the start datetime into a datetime object."""
281        if self.record_start_time is not None:
282            return datetime.datetime.fromtimestamp(self.record_start_time).astimezone(datetime.UTC)
283        return None
284
285    @property
286    def square_meters_area_cleaned(self) -> float | None:
287        """Returns the area cleaned in square meters."""
288        if self.record_clean_area is not None:
289            return self.record_clean_area / 100
290        return None
291
292
293@dataclass
294class CleanRecordListItem(RoborockBase):
295    """Represents an entry in the clean record list returned by `service.get_record_list`."""
296
297    url: str | None = None
298    detail: str | None = None
299
300    @cached_property
301    def detail_parsed(self) -> CleanRecordDetail | None:
302        """Parse and return the detail as a CleanRecordDetail object."""
303        if self.detail is None:
304            return None
305        try:
306            parsed = json.loads(self.detail)
307        except json.JSONDecodeError as ex:
308            raise RoborockException(f"Invalid B01 record detail JSON: {self.detail!r}") from ex
309        return CleanRecordDetail.from_dict(parsed)
310
311
312@dataclass
313class CleanRecordList(RoborockBase):
314    """Represents the clean record list response from `service.get_record_list`."""
315
316    total_area: int | None = None
317    total_time: int | None = None  # stored in seconds
318    total_count: int | None = None
319    record_list: list[CleanRecordListItem] = field(default_factory=list)
320
321    @property
322    def square_meters_area_cleaned(self) -> float | None:
323        """Returns the area cleaned in square meters."""
324        if self.total_area is not None:
325            return self.total_area / 100
326        return None
327
328
329@dataclass
330class CleanRecordSummary(RoborockBase):
331    """Represents clean record totals for B01/Q7 devices."""
332
333    total_time: int | None = None
334    total_area: int | None = None
335    total_count: int | None = None
336    last_record_detail: CleanRecordDetail | None = None
@dataclass
class NetStatus(roborock.data.containers.RoborockBase):
21@dataclass
22class NetStatus(RoborockBase):
23    """Represents the network status of the device."""
24
25    rssi: str
26    loss: int
27    ping: int
28    ip: str
29    mac: str
30    ssid: str
31    frequency: int
32    bssid: str

Represents the network status of the device.

NetStatus( rssi: str, loss: int, ping: int, ip: str, mac: str, ssid: str, frequency: int, bssid: str)
rssi: str
loss: int
ping: int
ip: str
mac: str
ssid: str
frequency: int
bssid: str
@dataclass
class OrderTotal(roborock.data.containers.RoborockBase):
35@dataclass
36class OrderTotal(RoborockBase):
37    """Represents the order total information."""
38
39    total: int
40    enable: int

Represents the order total information.

OrderTotal(total: int, enable: int)
total: int
enable: int
@dataclass
class Privacy(roborock.data.containers.RoborockBase):
43@dataclass
44class Privacy(RoborockBase):
45    """Represents the privacy settings of the device."""
46
47    ai_recognize: int
48    dirt_recognize: int
49    pet_recognize: int
50    carpet_turbo: int
51    carpet_avoid: int
52    carpet_show: int
53    map_uploads: int
54    ai_agent: int
55    ai_avoidance: int
56    record_uploads: int
57    along_floor: int
58    auto_upgrade: int

Represents the privacy settings of the device.

Privacy( ai_recognize: int, dirt_recognize: int, pet_recognize: int, carpet_turbo: int, carpet_avoid: int, carpet_show: int, map_uploads: int, ai_agent: int, ai_avoidance: int, record_uploads: int, along_floor: int, auto_upgrade: int)
ai_recognize: int
dirt_recognize: int
pet_recognize: int
carpet_turbo: int
carpet_avoid: int
carpet_show: int
map_uploads: int
ai_agent: int
ai_avoidance: int
record_uploads: int
along_floor: int
auto_upgrade: int
@dataclass
class PvCharging(roborock.data.containers.RoborockBase):
61@dataclass
62class PvCharging(RoborockBase):
63    """Represents the photovoltaic charging status."""
64
65    status: int
66    begin_time: int
67    end_time: int

Represents the photovoltaic charging status.

PvCharging(status: int, begin_time: int, end_time: int)
status: int
begin_time: int
end_time: int
@dataclass
class Recommend(roborock.data.containers.RoborockBase):
70@dataclass
71class Recommend(RoborockBase):
72    """Represents cleaning recommendations."""
73
74    sill: int
75    wall: int
76    room_id: list[int] = field(default_factory=list)

Represents cleaning recommendations.

Recommend(sill: int, wall: int, room_id: list[int] = <factory>)
sill: int
wall: int
room_id: list[int]
@dataclass
class Q7MapListEntry(roborock.data.containers.RoborockBase):
79@dataclass
80class Q7MapListEntry(RoborockBase):
81    """Single map list entry returned by `service.get_map_list`."""
82
83    id: int | None = None
84    cur: bool | None = None

Single map list entry returned by service.get_map_list.

Q7MapListEntry(id: int | None = None, cur: bool | None = None)
id: int | None = None
cur: bool | None = None
@dataclass
class Q7MapList(roborock.data.containers.RoborockBase):
 87@dataclass
 88class Q7MapList(RoborockBase):
 89    """Map list response returned by `service.get_map_list`."""
 90
 91    map_list: list[Q7MapListEntry] = field(default_factory=list)
 92
 93    @property
 94    def current_map_id(self) -> int | None:
 95        """Current map id, preferring the entry marked current."""
 96        if not self.map_list:
 97            return None
 98
 99        ordered = sorted(self.map_list, key=lambda entry: entry.cur or False, reverse=True)
100        first = next(iter(ordered), None)
101        if first is None or not isinstance(first.id, int):
102            return None
103        return first.id

Map list response returned by service.get_map_list.

Q7MapList( map_list: list[Q7MapListEntry] = <factory>)
map_list: list[Q7MapListEntry]
current_map_id: int | None
 93    @property
 94    def current_map_id(self) -> int | None:
 95        """Current map id, preferring the entry marked current."""
 96        if not self.map_list:
 97            return None
 98
 99        ordered = sorted(self.map_list, key=lambda entry: entry.cur or False, reverse=True)
100        first = next(iter(ordered), None)
101        if first is None or not isinstance(first.id, int):
102            return None
103        return first.id

Current map id, preferring the entry marked current.

@dataclass
class B01Props(roborock.data.containers.RoborockBase):
106@dataclass
107class B01Props(RoborockBase):
108    """
109    Represents the complete properties and status for a Roborock B01 model.
110    This dataclass is generated based on the device's status JSON object.
111    """
112
113    status: WorkStatusMapping | None = None
114    fault: B01Fault | None = None
115    wind: SCWindMapping | None = None
116    water: WaterLevelMapping | None = None
117    mode: CleanTypeMapping | None = None
118    quantity: int | None = None  # The Q7 L5 reports its battery level as 'quantity'
119    alarm: int | None = None
120    volume: int | None = None
121    hypa: int | None = None
122    main_brush: int | None = None
123    side_brush: int | None = None
124    mop_life: int | None = None
125    main_sensor: int | None = None
126    net_status: NetStatus | None = None
127    repeat_state: CleanRepeatMapping | None = None
128    tank_state: int | None = None
129    sweep_type: int | None = None
130    clean_path_preference: CleanPathPreferenceMapping | None = None
131    cloth_state: int | None = None
132    time_zone: int | None = None
133    time_zone_info: str | None = None
134    language: int | None = None
135    cleaning_time: int | None = None
136    real_clean_time: int | None = None
137    cleaning_area: int | None = None
138    custom_type: int | None = None
139    sound: int | None = None
140    work_mode: WorkModeMapping | None = None
141    station_act: int | None = None
142    charge_state: int | None = None
143    current_map_id: int | None = None
144    map_num: int | None = None
145    dust_action: int | None = None
146    quiet_is_open: int | None = None
147    quiet_begin_time: int | None = None
148    quiet_end_time: int | None = None
149    clean_finish: int | None = None
150    voice_type: int | None = None
151    voice_type_version: int | None = None
152    order_total: OrderTotal | None = None
153    build_map: int | None = None
154    privacy: Privacy | None = None
155    dust_auto_state: int | None = None
156    dust_frequency: int | None = None
157    child_lock: int | None = None
158    multi_floor: int | None = None
159    map_save: int | None = None
160    light_mode: int | None = None
161    green_laser: int | None = None
162    dust_bag_used: int | None = None
163    order_save_mode: int | None = None
164    manufacturer: str | None = None
165    back_to_wash: int | None = None
166    charge_station_type: int | None = None
167    pv_cut_charge: int | None = None
168    pv_charging: PvCharging | None = None
169    serial_number: str | None = None
170    recommend: Recommend | None = None
171    add_sweep_status: int | None = None
172
173    @property
174    def battery(self) -> int | None:
175        """
176        Returns device battery level as a percentage.
177        """
178        return self.quantity
179
180    @property
181    def main_brush_time_left(self) -> int | None:
182        """
183        Returns estimated remaining life of the main brush in minutes.
184        Total life is 300 hours (18000 minutes).
185        """
186        if self.main_brush is None:
187            return None
188        return max(0, 18000 - self.main_brush)
189
190    @property
191    def side_brush_time_left(self) -> int | None:
192        """
193        Returns estimated remaining life of the side brush in minutes.
194        Total life is 200 hours (12000 minutes).
195        """
196        if self.side_brush is None:
197            return None
198        return max(0, 12000 - self.side_brush)
199
200    @property
201    def filter_time_left(self) -> int | None:
202        """
203        Returns estimated remaining life of the filter (hypa) in minutes.
204        Total life is 150 hours (9000 minutes).
205        """
206        if self.hypa is None:
207            return None
208        return max(0, 9000 - self.hypa)
209
210    @property
211    def mop_life_time_left(self) -> int | None:
212        """
213        Returns estimated remaining life of the mop in minutes.
214        Total life is 180 hours (10800 minutes).
215        """
216        if self.mop_life is None:
217            return None
218        return max(0, 10800 - self.mop_life)
219
220    @property
221    def sensor_dirty_time_left(self) -> int | None:
222        """
223        Returns estimated time until sensors need cleaning in minutes.
224        Maintenance interval is typically 30 hours (1800 minutes).
225        """
226        if self.main_sensor is None:
227            return None
228        return max(0, 1800 - self.main_sensor)
229
230    @property
231    def status_name(self) -> str | None:
232        """Returns the name of the current status."""
233        return self.status.value if self.status is not None else None
234
235    @property
236    def fault_name(self) -> str | None:
237        """Returns the name of the current fault."""
238        return self.fault.value if self.fault is not None else None
239
240    @property
241    def wind_name(self) -> str | None:
242        """Returns the name of the current fan speed (wind)."""
243        return self.wind.value if self.wind is not None else None
244
245    @property
246    def work_mode_name(self) -> str | None:
247        """Returns the name of the current work mode."""
248        return self.work_mode.value if self.work_mode is not None else None
249
250    @property
251    def repeat_state_name(self) -> str | None:
252        """Returns the name of the current repeat state."""
253        return self.repeat_state.value if self.repeat_state is not None else None
254
255    @property
256    def clean_path_preference_name(self) -> str | None:
257        """Returns the name of the current clean path preference."""
258        return self.clean_path_preference.value if self.clean_path_preference is not None else None

Represents the complete properties and status for a Roborock B01 model. This dataclass is generated based on the device's status JSON object.

B01Props( status: roborock.data.b01_q7.b01_q7_code_mappings.WorkStatusMapping | None = None, fault: roborock.data.b01_q7.b01_q7_code_mappings.B01Fault | None = None, wind: roborock.data.b01_q7.b01_q7_code_mappings.SCWindMapping | None = None, water: roborock.data.b01_q7.b01_q7_code_mappings.WaterLevelMapping | None = None, mode: roborock.data.b01_q7.b01_q7_code_mappings.CleanTypeMapping | None = None, quantity: int | None = None, alarm: int | None = None, volume: int | None = None, hypa: int | None = None, main_brush: int | None = None, side_brush: int | None = None, mop_life: int | None = None, main_sensor: int | None = None, net_status: NetStatus | None = None, repeat_state: roborock.data.b01_q7.b01_q7_code_mappings.CleanRepeatMapping | None = None, tank_state: int | None = None, sweep_type: int | None = None, clean_path_preference: roborock.data.b01_q7.b01_q7_code_mappings.CleanPathPreferenceMapping | None = None, cloth_state: int | None = None, time_zone: int | None = None, time_zone_info: str | None = None, language: int | None = None, cleaning_time: int | None = None, real_clean_time: int | None = None, cleaning_area: int | None = None, custom_type: int | None = None, sound: int | None = None, work_mode: roborock.data.b01_q7.b01_q7_code_mappings.WorkModeMapping | None = None, station_act: int | None = None, charge_state: int | None = None, current_map_id: int | None = None, map_num: int | None = None, dust_action: int | None = None, quiet_is_open: int | None = None, quiet_begin_time: int | None = None, quiet_end_time: int | None = None, clean_finish: int | None = None, voice_type: int | None = None, voice_type_version: int | None = None, order_total: OrderTotal | None = None, build_map: int | None = None, privacy: Privacy | None = None, dust_auto_state: int | None = None, dust_frequency: int | None = None, child_lock: int | None = None, multi_floor: int | None = None, map_save: int | None = None, light_mode: int | None = None, green_laser: int | None = None, dust_bag_used: int | None = None, order_save_mode: int | None = None, manufacturer: str | None = None, back_to_wash: int | None = None, charge_station_type: int | None = None, pv_cut_charge: int | None = None, pv_charging: PvCharging | None = None, serial_number: str | None = None, recommend: Recommend | None = None, add_sweep_status: int | None = None)
quantity: int | None = None
alarm: int | None = None
volume: int | None = None
hypa: int | None = None
main_brush: int | None = None
side_brush: int | None = None
mop_life: int | None = None
main_sensor: int | None = None
net_status: NetStatus | None = None
tank_state: int | None = None
sweep_type: int | None = None
cloth_state: int | None = None
time_zone: int | None = None
time_zone_info: str | None = None
language: int | None = None
cleaning_time: int | None = None
real_clean_time: int | None = None
cleaning_area: int | None = None
custom_type: int | None = None
sound: int | None = None
station_act: int | None = None
charge_state: int | None = None
current_map_id: int | None = None
map_num: int | None = None
dust_action: int | None = None
quiet_is_open: int | None = None
quiet_begin_time: int | None = None
quiet_end_time: int | None = None
clean_finish: int | None = None
voice_type: int | None = None
voice_type_version: int | None = None
order_total: OrderTotal | None = None
build_map: int | None = None
privacy: Privacy | None = None
dust_auto_state: int | None = None
dust_frequency: int | None = None
child_lock: int | None = None
multi_floor: int | None = None
map_save: int | None = None
light_mode: int | None = None
green_laser: int | None = None
dust_bag_used: int | None = None
order_save_mode: int | None = None
manufacturer: str | None = None
back_to_wash: int | None = None
charge_station_type: int | None = None
pv_cut_charge: int | None = None
pv_charging: PvCharging | None = None
serial_number: str | None = None
recommend: Recommend | None = None
add_sweep_status: int | None = None
battery: int | None
173    @property
174    def battery(self) -> int | None:
175        """
176        Returns device battery level as a percentage.
177        """
178        return self.quantity

Returns device battery level as a percentage.

main_brush_time_left: int | None
180    @property
181    def main_brush_time_left(self) -> int | None:
182        """
183        Returns estimated remaining life of the main brush in minutes.
184        Total life is 300 hours (18000 minutes).
185        """
186        if self.main_brush is None:
187            return None
188        return max(0, 18000 - self.main_brush)

Returns estimated remaining life of the main brush in minutes. Total life is 300 hours (18000 minutes).

side_brush_time_left: int | None
190    @property
191    def side_brush_time_left(self) -> int | None:
192        """
193        Returns estimated remaining life of the side brush in minutes.
194        Total life is 200 hours (12000 minutes).
195        """
196        if self.side_brush is None:
197            return None
198        return max(0, 12000 - self.side_brush)

Returns estimated remaining life of the side brush in minutes. Total life is 200 hours (12000 minutes).

filter_time_left: int | None
200    @property
201    def filter_time_left(self) -> int | None:
202        """
203        Returns estimated remaining life of the filter (hypa) in minutes.
204        Total life is 150 hours (9000 minutes).
205        """
206        if self.hypa is None:
207            return None
208        return max(0, 9000 - self.hypa)

Returns estimated remaining life of the filter (hypa) in minutes. Total life is 150 hours (9000 minutes).

mop_life_time_left: int | None
210    @property
211    def mop_life_time_left(self) -> int | None:
212        """
213        Returns estimated remaining life of the mop in minutes.
214        Total life is 180 hours (10800 minutes).
215        """
216        if self.mop_life is None:
217            return None
218        return max(0, 10800 - self.mop_life)

Returns estimated remaining life of the mop in minutes. Total life is 180 hours (10800 minutes).

sensor_dirty_time_left: int | None
220    @property
221    def sensor_dirty_time_left(self) -> int | None:
222        """
223        Returns estimated time until sensors need cleaning in minutes.
224        Maintenance interval is typically 30 hours (1800 minutes).
225        """
226        if self.main_sensor is None:
227            return None
228        return max(0, 1800 - self.main_sensor)

Returns estimated time until sensors need cleaning in minutes. Maintenance interval is typically 30 hours (1800 minutes).

status_name: str | None
230    @property
231    def status_name(self) -> str | None:
232        """Returns the name of the current status."""
233        return self.status.value if self.status is not None else None

Returns the name of the current status.

fault_name: str | None
235    @property
236    def fault_name(self) -> str | None:
237        """Returns the name of the current fault."""
238        return self.fault.value if self.fault is not None else None

Returns the name of the current fault.

wind_name: str | None
240    @property
241    def wind_name(self) -> str | None:
242        """Returns the name of the current fan speed (wind)."""
243        return self.wind.value if self.wind is not None else None

Returns the name of the current fan speed (wind).

work_mode_name: str | None
245    @property
246    def work_mode_name(self) -> str | None:
247        """Returns the name of the current work mode."""
248        return self.work_mode.value if self.work_mode is not None else None

Returns the name of the current work mode.

repeat_state_name: str | None
250    @property
251    def repeat_state_name(self) -> str | None:
252        """Returns the name of the current repeat state."""
253        return self.repeat_state.value if self.repeat_state is not None else None

Returns the name of the current repeat state.

clean_path_preference_name: str | None
255    @property
256    def clean_path_preference_name(self) -> str | None:
257        """Returns the name of the current clean path preference."""
258        return self.clean_path_preference.value if self.clean_path_preference is not None else None

Returns the name of the current clean path preference.

@dataclass
class CleanRecordDetail(roborock.data.containers.RoborockBase):
261@dataclass
262class CleanRecordDetail(RoborockBase):
263    """Represents a single clean record detail (from `record_list[].detail`)."""
264
265    record_start_time: int | None = None
266    method: int | None = None
267    record_use_time: int | None = None
268    clean_count: int | None = None
269    # This is seemingly returned in meters (non-squared)
270    record_clean_area: int | None = None
271    record_clean_mode: int | None = None
272    record_clean_way: int | None = None
273    record_task_status: int | None = None
274    record_faultcode: int | None = None
275    record_dust_num: int | None = None
276    clean_current_map: int | None = None
277    record_map_url: str | None = None
278
279    @property
280    def start_datetime(self) -> datetime.datetime | None:
281        """Convert the start datetime into a datetime object."""
282        if self.record_start_time is not None:
283            return datetime.datetime.fromtimestamp(self.record_start_time).astimezone(datetime.UTC)
284        return None
285
286    @property
287    def square_meters_area_cleaned(self) -> float | None:
288        """Returns the area cleaned in square meters."""
289        if self.record_clean_area is not None:
290            return self.record_clean_area / 100
291        return None

Represents a single clean record detail (from record_list[].detail).

CleanRecordDetail( record_start_time: int | None = None, method: int | None = None, record_use_time: int | None = None, clean_count: int | None = None, record_clean_area: int | None = None, record_clean_mode: int | None = None, record_clean_way: int | None = None, record_task_status: int | None = None, record_faultcode: int | None = None, record_dust_num: int | None = None, clean_current_map: int | None = None, record_map_url: str | None = None)
record_start_time: int | None = None
method: int | None = None
record_use_time: int | None = None
clean_count: int | None = None
record_clean_area: int | None = None
record_clean_mode: int | None = None
record_clean_way: int | None = None
record_task_status: int | None = None
record_faultcode: int | None = None
record_dust_num: int | None = None
clean_current_map: int | None = None
record_map_url: str | None = None
start_datetime: datetime.datetime | None
279    @property
280    def start_datetime(self) -> datetime.datetime | None:
281        """Convert the start datetime into a datetime object."""
282        if self.record_start_time is not None:
283            return datetime.datetime.fromtimestamp(self.record_start_time).astimezone(datetime.UTC)
284        return None

Convert the start datetime into a datetime object.

square_meters_area_cleaned: float | None
286    @property
287    def square_meters_area_cleaned(self) -> float | None:
288        """Returns the area cleaned in square meters."""
289        if self.record_clean_area is not None:
290            return self.record_clean_area / 100
291        return None

Returns the area cleaned in square meters.

@dataclass
class CleanRecordListItem(roborock.data.containers.RoborockBase):
294@dataclass
295class CleanRecordListItem(RoborockBase):
296    """Represents an entry in the clean record list returned by `service.get_record_list`."""
297
298    url: str | None = None
299    detail: str | None = None
300
301    @cached_property
302    def detail_parsed(self) -> CleanRecordDetail | None:
303        """Parse and return the detail as a CleanRecordDetail object."""
304        if self.detail is None:
305            return None
306        try:
307            parsed = json.loads(self.detail)
308        except json.JSONDecodeError as ex:
309            raise RoborockException(f"Invalid B01 record detail JSON: {self.detail!r}") from ex
310        return CleanRecordDetail.from_dict(parsed)

Represents an entry in the clean record list returned by service.get_record_list.

CleanRecordListItem(url: str | None = None, detail: str | None = None)
url: str | None = None
detail: str | None = None
detail_parsed: CleanRecordDetail | None
301    @cached_property
302    def detail_parsed(self) -> CleanRecordDetail | None:
303        """Parse and return the detail as a CleanRecordDetail object."""
304        if self.detail is None:
305            return None
306        try:
307            parsed = json.loads(self.detail)
308        except json.JSONDecodeError as ex:
309            raise RoborockException(f"Invalid B01 record detail JSON: {self.detail!r}") from ex
310        return CleanRecordDetail.from_dict(parsed)

Parse and return the detail as a CleanRecordDetail object.

@dataclass
class CleanRecordList(roborock.data.containers.RoborockBase):
313@dataclass
314class CleanRecordList(RoborockBase):
315    """Represents the clean record list response from `service.get_record_list`."""
316
317    total_area: int | None = None
318    total_time: int | None = None  # stored in seconds
319    total_count: int | None = None
320    record_list: list[CleanRecordListItem] = field(default_factory=list)
321
322    @property
323    def square_meters_area_cleaned(self) -> float | None:
324        """Returns the area cleaned in square meters."""
325        if self.total_area is not None:
326            return self.total_area / 100
327        return None

Represents the clean record list response from service.get_record_list.

CleanRecordList( total_area: int | None = None, total_time: int | None = None, total_count: int | None = None, record_list: list[CleanRecordListItem] = <factory>)
total_area: int | None = None
total_time: int | None = None
total_count: int | None = None
record_list: list[CleanRecordListItem]
square_meters_area_cleaned: float | None
322    @property
323    def square_meters_area_cleaned(self) -> float | None:
324        """Returns the area cleaned in square meters."""
325        if self.total_area is not None:
326            return self.total_area / 100
327        return None

Returns the area cleaned in square meters.

@dataclass
class CleanRecordSummary(roborock.data.containers.RoborockBase):
330@dataclass
331class CleanRecordSummary(RoborockBase):
332    """Represents clean record totals for B01/Q7 devices."""
333
334    total_time: int | None = None
335    total_area: int | None = None
336    total_count: int | None = None
337    last_record_detail: CleanRecordDetail | None = None

Represents clean record totals for B01/Q7 devices.

CleanRecordSummary( total_time: int | None = None, total_area: int | None = None, total_count: int | None = None, last_record_detail: CleanRecordDetail | None = None)
total_time: int | None = None
total_area: int | None = None
total_count: int | None = None
last_record_detail: CleanRecordDetail | None = None