roborock.data.v1.v1_containers

  1import datetime
  2import logging
  3import warnings
  4from dataclasses import dataclass, field
  5from enum import StrEnum
  6from typing import Any
  7
  8from roborock.const import (
  9    CLEANING_BRUSH_REPLACE_TIME,
 10    DUST_COLLECTION_REPLACE_TIME,
 11    FILTER_REPLACE_TIME,
 12    MAIN_BRUSH_REPLACE_TIME,
 13    MOP_ROLLER_REPLACE_TIME,
 14    NO_MAP,
 15    SENSOR_DIRTY_REPLACE_TIME,
 16    SIDE_BRUSH_REPLACE_TIME,
 17    STRAINER_REPLACE_TIME,
 18)
 19from roborock.roborock_message import RoborockDataProtocol
 20
 21from ..containers import NamedRoomMapping, RoborockBase, RoborockBaseTimer, _attr_repr, field_metadata
 22from .v1_clean_modes import WashTowelModes
 23from .v1_code_mappings import (
 24    CleanFluidStatus,
 25    ClearWaterBoxStatus,
 26    DirtyWaterBoxStatus,
 27    DustBagStatus,
 28    RoborockChargeStatus,
 29    RoborockCleanType,
 30    RoborockDockDustCollectionModeCode,
 31    RoborockDockErrorCode,
 32    RoborockDockState,
 33    RoborockDockTypeCode,
 34    RoborockErrorCode,
 35    RoborockFinishReason,
 36    RoborockInCleaning,
 37    RoborockStartType,
 38    RoborockStateCode,
 39)
 40
 41_LOGGER = logging.getLogger(__name__)
 42
 43
 44class FieldNameBase(StrEnum):
 45    """A base enum class that represents a field name in a RoborockBase dataclass."""
 46
 47
 48class StatusField(FieldNameBase):
 49    """An enum that represents a field in the `StatusV2` class.
 50
 51    This is used with `roborock.devices.traits.v1.status.DeviceFeaturesTrait`
 52    to understand if a feature is supported by the device using `is_field_supported`.
 53
 54    The enum values are names of fields in the `StatusV2` class. Each field is
 55    annotated with `dps` metadata to map the field to a `RoborockDataProtocol`
 56    value used to check support against the product schema.
 57    """
 58
 59    STATE = "state"
 60    BATTERY = "battery"
 61    FAN_POWER = "fan_power"
 62    WATER_BOX_MODE = "water_box_mode"
 63    CHARGE_STATUS = "charge_status"
 64    DRY_STATUS = "dry_status"
 65    ERROR_CODE = "error_code"
 66    WATER_BOX_CARRIAGE_STATUS = "water_box_carriage_status"
 67    WATER_BOX_STATUS = "water_box_status"
 68    WATER_SHORTAGE_STATUS = "water_shortage_status"
 69    DIRTY_WATER_BOX_STATUS = "dirty_water_box_status"
 70    CLEAR_WATER_BOX_STATUS = "clear_water_box_status"
 71    CLEAN_FLUID_STATUS = "clean_fluid_status"
 72    CLEAN_PERCENT = "clean_percent"
 73    DOCK_ERROR_STATUS = "dock_error_status"
 74    RDT = "rdt"
 75
 76
 77def get_dock_state(
 78    state: RoborockStateCode | int | None,
 79    battery: int | None,
 80    charge_status: RoborockChargeStatus | int | None = None,
 81    *,
 82    is_supported_valley_electricity: bool = False,
 83) -> RoborockDockState:
 84    """Return the dock state, accounting for off-peak charging support."""
 85    if state is None or state == RoborockStateCode.unknown:
 86        return RoborockDockState.unknown
 87
 88    # 6. DUSTING
 89    if state == RoborockStateCode.emptying_the_bin:
 90        return RoborockDockState.dusting
 91
 92    # 5. FULL
 93    if state == RoborockStateCode.charging_complete or (state == RoborockStateCode.charging and battery == 100):
 94        return RoborockDockState.full
 95
 96    # 3 & 4. CHARGING and CHARGE_WAITING
 97    if state == RoborockStateCode.charging:
 98        if is_supported_valley_electricity and charge_status == RoborockChargeStatus.charge_waiting:
 99            return RoborockDockState.off_peak_waiting
100        return RoborockDockState.charging
101
102    # 2. RECHARGING
103    if state in (RoborockStateCode.returning_home, RoborockStateCode.docking):
104        return RoborockDockState.returning
105
106    # 1. IDLE (Not on dock, or doing something else)
107    return RoborockDockState.idle
108
109
110@dataclass
111class StatusV2(RoborockBase):
112    """The result of a GET_STATUS API request."""
113
114    msg_ver: int | None = None
115    msg_seq: int | None = None
116    state: RoborockStateCode | None = field(default=None, metadata={"dps": RoborockDataProtocol.STATE})
117    battery: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.BATTERY})
118    clean_time: int | None = None
119    clean_area: int | None = None
120    error_code: RoborockErrorCode | None = field(default=None, metadata={"dps": RoborockDataProtocol.ERROR_CODE})
121    map_present: int | None = None
122    in_cleaning: RoborockInCleaning | None = None
123    in_returning: int | None = None
124    in_fresh_state: int | None = None
125    lab_status: int | None = None
126    water_box_status: int | None = field(default=None, metadata={"feature": "is_support_water_mode"})
127    back_type: int | None = None
128    wash_phase: int | None = None
129    wash_ready: int | None = None
130    fan_power: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.FAN_POWER})
131    dnd_enabled: int | None = None
132    map_status: int | None = None
133    is_locating: int | None = None
134    lock_status: int | None = None
135    water_box_mode: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.WATER_BOX_MODE})
136    water_box_carriage_status: int | None = field(default=None, metadata={"feature": "is_support_water_mode"})
137    mop_forbidden_enable: int | None = None
138    camera_status: int | None = None
139    is_exploring: int | None = None
140    home_sec_status: int | None = None
141    home_sec_enable_password: int | None = None
142    adbumper_status: list[int] | None = None
143    water_shortage_status: int | None = field(default=None, metadata={"feature": "is_support_water_mode"})
144    dock_type: RoborockDockTypeCode | None = None
145    dust_collection_status: int | None = None
146    auto_dust_collection: int | None = None
147    avoid_count: int | None = None
148    mop_mode: int | None = None
149    debug_mode: int | None = None
150    collision_avoid_status: int | None = None
151    switch_map_mode: int | None = None
152    dock_error_status: RoborockDockErrorCode | None = field(default=None, metadata={"dock_feature": "has_dock"})
153
154    charge_status: RoborockChargeStatus | None = field(
155        default=None, metadata={"dps": RoborockDataProtocol.CHARGE_STATUS}
156    )
157    unsave_map_reason: int | None = None
158    unsave_map_flag: int | None = None
159    wash_status: int | None = None
160    distance_off: int | None = None
161    in_warmup: int | None = None
162    dry_status: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.DRYING_STATUS})
163    rdt: int | None = field(default=None, metadata={"feature": "is_supported_drying"})
164    clean_percent: int | None = field(default=None, metadata={"feature": "is_support_clean_estimate"})
165    rss: int | None = None
166    dss: int | None = None
167    common_status: int | None = None
168    corner_clean_mode: int | None = None
169    last_clean_t: int | None = None
170    replenish_mode: int | None = None
171    repeat: int | None = None
172    kct: int | None = None
173    subdivision_sets: int | None = None
174
175    @property
176    def square_meter_clean_area(self) -> float | None:
177        return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None
178
179    @property
180    def error_code_name(self) -> str | None:
181        return self.error_code.display_name if self.error_code is not None else None
182
183    @property
184    def state_name(self) -> str | None:
185        return self.state.display_name if self.state is not None else None
186
187    @property
188    def current_map(self) -> int | None:
189        """Returns the current map ID if the map is present."""
190        if self.map_status is not None:
191            map_flag = self.map_status >> 2
192            if map_flag != NO_MAP:
193                return map_flag
194        return None
195
196    @property
197    def has_am(self) -> bool | None:
198        if self.dss is None:
199            return None
200        return (self.dss & 3) == 2
201
202    @property
203    @field_metadata(dock_feature="is_washable")
204    def clear_water_box_status(self) -> ClearWaterBoxStatus | None:
205        if self.dss:
206            return ClearWaterBoxStatus((self.dss >> 2) & 3)
207        return None
208
209    @property
210    @field_metadata(dock_feature="is_washable")
211    def dirty_water_box_status(self) -> DirtyWaterBoxStatus | None:
212        if self.dss:
213            return DirtyWaterBoxStatus((self.dss >> 4) & 3)
214        return None
215
216    @property
217    def dust_bag_status(self) -> DustBagStatus | None:
218        if self.dss:
219            return DustBagStatus((self.dss >> 6) & 3)
220        return None
221
222    @property
223    def water_box_filter_status(self) -> int | None:
224        if self.dss:
225            return (self.dss >> 8) & 3
226        return None
227
228    @property
229    @field_metadata(
230        dock_feature="is_clean_fluid_auto_delivery_supported",
231    )
232    def clean_fluid_status(self) -> CleanFluidStatus | None:
233        if self.dss:
234            value = (self.dss >> 10) & 3
235            if value == 0:
236                return None  # Feature not supported by this device
237            return CleanFluidStatus(value)
238        return None
239
240    @property
241    def hatch_door_status(self) -> int | None:
242        if self.dss:
243            return (self.dss >> 12) & 7
244        return None
245
246    @property
247    def dock_cool_fan_status(self) -> int | None:
248        if self.dss:
249            return (self.dss >> 15) & 3
250        return None
251
252    @property
253    def dock_state(self) -> RoborockDockState:
254        """A synthesized, high-level dock state reflecting the UI's display.
255
256        .. deprecated::
257            Use StatusTrait.dock_state or get_dock_state instead.
258        """
259        warnings.warn(
260            "StatusV2.dock_state is deprecated and will be removed in a future release. "
261            "Use StatusTrait.dock_state or get_dock_state instead.",
262            DeprecationWarning,
263            stacklevel=2,
264        )
265        return get_dock_state(
266            state=self.state,
267            battery=self.battery,
268            charge_status=self.charge_status,
269            is_supported_valley_electricity=True,
270        )
271
272    @property
273    def is_battery_charging(self) -> bool:
274        """Return whether the battery is actively charging."""
275        return self.dock_state == RoborockDockState.charging
276
277    def __repr__(self) -> str:
278        return _attr_repr(self)
279
280
281@dataclass
282class DnDTimer(RoborockBaseTimer):
283    """DnDTimer"""
284
285
286@dataclass
287class ValleyElectricityTimer(RoborockBaseTimer):
288    """ValleyElectricityTimer"""
289
290
291@dataclass
292class CleanSummary(RoborockBase):
293    clean_time: int | None = None
294    clean_area: int | None = None
295    clean_count: int | None = None
296    dust_collection_count: int | None = None
297    records: list[int] | None = None
298    last_clean_t: int | None = None
299
300    @property
301    def square_meter_clean_area(self) -> float | None:
302        """Returns the clean area in square meters."""
303        if isinstance(self.clean_area, list | str):
304            _LOGGER.warning(f"Clean area is a unexpected type! Please give the following in a issue: {self.clean_area}")
305            return None
306        return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None
307
308    def __repr__(self) -> str:
309        """Return a string representation of the object including all attributes."""
310        return _attr_repr(self)
311
312
313@dataclass
314class CleanRecord(RoborockBase):
315    begin: int | None = None
316    end: int | None = None
317    duration: int | None = None
318    area: int | None = None
319    error: int | None = None
320    complete: int | None = None
321    start_type: RoborockStartType | None = None
322    clean_type: RoborockCleanType | None = None
323    finish_reason: RoborockFinishReason | None = None
324    dust_collection_status: int | None = None
325    avoid_count: int | None = None
326    wash_count: int | None = None
327    map_flag: int | None = None
328
329    @property
330    def square_meter_area(self) -> float | None:
331        return round(self.area / 1000000, 1) if self.area is not None else None
332
333    @property
334    def begin_datetime(self) -> datetime.datetime | None:
335        return datetime.datetime.fromtimestamp(self.begin).astimezone(datetime.UTC) if self.begin else None
336
337    @property
338    def end_datetime(self) -> datetime.datetime | None:
339        return datetime.datetime.fromtimestamp(self.end).astimezone(datetime.UTC) if self.end else None
340
341    def __repr__(self) -> str:
342        return _attr_repr(self)
343
344
345class CleanSummaryWithDetail(CleanSummary):
346    """CleanSummary with the last CleanRecord included."""
347
348    last_clean_record: CleanRecord | None = None
349
350
351class ConsumableField(FieldNameBase):
352    """An enum that represents a field in the `Consumable` class.
353
354    This is used with `roborock.devices.traits.v1.status.DeviceFeaturesTrait`
355    to understand if a feature is supported by the device using `is_field_supported`.
356
357    The enum values are names of fields in the `Consumable` class. Each field is
358    annotated with `dps` metadata to map the field to a `RoborockDataProtocol`
359    value used to check support against the product schema.
360    """
361
362    MAIN_BRUSH_WORK_TIME = "main_brush_work_time"
363    SIDE_BRUSH_WORK_TIME = "side_brush_work_time"
364    FILTER_WORK_TIME = "filter_work_time"
365
366
367@dataclass
368class Consumable(RoborockBase):
369    main_brush_work_time: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.MAIN_BRUSH_WORK_TIME})
370    side_brush_work_time: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.SIDE_BRUSH_WORK_TIME})
371    filter_work_time: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.FILTER_WORK_TIME})
372    filter_element_work_time: int | None = None
373    sensor_dirty_time: int | None = None
374    strainer_work_times: int | None = None
375    dust_collection_work_times: int | None = None
376    cleaning_brush_work_times: int | None = None
377    moproller_work_time: int | None = None
378
379    @property
380    def main_brush_time_left(self) -> int | None:
381        return MAIN_BRUSH_REPLACE_TIME - self.main_brush_work_time if self.main_brush_work_time is not None else None
382
383    @property
384    def side_brush_time_left(self) -> int | None:
385        return SIDE_BRUSH_REPLACE_TIME - self.side_brush_work_time if self.side_brush_work_time is not None else None
386
387    @property
388    def filter_time_left(self) -> int | None:
389        return FILTER_REPLACE_TIME - self.filter_work_time if self.filter_work_time is not None else None
390
391    @property
392    def sensor_time_left(self) -> int | None:
393        return SENSOR_DIRTY_REPLACE_TIME - self.sensor_dirty_time if self.sensor_dirty_time is not None else None
394
395    @property
396    def strainer_time_left(self) -> int | None:
397        return STRAINER_REPLACE_TIME - self.strainer_work_times if self.strainer_work_times is not None else None
398
399    @property
400    def dust_collection_time_left(self) -> int | None:
401        return (
402            DUST_COLLECTION_REPLACE_TIME - self.dust_collection_work_times
403            if self.dust_collection_work_times is not None
404            else None
405        )
406
407    @property
408    def cleaning_brush_time_left(self) -> int | None:
409        return (
410            CLEANING_BRUSH_REPLACE_TIME - self.cleaning_brush_work_times
411            if self.cleaning_brush_work_times is not None
412            else None
413        )
414
415    @property
416    def mop_roller_time_left(self) -> int | None:
417        return MOP_ROLLER_REPLACE_TIME - self.moproller_work_time if self.moproller_work_time is not None else None
418
419    def __repr__(self) -> str:
420        return _attr_repr(self)
421
422
423@dataclass
424class MultiMapsListRoom(RoborockBase):
425    id: int | None = None
426    tag: int | None = None
427    iot_name_id: str | None = None
428    iot_name: str | None = None
429
430    @property
431    def named_room_mapping(self) -> NamedRoomMapping | None:
432        """Returns a NamedRoomMapping object if valid."""
433        if self.id is None or self.iot_name_id is None:
434            return None
435        return NamedRoomMapping(
436            segment_id=self.id,
437            iot_id=self.iot_name_id,
438            raw_name=self.iot_name,
439        )
440
441
442@dataclass
443class MultiMapsListMapInfoBakMaps(RoborockBase):
444    mapflag: Any | None = None
445    add_time: Any | None = None
446
447
448@dataclass
449class MultiMapsListMapInfo(RoborockBase):
450    map_flag: int
451    name: str
452    add_time: Any | None = None
453    length: Any | None = None
454    bak_maps: list[MultiMapsListMapInfoBakMaps] | None = None
455    rooms: list[MultiMapsListRoom] | None = None
456
457    @property
458    def mapFlag(self) -> int:
459        """Alias for map_flag, returns the map flag as an integer."""
460        return self.map_flag
461
462    @property
463    def rooms_map(self) -> dict[int, NamedRoomMapping]:
464        """Returns a dictionary of room mappings by segment id."""
465        return {
466            room.id: mapping
467            for room in self.rooms or ()
468            if room.id is not None and (mapping := room.named_room_mapping) is not None
469        }
470
471
472@dataclass
473class MultiMapsList(RoborockBase):
474    max_multi_map: int | None = None
475    max_bak_map: int | None = None
476    multi_map_count: int | None = None
477    map_info: list[MultiMapsListMapInfo] | None = None
478
479
480@dataclass
481class SmartWashParams(RoborockBase):
482    smart_wash: int | None = None
483    wash_interval: int | None = None
484
485
486@dataclass
487class MopDryerProfile(RoborockBase):
488    """Dryer parameters for one state of the auto mop-drying setting."""
489
490    cliff_on: int | None = None
491    cliff_off: int | None = None
492    count: int | None = None
493    dry_time: int | None = None
494    """Drying duration in seconds. Only present in the ``on`` profile."""
495    dry_heating_film_time: int | None = None
496    """Heating element run time in seconds. Only present in the ``on`` profile."""
497
498
499@dataclass
500class MopDryerSetting(RoborockBase):
501    """Auto mop-drying setting, as returned by APP_GET_DRYER_SETTING."""
502
503    status: int | None = None
504    on: MopDryerProfile | None = None
505    off: MopDryerProfile | None = None
506
507
508@dataclass
509class DustCollectionMode(RoborockBase):
510    mode: RoborockDockDustCollectionModeCode | None = None
511
512
513@dataclass
514class WashTowelMode(RoborockBase):
515    wash_mode: WashTowelModes | None = None
516
517
518SEQ_TYPE_CLEAN_THEN_MOP = 1
519
520
521@dataclass
522class SegmentCleanMode(RoborockBase):
523    """A single room's clean mode override, as reported by the device.
524
525    `fan_power` / `water_box_mode` / `mop_mode` / `seq_type` are kept as raw
526    `int | None` wire codes rather than typed enums, matching `StatusV2`, so
527    model-specific values (e.g. water-slide codes like 222) round-trip
528    losslessly even for codes this library doesn't otherwise model.
529
530    `mop_power` and `custom_water_box_distance` are only reported by some
531    models (roller-mop / custom-water-flow devices); they are `None` on
532    models that don't report them.
533    """
534
535    segment: int
536    fan_power: int | None = None
537    water_box_mode: int | None = None
538    mop_mode: int | None = None
539    repeat: int | None = None
540    seq_type: int | None = None
541    mop_power: int | None = None
542    custom_water_box_distance: int | None = None
543
544    @property
545    def clean_then_mop(self) -> bool | None:
546        """Whether this room is vacuumed fully before it is mopped.
547
548        `None` means the device did not report a sequence type for this
549        room's override.
550        """
551        if self.seq_type is None:
552            return None
553        return self.seq_type == SEQ_TYPE_CLEAN_THEN_MOP
554
555
556@dataclass
557class CustomizeCleanModes(RoborockBase):
558    """Collection of per-room clean mode overrides."""
559
560    modes: list[SegmentCleanMode] | None = None
561
562    @property
563    def by_segment(self) -> dict[int, SegmentCleanMode]:
564        """Index the overrides by segment id."""
565        return {mode.segment: mode for mode in self.modes or []}
566
567
568@dataclass
569class NetworkInfo(RoborockBase):
570    ip: str
571    ssid: str | None = None
572    mac: str | None = None
573    bssid: str | None = None
574    rssi: int | None = None
575
576
577@dataclass
578class AppInitStatusLocalInfo(RoborockBase):
579    location: str
580    bom: str | None = None
581    featureset: int | None = None
582    language: str | None = None
583    logserver: str | None = None
584    wifiplan: str | None = None
585    timezone: str | None = None
586    name: str | None = None
587
588
589@dataclass
590class AppInitStatus(RoborockBase):
591    local_info: AppInitStatusLocalInfo
592    feature_info: list[int]
593    new_feature_info: int = 0
594    new_feature_info_str: str = ""
595    new_feature_info_2: int | None = None
596    carriage_type: int | None = None
597    dsp_version: str | None = None
598
599
600@dataclass
601class ChildLockStatus(RoborockBase):
602    lock_status: int = 0
603
604
605@dataclass
606class FlowLedStatus(RoborockBase):
607    status: int = 0
608
609
610@dataclass
611class LedStatus(RoborockBase):
612    status: int = 0
class FieldNameBase(enum.StrEnum):
45class FieldNameBase(StrEnum):
46    """A base enum class that represents a field name in a RoborockBase dataclass."""

A base enum class that represents a field name in a RoborockBase dataclass.

class StatusField(FieldNameBase):
49class StatusField(FieldNameBase):
50    """An enum that represents a field in the `StatusV2` class.
51
52    This is used with `roborock.devices.traits.v1.status.DeviceFeaturesTrait`
53    to understand if a feature is supported by the device using `is_field_supported`.
54
55    The enum values are names of fields in the `StatusV2` class. Each field is
56    annotated with `dps` metadata to map the field to a `RoborockDataProtocol`
57    value used to check support against the product schema.
58    """
59
60    STATE = "state"
61    BATTERY = "battery"
62    FAN_POWER = "fan_power"
63    WATER_BOX_MODE = "water_box_mode"
64    CHARGE_STATUS = "charge_status"
65    DRY_STATUS = "dry_status"
66    ERROR_CODE = "error_code"
67    WATER_BOX_CARRIAGE_STATUS = "water_box_carriage_status"
68    WATER_BOX_STATUS = "water_box_status"
69    WATER_SHORTAGE_STATUS = "water_shortage_status"
70    DIRTY_WATER_BOX_STATUS = "dirty_water_box_status"
71    CLEAR_WATER_BOX_STATUS = "clear_water_box_status"
72    CLEAN_FLUID_STATUS = "clean_fluid_status"
73    CLEAN_PERCENT = "clean_percent"
74    DOCK_ERROR_STATUS = "dock_error_status"
75    RDT = "rdt"

An enum that represents a field in the StatusV2 class.

This is used with roborock.devices.traits.v1.status.DeviceFeaturesTrait to understand if a feature is supported by the device using is_field_supported.

The enum values are names of fields in the StatusV2 class. Each field is annotated with dps metadata to map the field to a RoborockDataProtocol value used to check support against the product schema.

STATE = <StatusField.STATE: 'state'>
BATTERY = <StatusField.BATTERY: 'battery'>
FAN_POWER = <StatusField.FAN_POWER: 'fan_power'>
WATER_BOX_MODE = <StatusField.WATER_BOX_MODE: 'water_box_mode'>
CHARGE_STATUS = <StatusField.CHARGE_STATUS: 'charge_status'>
DRY_STATUS = <StatusField.DRY_STATUS: 'dry_status'>
ERROR_CODE = <StatusField.ERROR_CODE: 'error_code'>
WATER_BOX_CARRIAGE_STATUS = <StatusField.WATER_BOX_CARRIAGE_STATUS: 'water_box_carriage_status'>
WATER_BOX_STATUS = <StatusField.WATER_BOX_STATUS: 'water_box_status'>
WATER_SHORTAGE_STATUS = <StatusField.WATER_SHORTAGE_STATUS: 'water_shortage_status'>
DIRTY_WATER_BOX_STATUS = <StatusField.DIRTY_WATER_BOX_STATUS: 'dirty_water_box_status'>
CLEAR_WATER_BOX_STATUS = <StatusField.CLEAR_WATER_BOX_STATUS: 'clear_water_box_status'>
CLEAN_FLUID_STATUS = <StatusField.CLEAN_FLUID_STATUS: 'clean_fluid_status'>
CLEAN_PERCENT = <StatusField.CLEAN_PERCENT: 'clean_percent'>
DOCK_ERROR_STATUS = <StatusField.DOCK_ERROR_STATUS: 'dock_error_status'>
RDT = <StatusField.RDT: 'rdt'>
def get_dock_state( state: roborock.data.v1.v1_code_mappings.RoborockStateCode | int | None, battery: int | None, charge_status: roborock.data.v1.v1_code_mappings.RoborockChargeStatus | int | None = None, *, is_supported_valley_electricity: bool = False) -> roborock.data.v1.v1_code_mappings.RoborockDockState:
 78def get_dock_state(
 79    state: RoborockStateCode | int | None,
 80    battery: int | None,
 81    charge_status: RoborockChargeStatus | int | None = None,
 82    *,
 83    is_supported_valley_electricity: bool = False,
 84) -> RoborockDockState:
 85    """Return the dock state, accounting for off-peak charging support."""
 86    if state is None or state == RoborockStateCode.unknown:
 87        return RoborockDockState.unknown
 88
 89    # 6. DUSTING
 90    if state == RoborockStateCode.emptying_the_bin:
 91        return RoborockDockState.dusting
 92
 93    # 5. FULL
 94    if state == RoborockStateCode.charging_complete or (state == RoborockStateCode.charging and battery == 100):
 95        return RoborockDockState.full
 96
 97    # 3 & 4. CHARGING and CHARGE_WAITING
 98    if state == RoborockStateCode.charging:
 99        if is_supported_valley_electricity and charge_status == RoborockChargeStatus.charge_waiting:
100            return RoborockDockState.off_peak_waiting
101        return RoborockDockState.charging
102
103    # 2. RECHARGING
104    if state in (RoborockStateCode.returning_home, RoborockStateCode.docking):
105        return RoborockDockState.returning
106
107    # 1. IDLE (Not on dock, or doing something else)
108    return RoborockDockState.idle

Return the dock state, accounting for off-peak charging support.

@dataclass
class StatusV2(roborock.data.containers.RoborockBase):
111@dataclass
112class StatusV2(RoborockBase):
113    """The result of a GET_STATUS API request."""
114
115    msg_ver: int | None = None
116    msg_seq: int | None = None
117    state: RoborockStateCode | None = field(default=None, metadata={"dps": RoborockDataProtocol.STATE})
118    battery: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.BATTERY})
119    clean_time: int | None = None
120    clean_area: int | None = None
121    error_code: RoborockErrorCode | None = field(default=None, metadata={"dps": RoborockDataProtocol.ERROR_CODE})
122    map_present: int | None = None
123    in_cleaning: RoborockInCleaning | None = None
124    in_returning: int | None = None
125    in_fresh_state: int | None = None
126    lab_status: int | None = None
127    water_box_status: int | None = field(default=None, metadata={"feature": "is_support_water_mode"})
128    back_type: int | None = None
129    wash_phase: int | None = None
130    wash_ready: int | None = None
131    fan_power: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.FAN_POWER})
132    dnd_enabled: int | None = None
133    map_status: int | None = None
134    is_locating: int | None = None
135    lock_status: int | None = None
136    water_box_mode: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.WATER_BOX_MODE})
137    water_box_carriage_status: int | None = field(default=None, metadata={"feature": "is_support_water_mode"})
138    mop_forbidden_enable: int | None = None
139    camera_status: int | None = None
140    is_exploring: int | None = None
141    home_sec_status: int | None = None
142    home_sec_enable_password: int | None = None
143    adbumper_status: list[int] | None = None
144    water_shortage_status: int | None = field(default=None, metadata={"feature": "is_support_water_mode"})
145    dock_type: RoborockDockTypeCode | None = None
146    dust_collection_status: int | None = None
147    auto_dust_collection: int | None = None
148    avoid_count: int | None = None
149    mop_mode: int | None = None
150    debug_mode: int | None = None
151    collision_avoid_status: int | None = None
152    switch_map_mode: int | None = None
153    dock_error_status: RoborockDockErrorCode | None = field(default=None, metadata={"dock_feature": "has_dock"})
154
155    charge_status: RoborockChargeStatus | None = field(
156        default=None, metadata={"dps": RoborockDataProtocol.CHARGE_STATUS}
157    )
158    unsave_map_reason: int | None = None
159    unsave_map_flag: int | None = None
160    wash_status: int | None = None
161    distance_off: int | None = None
162    in_warmup: int | None = None
163    dry_status: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.DRYING_STATUS})
164    rdt: int | None = field(default=None, metadata={"feature": "is_supported_drying"})
165    clean_percent: int | None = field(default=None, metadata={"feature": "is_support_clean_estimate"})
166    rss: int | None = None
167    dss: int | None = None
168    common_status: int | None = None
169    corner_clean_mode: int | None = None
170    last_clean_t: int | None = None
171    replenish_mode: int | None = None
172    repeat: int | None = None
173    kct: int | None = None
174    subdivision_sets: int | None = None
175
176    @property
177    def square_meter_clean_area(self) -> float | None:
178        return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None
179
180    @property
181    def error_code_name(self) -> str | None:
182        return self.error_code.display_name if self.error_code is not None else None
183
184    @property
185    def state_name(self) -> str | None:
186        return self.state.display_name if self.state is not None else None
187
188    @property
189    def current_map(self) -> int | None:
190        """Returns the current map ID if the map is present."""
191        if self.map_status is not None:
192            map_flag = self.map_status >> 2
193            if map_flag != NO_MAP:
194                return map_flag
195        return None
196
197    @property
198    def has_am(self) -> bool | None:
199        if self.dss is None:
200            return None
201        return (self.dss & 3) == 2
202
203    @property
204    @field_metadata(dock_feature="is_washable")
205    def clear_water_box_status(self) -> ClearWaterBoxStatus | None:
206        if self.dss:
207            return ClearWaterBoxStatus((self.dss >> 2) & 3)
208        return None
209
210    @property
211    @field_metadata(dock_feature="is_washable")
212    def dirty_water_box_status(self) -> DirtyWaterBoxStatus | None:
213        if self.dss:
214            return DirtyWaterBoxStatus((self.dss >> 4) & 3)
215        return None
216
217    @property
218    def dust_bag_status(self) -> DustBagStatus | None:
219        if self.dss:
220            return DustBagStatus((self.dss >> 6) & 3)
221        return None
222
223    @property
224    def water_box_filter_status(self) -> int | None:
225        if self.dss:
226            return (self.dss >> 8) & 3
227        return None
228
229    @property
230    @field_metadata(
231        dock_feature="is_clean_fluid_auto_delivery_supported",
232    )
233    def clean_fluid_status(self) -> CleanFluidStatus | None:
234        if self.dss:
235            value = (self.dss >> 10) & 3
236            if value == 0:
237                return None  # Feature not supported by this device
238            return CleanFluidStatus(value)
239        return None
240
241    @property
242    def hatch_door_status(self) -> int | None:
243        if self.dss:
244            return (self.dss >> 12) & 7
245        return None
246
247    @property
248    def dock_cool_fan_status(self) -> int | None:
249        if self.dss:
250            return (self.dss >> 15) & 3
251        return None
252
253    @property
254    def dock_state(self) -> RoborockDockState:
255        """A synthesized, high-level dock state reflecting the UI's display.
256
257        .. deprecated::
258            Use StatusTrait.dock_state or get_dock_state instead.
259        """
260        warnings.warn(
261            "StatusV2.dock_state is deprecated and will be removed in a future release. "
262            "Use StatusTrait.dock_state or get_dock_state instead.",
263            DeprecationWarning,
264            stacklevel=2,
265        )
266        return get_dock_state(
267            state=self.state,
268            battery=self.battery,
269            charge_status=self.charge_status,
270            is_supported_valley_electricity=True,
271        )
272
273    @property
274    def is_battery_charging(self) -> bool:
275        """Return whether the battery is actively charging."""
276        return self.dock_state == RoborockDockState.charging
277
278    def __repr__(self) -> str:
279        return _attr_repr(self)

The result of a GET_STATUS API request.

StatusV2( msg_ver: int | None = None, msg_seq: int | None = None, state: roborock.data.v1.v1_code_mappings.RoborockStateCode | None = None, battery: int | None = None, clean_time: int | None = None, clean_area: int | None = None, error_code: roborock.data.v1.v1_code_mappings.RoborockErrorCode | None = None, map_present: int | None = None, in_cleaning: roborock.data.v1.v1_code_mappings.RoborockInCleaning | None = None, in_returning: int | None = None, in_fresh_state: int | None = None, lab_status: int | None = None, water_box_status: int | None = None, back_type: int | None = None, wash_phase: int | None = None, wash_ready: int | None = None, fan_power: int | None = None, dnd_enabled: int | None = None, map_status: int | None = None, is_locating: int | None = None, lock_status: int | None = None, water_box_mode: int | None = None, water_box_carriage_status: int | None = None, mop_forbidden_enable: int | None = None, camera_status: int | None = None, is_exploring: int | None = None, home_sec_status: int | None = None, home_sec_enable_password: int | None = None, adbumper_status: list[int] | None = None, water_shortage_status: int | None = None, dock_type: roborock.data.v1.v1_code_mappings.RoborockDockTypeCode | None = None, dust_collection_status: int | None = None, auto_dust_collection: int | None = None, avoid_count: int | None = None, mop_mode: int | None = None, debug_mode: int | None = None, collision_avoid_status: int | None = None, switch_map_mode: int | None = None, dock_error_status: roborock.data.v1.v1_code_mappings.RoborockDockErrorCode | None = None, charge_status: roborock.data.v1.v1_code_mappings.RoborockChargeStatus | None = None, unsave_map_reason: int | None = None, unsave_map_flag: int | None = None, wash_status: int | None = None, distance_off: int | None = None, in_warmup: int | None = None, dry_status: int | None = None, rdt: int | None = None, clean_percent: int | None = None, rss: int | None = None, dss: int | None = None, common_status: int | None = None, corner_clean_mode: int | None = None, last_clean_t: int | None = None, replenish_mode: int | None = None, repeat: int | None = None, kct: int | None = None, subdivision_sets: int | None = None)
msg_ver: int | None = None
msg_seq: int | None = None
battery: int | None = None
clean_time: int | None = None
clean_area: int | None = None
map_present: int | None = None
in_returning: int | None = None
in_fresh_state: int | None = None
lab_status: int | None = None
water_box_status: int | None = None
back_type: int | None = None
wash_phase: int | None = None
wash_ready: int | None = None
fan_power: int | None = None
dnd_enabled: int | None = None
map_status: int | None = None
is_locating: int | None = None
lock_status: int | None = None
water_box_mode: int | None = None
water_box_carriage_status: int | None = None
mop_forbidden_enable: int | None = None
camera_status: int | None = None
is_exploring: int | None = None
home_sec_status: int | None = None
home_sec_enable_password: int | None = None
adbumper_status: list[int] | None = None
water_shortage_status: int | None = None
dust_collection_status: int | None = None
auto_dust_collection: int | None = None
avoid_count: int | None = None
mop_mode: int | None = None
debug_mode: int | None = None
collision_avoid_status: int | None = None
switch_map_mode: int | None = None
unsave_map_reason: int | None = None
unsave_map_flag: int | None = None
wash_status: int | None = None
distance_off: int | None = None
in_warmup: int | None = None
dry_status: int | None = None
rdt: int | None = None
clean_percent: int | None = None
rss: int | None = None
dss: int | None = None
common_status: int | None = None
corner_clean_mode: int | None = None
last_clean_t: int | None = None
replenish_mode: int | None = None
repeat: int | None = None
kct: int | None = None
subdivision_sets: int | None = None
square_meter_clean_area: float | None
176    @property
177    def square_meter_clean_area(self) -> float | None:
178        return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None
error_code_name: str | None
180    @property
181    def error_code_name(self) -> str | None:
182        return self.error_code.display_name if self.error_code is not None else None
state_name: str | None
184    @property
185    def state_name(self) -> str | None:
186        return self.state.display_name if self.state is not None else None
current_map: int | None
188    @property
189    def current_map(self) -> int | None:
190        """Returns the current map ID if the map is present."""
191        if self.map_status is not None:
192            map_flag = self.map_status >> 2
193            if map_flag != NO_MAP:
194                return map_flag
195        return None

Returns the current map ID if the map is present.

has_am: bool | None
197    @property
198    def has_am(self) -> bool | None:
199        if self.dss is None:
200            return None
201        return (self.dss & 3) == 2
clear_water_box_status: roborock.data.v1.v1_code_mappings.ClearWaterBoxStatus | None
203    @property
204    @field_metadata(dock_feature="is_washable")
205    def clear_water_box_status(self) -> ClearWaterBoxStatus | None:
206        if self.dss:
207            return ClearWaterBoxStatus((self.dss >> 2) & 3)
208        return None
dirty_water_box_status: roborock.data.v1.v1_code_mappings.DirtyWaterBoxStatus | None
210    @property
211    @field_metadata(dock_feature="is_washable")
212    def dirty_water_box_status(self) -> DirtyWaterBoxStatus | None:
213        if self.dss:
214            return DirtyWaterBoxStatus((self.dss >> 4) & 3)
215        return None
dust_bag_status: roborock.data.v1.v1_code_mappings.DustBagStatus | None
217    @property
218    def dust_bag_status(self) -> DustBagStatus | None:
219        if self.dss:
220            return DustBagStatus((self.dss >> 6) & 3)
221        return None
water_box_filter_status: int | None
223    @property
224    def water_box_filter_status(self) -> int | None:
225        if self.dss:
226            return (self.dss >> 8) & 3
227        return None
clean_fluid_status: roborock.data.v1.v1_code_mappings.CleanFluidStatus | None
229    @property
230    @field_metadata(
231        dock_feature="is_clean_fluid_auto_delivery_supported",
232    )
233    def clean_fluid_status(self) -> CleanFluidStatus | None:
234        if self.dss:
235            value = (self.dss >> 10) & 3
236            if value == 0:
237                return None  # Feature not supported by this device
238            return CleanFluidStatus(value)
239        return None
hatch_door_status: int | None
241    @property
242    def hatch_door_status(self) -> int | None:
243        if self.dss:
244            return (self.dss >> 12) & 7
245        return None
dock_cool_fan_status: int | None
247    @property
248    def dock_cool_fan_status(self) -> int | None:
249        if self.dss:
250            return (self.dss >> 15) & 3
251        return None
253    @property
254    def dock_state(self) -> RoborockDockState:
255        """A synthesized, high-level dock state reflecting the UI's display.
256
257        .. deprecated::
258            Use StatusTrait.dock_state or get_dock_state instead.
259        """
260        warnings.warn(
261            "StatusV2.dock_state is deprecated and will be removed in a future release. "
262            "Use StatusTrait.dock_state or get_dock_state instead.",
263            DeprecationWarning,
264            stacklevel=2,
265        )
266        return get_dock_state(
267            state=self.state,
268            battery=self.battery,
269            charge_status=self.charge_status,
270            is_supported_valley_electricity=True,
271        )

A synthesized, high-level dock state reflecting the UI's display.

Deprecated since version : Use StatusTrait.dock_state or get_dock_state instead.

is_battery_charging: bool
273    @property
274    def is_battery_charging(self) -> bool:
275        """Return whether the battery is actively charging."""
276        return self.dock_state == RoborockDockState.charging

Return whether the battery is actively charging.

@dataclass
class DnDTimer(roborock.data.containers.RoborockBaseTimer):
282@dataclass
283class DnDTimer(RoborockBaseTimer):
284    """DnDTimer"""

DnDTimer

DnDTimer( start_hour: int | None = None, start_minute: int | None = None, end_hour: int | None = None, end_minute: int | None = None, enabled: int | None = None)
@dataclass
class ValleyElectricityTimer(roborock.data.containers.RoborockBaseTimer):
287@dataclass
288class ValleyElectricityTimer(RoborockBaseTimer):
289    """ValleyElectricityTimer"""

ValleyElectricityTimer

ValleyElectricityTimer( start_hour: int | None = None, start_minute: int | None = None, end_hour: int | None = None, end_minute: int | None = None, enabled: int | None = None)
@dataclass
class CleanSummary(roborock.data.containers.RoborockBase):
292@dataclass
293class CleanSummary(RoborockBase):
294    clean_time: int | None = None
295    clean_area: int | None = None
296    clean_count: int | None = None
297    dust_collection_count: int | None = None
298    records: list[int] | None = None
299    last_clean_t: int | None = None
300
301    @property
302    def square_meter_clean_area(self) -> float | None:
303        """Returns the clean area in square meters."""
304        if isinstance(self.clean_area, list | str):
305            _LOGGER.warning(f"Clean area is a unexpected type! Please give the following in a issue: {self.clean_area}")
306            return None
307        return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None
308
309    def __repr__(self) -> str:
310        """Return a string representation of the object including all attributes."""
311        return _attr_repr(self)
CleanSummary( clean_time: int | None = None, clean_area: int | None = None, clean_count: int | None = None, dust_collection_count: int | None = None, records: list[int] | None = None, last_clean_t: int | None = None)
clean_time: int | None = None
clean_area: int | None = None
clean_count: int | None = None
dust_collection_count: int | None = None
records: list[int] | None = None
last_clean_t: int | None = None
square_meter_clean_area: float | None
301    @property
302    def square_meter_clean_area(self) -> float | None:
303        """Returns the clean area in square meters."""
304        if isinstance(self.clean_area, list | str):
305            _LOGGER.warning(f"Clean area is a unexpected type! Please give the following in a issue: {self.clean_area}")
306            return None
307        return round(self.clean_area / 1000000, 1) if self.clean_area is not None else None

Returns the clean area in square meters.

@dataclass
class CleanRecord(roborock.data.containers.RoborockBase):
314@dataclass
315class CleanRecord(RoborockBase):
316    begin: int | None = None
317    end: int | None = None
318    duration: int | None = None
319    area: int | None = None
320    error: int | None = None
321    complete: int | None = None
322    start_type: RoborockStartType | None = None
323    clean_type: RoborockCleanType | None = None
324    finish_reason: RoborockFinishReason | None = None
325    dust_collection_status: int | None = None
326    avoid_count: int | None = None
327    wash_count: int | None = None
328    map_flag: int | None = None
329
330    @property
331    def square_meter_area(self) -> float | None:
332        return round(self.area / 1000000, 1) if self.area is not None else None
333
334    @property
335    def begin_datetime(self) -> datetime.datetime | None:
336        return datetime.datetime.fromtimestamp(self.begin).astimezone(datetime.UTC) if self.begin else None
337
338    @property
339    def end_datetime(self) -> datetime.datetime | None:
340        return datetime.datetime.fromtimestamp(self.end).astimezone(datetime.UTC) if self.end else None
341
342    def __repr__(self) -> str:
343        return _attr_repr(self)
CleanRecord( begin: int | None = None, end: int | None = None, duration: int | None = None, area: int | None = None, error: int | None = None, complete: int | None = None, start_type: roborock.data.v1.v1_code_mappings.RoborockStartType | None = None, clean_type: roborock.data.v1.v1_code_mappings.RoborockCleanType | None = None, finish_reason: roborock.data.v1.v1_code_mappings.RoborockFinishReason | None = None, dust_collection_status: int | None = None, avoid_count: int | None = None, wash_count: int | None = None, map_flag: int | None = None)
begin: int | None = None
end: int | None = None
duration: int | None = None
area: int | None = None
error: int | None = None
complete: int | None = None
dust_collection_status: int | None = None
avoid_count: int | None = None
wash_count: int | None = None
map_flag: int | None = None
square_meter_area: float | None
330    @property
331    def square_meter_area(self) -> float | None:
332        return round(self.area / 1000000, 1) if self.area is not None else None
begin_datetime: datetime.datetime | None
334    @property
335    def begin_datetime(self) -> datetime.datetime | None:
336        return datetime.datetime.fromtimestamp(self.begin).astimezone(datetime.UTC) if self.begin else None
end_datetime: datetime.datetime | None
338    @property
339    def end_datetime(self) -> datetime.datetime | None:
340        return datetime.datetime.fromtimestamp(self.end).astimezone(datetime.UTC) if self.end else None
class CleanSummaryWithDetail(CleanSummary):
346class CleanSummaryWithDetail(CleanSummary):
347    """CleanSummary with the last CleanRecord included."""
348
349    last_clean_record: CleanRecord | None = None

CleanSummary with the last CleanRecord included.

last_clean_record: CleanRecord | None = None
class ConsumableField(FieldNameBase):
352class ConsumableField(FieldNameBase):
353    """An enum that represents a field in the `Consumable` class.
354
355    This is used with `roborock.devices.traits.v1.status.DeviceFeaturesTrait`
356    to understand if a feature is supported by the device using `is_field_supported`.
357
358    The enum values are names of fields in the `Consumable` class. Each field is
359    annotated with `dps` metadata to map the field to a `RoborockDataProtocol`
360    value used to check support against the product schema.
361    """
362
363    MAIN_BRUSH_WORK_TIME = "main_brush_work_time"
364    SIDE_BRUSH_WORK_TIME = "side_brush_work_time"
365    FILTER_WORK_TIME = "filter_work_time"

An enum that represents a field in the Consumable class.

This is used with roborock.devices.traits.v1.status.DeviceFeaturesTrait to understand if a feature is supported by the device using is_field_supported.

The enum values are names of fields in the Consumable class. Each field is annotated with dps metadata to map the field to a RoborockDataProtocol value used to check support against the product schema.

MAIN_BRUSH_WORK_TIME = <ConsumableField.MAIN_BRUSH_WORK_TIME: 'main_brush_work_time'>
SIDE_BRUSH_WORK_TIME = <ConsumableField.SIDE_BRUSH_WORK_TIME: 'side_brush_work_time'>
FILTER_WORK_TIME = <ConsumableField.FILTER_WORK_TIME: 'filter_work_time'>
@dataclass
class Consumable(roborock.data.containers.RoborockBase):
368@dataclass
369class Consumable(RoborockBase):
370    main_brush_work_time: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.MAIN_BRUSH_WORK_TIME})
371    side_brush_work_time: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.SIDE_BRUSH_WORK_TIME})
372    filter_work_time: int | None = field(default=None, metadata={"dps": RoborockDataProtocol.FILTER_WORK_TIME})
373    filter_element_work_time: int | None = None
374    sensor_dirty_time: int | None = None
375    strainer_work_times: int | None = None
376    dust_collection_work_times: int | None = None
377    cleaning_brush_work_times: int | None = None
378    moproller_work_time: int | None = None
379
380    @property
381    def main_brush_time_left(self) -> int | None:
382        return MAIN_BRUSH_REPLACE_TIME - self.main_brush_work_time if self.main_brush_work_time is not None else None
383
384    @property
385    def side_brush_time_left(self) -> int | None:
386        return SIDE_BRUSH_REPLACE_TIME - self.side_brush_work_time if self.side_brush_work_time is not None else None
387
388    @property
389    def filter_time_left(self) -> int | None:
390        return FILTER_REPLACE_TIME - self.filter_work_time if self.filter_work_time is not None else None
391
392    @property
393    def sensor_time_left(self) -> int | None:
394        return SENSOR_DIRTY_REPLACE_TIME - self.sensor_dirty_time if self.sensor_dirty_time is not None else None
395
396    @property
397    def strainer_time_left(self) -> int | None:
398        return STRAINER_REPLACE_TIME - self.strainer_work_times if self.strainer_work_times is not None else None
399
400    @property
401    def dust_collection_time_left(self) -> int | None:
402        return (
403            DUST_COLLECTION_REPLACE_TIME - self.dust_collection_work_times
404            if self.dust_collection_work_times is not None
405            else None
406        )
407
408    @property
409    def cleaning_brush_time_left(self) -> int | None:
410        return (
411            CLEANING_BRUSH_REPLACE_TIME - self.cleaning_brush_work_times
412            if self.cleaning_brush_work_times is not None
413            else None
414        )
415
416    @property
417    def mop_roller_time_left(self) -> int | None:
418        return MOP_ROLLER_REPLACE_TIME - self.moproller_work_time if self.moproller_work_time is not None else None
419
420    def __repr__(self) -> str:
421        return _attr_repr(self)
Consumable( main_brush_work_time: int | None = None, side_brush_work_time: int | None = None, filter_work_time: int | None = None, filter_element_work_time: int | None = None, sensor_dirty_time: int | None = None, strainer_work_times: int | None = None, dust_collection_work_times: int | None = None, cleaning_brush_work_times: int | None = None, moproller_work_time: int | None = None)
main_brush_work_time: int | None = None
side_brush_work_time: int | None = None
filter_work_time: int | None = None
filter_element_work_time: int | None = None
sensor_dirty_time: int | None = None
strainer_work_times: int | None = None
dust_collection_work_times: int | None = None
cleaning_brush_work_times: int | None = None
moproller_work_time: int | None = None
main_brush_time_left: int | None
380    @property
381    def main_brush_time_left(self) -> int | None:
382        return MAIN_BRUSH_REPLACE_TIME - self.main_brush_work_time if self.main_brush_work_time is not None else None
side_brush_time_left: int | None
384    @property
385    def side_brush_time_left(self) -> int | None:
386        return SIDE_BRUSH_REPLACE_TIME - self.side_brush_work_time if self.side_brush_work_time is not None else None
filter_time_left: int | None
388    @property
389    def filter_time_left(self) -> int | None:
390        return FILTER_REPLACE_TIME - self.filter_work_time if self.filter_work_time is not None else None
sensor_time_left: int | None
392    @property
393    def sensor_time_left(self) -> int | None:
394        return SENSOR_DIRTY_REPLACE_TIME - self.sensor_dirty_time if self.sensor_dirty_time is not None else None
strainer_time_left: int | None
396    @property
397    def strainer_time_left(self) -> int | None:
398        return STRAINER_REPLACE_TIME - self.strainer_work_times if self.strainer_work_times is not None else None
dust_collection_time_left: int | None
400    @property
401    def dust_collection_time_left(self) -> int | None:
402        return (
403            DUST_COLLECTION_REPLACE_TIME - self.dust_collection_work_times
404            if self.dust_collection_work_times is not None
405            else None
406        )
cleaning_brush_time_left: int | None
408    @property
409    def cleaning_brush_time_left(self) -> int | None:
410        return (
411            CLEANING_BRUSH_REPLACE_TIME - self.cleaning_brush_work_times
412            if self.cleaning_brush_work_times is not None
413            else None
414        )
mop_roller_time_left: int | None
416    @property
417    def mop_roller_time_left(self) -> int | None:
418        return MOP_ROLLER_REPLACE_TIME - self.moproller_work_time if self.moproller_work_time is not None else None
@dataclass
class MultiMapsListRoom(roborock.data.containers.RoborockBase):
424@dataclass
425class MultiMapsListRoom(RoborockBase):
426    id: int | None = None
427    tag: int | None = None
428    iot_name_id: str | None = None
429    iot_name: str | None = None
430
431    @property
432    def named_room_mapping(self) -> NamedRoomMapping | None:
433        """Returns a NamedRoomMapping object if valid."""
434        if self.id is None or self.iot_name_id is None:
435            return None
436        return NamedRoomMapping(
437            segment_id=self.id,
438            iot_id=self.iot_name_id,
439            raw_name=self.iot_name,
440        )
MultiMapsListRoom( id: int | None = None, tag: int | None = None, iot_name_id: str | None = None, iot_name: str | None = None)
id: int | None = None
tag: int | None = None
iot_name_id: str | None = None
iot_name: str | None = None
named_room_mapping: roborock.data.containers.NamedRoomMapping | None
431    @property
432    def named_room_mapping(self) -> NamedRoomMapping | None:
433        """Returns a NamedRoomMapping object if valid."""
434        if self.id is None or self.iot_name_id is None:
435            return None
436        return NamedRoomMapping(
437            segment_id=self.id,
438            iot_id=self.iot_name_id,
439            raw_name=self.iot_name,
440        )

Returns a NamedRoomMapping object if valid.

@dataclass
class MultiMapsListMapInfoBakMaps(roborock.data.containers.RoborockBase):
443@dataclass
444class MultiMapsListMapInfoBakMaps(RoborockBase):
445    mapflag: Any | None = None
446    add_time: Any | None = None
MultiMapsListMapInfoBakMaps( mapflag: typing.Any | None = None, add_time: typing.Any | None = None)
mapflag: typing.Any | None = None
add_time: typing.Any | None = None
@dataclass
class MultiMapsListMapInfo(roborock.data.containers.RoborockBase):
449@dataclass
450class MultiMapsListMapInfo(RoborockBase):
451    map_flag: int
452    name: str
453    add_time: Any | None = None
454    length: Any | None = None
455    bak_maps: list[MultiMapsListMapInfoBakMaps] | None = None
456    rooms: list[MultiMapsListRoom] | None = None
457
458    @property
459    def mapFlag(self) -> int:
460        """Alias for map_flag, returns the map flag as an integer."""
461        return self.map_flag
462
463    @property
464    def rooms_map(self) -> dict[int, NamedRoomMapping]:
465        """Returns a dictionary of room mappings by segment id."""
466        return {
467            room.id: mapping
468            for room in self.rooms or ()
469            if room.id is not None and (mapping := room.named_room_mapping) is not None
470        }
MultiMapsListMapInfo( map_flag: int, name: str, add_time: typing.Any | None = None, length: typing.Any | None = None, bak_maps: list[MultiMapsListMapInfoBakMaps] | None = None, rooms: list[MultiMapsListRoom] | None = None)
map_flag: int
name: str
add_time: typing.Any | None = None
length: typing.Any | None = None
bak_maps: list[MultiMapsListMapInfoBakMaps] | None = None
rooms: list[MultiMapsListRoom] | None = None
mapFlag: int
458    @property
459    def mapFlag(self) -> int:
460        """Alias for map_flag, returns the map flag as an integer."""
461        return self.map_flag

Alias for map_flag, returns the map flag as an integer.

rooms_map: dict[int, roborock.data.containers.NamedRoomMapping]
463    @property
464    def rooms_map(self) -> dict[int, NamedRoomMapping]:
465        """Returns a dictionary of room mappings by segment id."""
466        return {
467            room.id: mapping
468            for room in self.rooms or ()
469            if room.id is not None and (mapping := room.named_room_mapping) is not None
470        }

Returns a dictionary of room mappings by segment id.

@dataclass
class MultiMapsList(roborock.data.containers.RoborockBase):
473@dataclass
474class MultiMapsList(RoborockBase):
475    max_multi_map: int | None = None
476    max_bak_map: int | None = None
477    multi_map_count: int | None = None
478    map_info: list[MultiMapsListMapInfo] | None = None
MultiMapsList( max_multi_map: int | None = None, max_bak_map: int | None = None, multi_map_count: int | None = None, map_info: list[MultiMapsListMapInfo] | None = None)
max_multi_map: int | None = None
max_bak_map: int | None = None
multi_map_count: int | None = None
map_info: list[MultiMapsListMapInfo] | None = None
@dataclass
class SmartWashParams(roborock.data.containers.RoborockBase):
481@dataclass
482class SmartWashParams(RoborockBase):
483    smart_wash: int | None = None
484    wash_interval: int | None = None
SmartWashParams(smart_wash: int | None = None, wash_interval: int | None = None)
smart_wash: int | None = None
wash_interval: int | None = None
@dataclass
class MopDryerProfile(roborock.data.containers.RoborockBase):
487@dataclass
488class MopDryerProfile(RoborockBase):
489    """Dryer parameters for one state of the auto mop-drying setting."""
490
491    cliff_on: int | None = None
492    cliff_off: int | None = None
493    count: int | None = None
494    dry_time: int | None = None
495    """Drying duration in seconds. Only present in the ``on`` profile."""
496    dry_heating_film_time: int | None = None
497    """Heating element run time in seconds. Only present in the ``on`` profile."""

Dryer parameters for one state of the auto mop-drying setting.

MopDryerProfile( cliff_on: int | None = None, cliff_off: int | None = None, count: int | None = None, dry_time: int | None = None, dry_heating_film_time: int | None = None)
cliff_on: int | None = None
cliff_off: int | None = None
count: int | None = None
dry_time: int | None = None

Drying duration in seconds. Only present in the on profile.

dry_heating_film_time: int | None = None

Heating element run time in seconds. Only present in the on profile.

@dataclass
class MopDryerSetting(roborock.data.containers.RoborockBase):
500@dataclass
501class MopDryerSetting(RoborockBase):
502    """Auto mop-drying setting, as returned by APP_GET_DRYER_SETTING."""
503
504    status: int | None = None
505    on: MopDryerProfile | None = None
506    off: MopDryerProfile | None = None

Auto mop-drying setting, as returned by APP_GET_DRYER_SETTING.

MopDryerSetting( status: int | None = None, on: MopDryerProfile | None = None, off: MopDryerProfile | None = None)
status: int | None = None
on: MopDryerProfile | None = None
off: MopDryerProfile | None = None
@dataclass
class DustCollectionMode(roborock.data.containers.RoborockBase):
509@dataclass
510class DustCollectionMode(RoborockBase):
511    mode: RoborockDockDustCollectionModeCode | None = None
@dataclass
class WashTowelMode(roborock.data.containers.RoborockBase):
514@dataclass
515class WashTowelMode(RoborockBase):
516    wash_mode: WashTowelModes | None = None
WashTowelMode( wash_mode: roborock.data.v1.v1_clean_modes.WashTowelModes | None = None)
SEQ_TYPE_CLEAN_THEN_MOP = 1
@dataclass
class SegmentCleanMode(roborock.data.containers.RoborockBase):
522@dataclass
523class SegmentCleanMode(RoborockBase):
524    """A single room's clean mode override, as reported by the device.
525
526    `fan_power` / `water_box_mode` / `mop_mode` / `seq_type` are kept as raw
527    `int | None` wire codes rather than typed enums, matching `StatusV2`, so
528    model-specific values (e.g. water-slide codes like 222) round-trip
529    losslessly even for codes this library doesn't otherwise model.
530
531    `mop_power` and `custom_water_box_distance` are only reported by some
532    models (roller-mop / custom-water-flow devices); they are `None` on
533    models that don't report them.
534    """
535
536    segment: int
537    fan_power: int | None = None
538    water_box_mode: int | None = None
539    mop_mode: int | None = None
540    repeat: int | None = None
541    seq_type: int | None = None
542    mop_power: int | None = None
543    custom_water_box_distance: int | None = None
544
545    @property
546    def clean_then_mop(self) -> bool | None:
547        """Whether this room is vacuumed fully before it is mopped.
548
549        `None` means the device did not report a sequence type for this
550        room's override.
551        """
552        if self.seq_type is None:
553            return None
554        return self.seq_type == SEQ_TYPE_CLEAN_THEN_MOP

A single room's clean mode override, as reported by the device.

fan_power / water_box_mode / mop_mode / seq_type are kept as raw int | None wire codes rather than typed enums, matching StatusV2, so model-specific values (e.g. water-slide codes like 222) round-trip losslessly even for codes this library doesn't otherwise model.

mop_power and custom_water_box_distance are only reported by some models (roller-mop / custom-water-flow devices); they are None on models that don't report them.

SegmentCleanMode( segment: int, fan_power: int | None = None, water_box_mode: int | None = None, mop_mode: int | None = None, repeat: int | None = None, seq_type: int | None = None, mop_power: int | None = None, custom_water_box_distance: int | None = None)
segment: int
fan_power: int | None = None
water_box_mode: int | None = None
mop_mode: int | None = None
repeat: int | None = None
seq_type: int | None = None
mop_power: int | None = None
custom_water_box_distance: int | None = None
clean_then_mop: bool | None
545    @property
546    def clean_then_mop(self) -> bool | None:
547        """Whether this room is vacuumed fully before it is mopped.
548
549        `None` means the device did not report a sequence type for this
550        room's override.
551        """
552        if self.seq_type is None:
553            return None
554        return self.seq_type == SEQ_TYPE_CLEAN_THEN_MOP

Whether this room is vacuumed fully before it is mopped.

None means the device did not report a sequence type for this room's override.

@dataclass
class CustomizeCleanModes(roborock.data.containers.RoborockBase):
557@dataclass
558class CustomizeCleanModes(RoborockBase):
559    """Collection of per-room clean mode overrides."""
560
561    modes: list[SegmentCleanMode] | None = None
562
563    @property
564    def by_segment(self) -> dict[int, SegmentCleanMode]:
565        """Index the overrides by segment id."""
566        return {mode.segment: mode for mode in self.modes or []}

Collection of per-room clean mode overrides.

CustomizeCleanModes( modes: list[SegmentCleanMode] | None = None)
modes: list[SegmentCleanMode] | None = None
by_segment: dict[int, SegmentCleanMode]
563    @property
564    def by_segment(self) -> dict[int, SegmentCleanMode]:
565        """Index the overrides by segment id."""
566        return {mode.segment: mode for mode in self.modes or []}

Index the overrides by segment id.

@dataclass
class NetworkInfo(roborock.data.containers.RoborockBase):
569@dataclass
570class NetworkInfo(RoborockBase):
571    ip: str
572    ssid: str | None = None
573    mac: str | None = None
574    bssid: str | None = None
575    rssi: int | None = None
NetworkInfo( ip: str, ssid: str | None = None, mac: str | None = None, bssid: str | None = None, rssi: int | None = None)
ip: str
ssid: str | None = None
mac: str | None = None
bssid: str | None = None
rssi: int | None = None
@dataclass
class AppInitStatusLocalInfo(roborock.data.containers.RoborockBase):
578@dataclass
579class AppInitStatusLocalInfo(RoborockBase):
580    location: str
581    bom: str | None = None
582    featureset: int | None = None
583    language: str | None = None
584    logserver: str | None = None
585    wifiplan: str | None = None
586    timezone: str | None = None
587    name: str | None = None
AppInitStatusLocalInfo( location: str, bom: str | None = None, featureset: int | None = None, language: str | None = None, logserver: str | None = None, wifiplan: str | None = None, timezone: str | None = None, name: str | None = None)
location: str
bom: str | None = None
featureset: int | None = None
language: str | None = None
logserver: str | None = None
wifiplan: str | None = None
timezone: str | None = None
name: str | None = None
@dataclass
class AppInitStatus(roborock.data.containers.RoborockBase):
590@dataclass
591class AppInitStatus(RoborockBase):
592    local_info: AppInitStatusLocalInfo
593    feature_info: list[int]
594    new_feature_info: int = 0
595    new_feature_info_str: str = ""
596    new_feature_info_2: int | None = None
597    carriage_type: int | None = None
598    dsp_version: str | None = None
AppInitStatus( local_info: AppInitStatusLocalInfo, feature_info: list[int], new_feature_info: int = 0, new_feature_info_str: str = '', new_feature_info_2: int | None = None, carriage_type: int | None = None, dsp_version: str | None = None)
feature_info: list[int]
new_feature_info: int = 0
new_feature_info_str: str = ''
new_feature_info_2: int | None = None
carriage_type: int | None = None
dsp_version: str | None = None
@dataclass
class ChildLockStatus(roborock.data.containers.RoborockBase):
601@dataclass
602class ChildLockStatus(RoborockBase):
603    lock_status: int = 0
ChildLockStatus(lock_status: int = 0)
lock_status: int = 0
@dataclass
class FlowLedStatus(roborock.data.containers.RoborockBase):
606@dataclass
607class FlowLedStatus(RoborockBase):
608    status: int = 0
FlowLedStatus(status: int = 0)
status: int = 0
@dataclass
class LedStatus(roborock.data.containers.RoborockBase):
611@dataclass
612class LedStatus(RoborockBase):
613    status: int = 0
LedStatus(status: int = 0)
status: int = 0