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

Represents the order total information.

OrderTotal(total: int, enable: int)
total: int
enable: int
@dataclass
class Privacy(roborock.data.containers.RoborockBase):
45@dataclass
46class Privacy(RoborockBase):
47    """Represents the privacy settings of the device."""
48
49    ai_recognize: int
50    dirt_recognize: int
51    pet_recognize: int
52    carpet_turbo: int
53    carpet_avoid: int
54    carpet_show: int
55    map_uploads: int
56    ai_agent: int
57    ai_avoidance: int
58    record_uploads: int
59    along_floor: int
60    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):
63@dataclass
64class PvCharging(RoborockBase):
65    """Represents the photovoltaic charging status."""
66
67    status: int
68    begin_time: int
69    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):
72@dataclass
73class Recommend(RoborockBase):
74    """Represents cleaning recommendations."""
75
76    sill: int
77    wall: int
78    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):
81@dataclass
82class Q7MapListEntry(RoborockBase):
83    """Single map list entry returned by `service.get_map_list`."""
84
85    id: int | None = None
86    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):
 89@dataclass
 90class Q7MapList(RoborockBase):
 91    """Map list response returned by `service.get_map_list`."""
 92
 93    map_list: list[Q7MapListEntry] = field(default_factory=list)
 94
 95    @property
 96    def current_map_id(self) -> int | None:
 97        """Current map id, preferring the entry marked current."""
 98        if not self.map_list:
 99            return None
100
101        ordered = sorted(self.map_list, key=lambda entry: entry.cur or False, reverse=True)
102        first = next(iter(ordered), None)
103        if first is None or not isinstance(first.id, int):
104            return None
105        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
 95    @property
 96    def current_map_id(self) -> int | None:
 97        """Current map id, preferring the entry marked current."""
 98        if not self.map_list:
 99            return None
100
101        ordered = sorted(self.map_list, key=lambda entry: entry.cur or False, reverse=True)
102        first = next(iter(ordered), None)
103        if first is None or not isinstance(first.id, int):
104            return None
105        return first.id

Current map id, preferring the entry marked current.

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

Returns device battery level as a percentage.

main_brush_time_left: int | None
182    @property
183    def main_brush_time_left(self) -> int | None:
184        """
185        Returns estimated remaining life of the main brush in minutes.
186        Total life is 300 hours (18000 minutes).
187        """
188        if self.main_brush is None:
189            return None
190        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
192    @property
193    def side_brush_time_left(self) -> int | None:
194        """
195        Returns estimated remaining life of the side brush in minutes.
196        Total life is 200 hours (12000 minutes).
197        """
198        if self.side_brush is None:
199            return None
200        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
202    @property
203    def filter_time_left(self) -> int | None:
204        """
205        Returns estimated remaining life of the filter (hypa) in minutes.
206        Total life is 150 hours (9000 minutes).
207        """
208        if self.hypa is None:
209            return None
210        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
212    @property
213    def mop_life_time_left(self) -> int | None:
214        """
215        Returns estimated remaining life of the mop in minutes.
216        Total life is 180 hours (10800 minutes).
217        """
218        if self.mop_life is None:
219            return None
220        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
222    @property
223    def sensor_dirty_time_left(self) -> int | None:
224        """
225        Returns estimated time until sensors need cleaning in minutes.
226        Maintenance interval is typically 30 hours (1800 minutes).
227        """
228        if self.main_sensor is None:
229            return None
230        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
232    @property
233    def status_name(self) -> str | None:
234        """Returns the name of the current status."""
235        return self.status.value if self.status is not None else None

Returns the name of the current status.

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

Returns the name of the current fault.

wind_name: str | None
242    @property
243    def wind_name(self) -> str | None:
244        """Returns the name of the current fan speed (wind)."""
245        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
247    @property
248    def work_mode_name(self) -> str | None:
249        """Returns the name of the current work mode."""
250        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
252    @property
253    def repeat_state_name(self) -> str | None:
254        """Returns the name of the current repeat state."""
255        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
257    @property
258    def clean_path_preference_name(self) -> str | None:
259        """Returns the name of the current clean path preference."""
260        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):
263@dataclass
264class CleanRecordDetail(RoborockBase):
265    """Represents a single clean record detail (from `record_list[].detail`)."""
266
267    record_start_time: int | None = None
268    method: int | None = None
269    record_use_time: int | None = None
270    clean_count: int | None = None
271    # This is seemingly returned in meters (non-squared)
272    record_clean_area: int | None = None
273    record_clean_mode: int | None = None
274    record_clean_way: int | None = None
275    record_task_status: int | None = None
276    record_faultcode: int | None = None
277    record_dust_num: int | None = None
278    clean_current_map: int | None = None
279    record_map_url: str | None = None
280
281    @property
282    def start_datetime(self) -> datetime.datetime | None:
283        """Convert the start datetime into a datetime object."""
284        if self.record_start_time is not None:
285            return datetime.datetime.fromtimestamp(self.record_start_time).astimezone(datetime.UTC)
286        return None
287
288    @property
289    def square_meters_area_cleaned(self) -> float | None:
290        """Returns the area cleaned in square meters."""
291        if self.record_clean_area is not None:
292            return self.record_clean_area / 100
293        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
281    @property
282    def start_datetime(self) -> datetime.datetime | None:
283        """Convert the start datetime into a datetime object."""
284        if self.record_start_time is not None:
285            return datetime.datetime.fromtimestamp(self.record_start_time).astimezone(datetime.UTC)
286        return None

Convert the start datetime into a datetime object.

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

Returns the area cleaned in square meters.

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

Parse and return the detail as a CleanRecordDetail object.

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

Returns the area cleaned in square meters.

@dataclass
class CleanRecordSummary(roborock.data.containers.RoborockBase):
332@dataclass
333class CleanRecordSummary(RoborockBase):
334    """Represents clean record totals for B01/Q7 devices."""
335
336    total_time: int | None = None
337    total_area: int | None = None
338    total_count: int | None = None
339    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