roborock.data.containers

  1import dataclasses
  2import datetime
  3import inspect
  4import json
  5import logging
  6import re
  7import types
  8from dataclasses import asdict, dataclass, field
  9from enum import Enum
 10from functools import cached_property
 11from typing import Any, ClassVar, NamedTuple, get_args, get_origin
 12
 13from .code_mappings import (
 14    SHORT_MODEL_TO_ENUM,
 15    RoborockCategory,
 16    RoborockModeEnum,
 17    RoborockProductNickname,
 18)
 19
 20_LOGGER = logging.getLogger(__name__)
 21
 22
 23def _camelize(s: str):
 24    first, *others = s.split("_")
 25    if len(others) == 0:
 26        return s
 27    return "".join([first.lower(), *map(str.title, others)])
 28
 29
 30def _decamelize(s: str):
 31    # Split before uppercase letters not at the start, and before numbers
 32    s = re.sub(r"(?<=[a-z0-9])([A-Z])", r"_\1", s)
 33    s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s)  # Split acronyms followed by normal camelCase
 34    s = re.sub(r"([a-zA-Z])([0-9]+)", r"\1_\2", s)
 35    s = s.lower()
 36    # Temporary fix to avoid breaking any serialization.
 37    s = s.replace("base_64", "base64")
 38    return s
 39
 40
 41def _attr_repr(obj: Any) -> str:
 42    """Return a string representation of the object including specified attributes.
 43
 44    This reproduces the default repr behavior of dataclasses, but also includes
 45    properties. This must be called by the child class's __repr__ method since
 46    the parent RoborockBase class does not know about the child class's attributes.
 47    """
 48    # Reproduce default repr behavior
 49    parts = []
 50    for k in dir(obj):
 51        if k.startswith("_"):
 52            continue
 53        try:
 54            v = getattr(obj, k)
 55        except (RuntimeError, Exception):
 56            continue
 57        if callable(v):
 58            continue
 59        parts.append(f"{k}={v!r}")
 60    return f"{type(obj).__name__}({', '.join(parts)})"
 61
 62
 63@dataclass(repr=False)
 64class RoborockBase:
 65    """Base class for all Roborock data classes."""
 66
 67    _missing_logged: ClassVar[set[str]] = set()
 68
 69    @staticmethod
 70    def _convert_to_class_obj(class_type: type, value):
 71        if get_origin(class_type) is list:
 72            sub_type = get_args(class_type)[0]
 73            return [RoborockBase._convert_to_class_obj(sub_type, obj) for obj in value]
 74        if get_origin(class_type) is dict:
 75            key_type, value_type = get_args(class_type)
 76            if key_type is not None:
 77                return {key_type(k): RoborockBase._convert_to_class_obj(value_type, v) for k, v in value.items()}
 78            return {k: RoborockBase._convert_to_class_obj(value_type, v) for k, v in value.items()}
 79        if inspect.isclass(class_type):
 80            if issubclass(class_type, RoborockBase):
 81                return class_type.from_dict(value)
 82            if issubclass(class_type, RoborockModeEnum):
 83                return class_type.from_code(value)
 84        if class_type is Any or type(class_type) is str:
 85            return value
 86        return class_type(value)  # type: ignore[call-arg]
 87
 88    @classmethod
 89    def from_dict(cls, data: dict[str, Any]):
 90        """Create an instance of the class from a dictionary."""
 91        if not isinstance(data, dict):
 92            return None
 93        field_types = {field.name: field.type for field in dataclasses.fields(cls)}
 94        result: dict[str, Any] = {}
 95        for orig_key, value in data.items():
 96            key = _decamelize(orig_key)
 97            if (field_type := field_types.get(key)) is None:
 98                if (log_key := f"{cls.__name__}.{key}") not in RoborockBase._missing_logged:
 99                    _LOGGER.debug(
100                        "Key '%s' (decamelized: '%s') not found in %s fields, skipping",
101                        orig_key,
102                        key,
103                        cls.__name__,
104                    )
105                    RoborockBase._missing_logged.add(log_key)
106                continue
107            if value == "None" or value is None:
108                result[key] = None
109                continue
110            if isinstance(field_type, types.UnionType):
111                for subtype in get_args(field_type):
112                    if subtype is types.NoneType:
113                        continue
114                    try:
115                        result[key] = RoborockBase._convert_to_class_obj(subtype, value)
116                        break
117                    except Exception:
118                        _LOGGER.exception(f"Failed to convert {key} with value {value} to type {subtype}")
119                        continue
120            else:
121                try:
122                    result[key] = RoborockBase._convert_to_class_obj(field_type, value)
123                except Exception:
124                    _LOGGER.exception(f"Failed to convert {key} with value {value} to type {field_type}")
125                    continue
126
127        return cls(**result)
128
129    def as_dict(self) -> dict:
130        return asdict(
131            self,
132            dict_factory=lambda _fields: {
133                _camelize(key): value.value if isinstance(value, Enum) else value
134                for (key, value) in _fields
135                if value is not None
136            },
137        )
138
139
140@dataclass
141class RoborockBaseTimer(RoborockBase):
142    start_hour: int | None = None
143    start_minute: int | None = None
144    end_hour: int | None = None
145    end_minute: int | None = None
146    enabled: int | None = None
147
148    @property
149    def start_time(self) -> datetime.time | None:
150        return (
151            datetime.time(hour=self.start_hour, minute=self.start_minute)
152            if self.start_hour is not None and self.start_minute is not None
153            else None
154        )
155
156    @property
157    def end_time(self) -> datetime.time | None:
158        return (
159            datetime.time(hour=self.end_hour, minute=self.end_minute)
160            if self.end_hour is not None and self.end_minute is not None
161            else None
162        )
163
164    def as_list(self) -> list:
165        return [self.start_hour, self.start_minute, self.end_hour, self.end_minute]
166
167    def __repr__(self) -> str:
168        return _attr_repr(self)
169
170
171@dataclass
172class Reference(RoborockBase):
173    r: str | None = None
174    a: str | None = None
175    m: str | None = None
176    l: str | None = None
177
178
179@dataclass
180class RRiot(RoborockBase):
181    u: str
182    s: str
183    h: str
184    k: str
185    r: Reference
186
187
188@dataclass
189class UserData(RoborockBase):
190    rriot: RRiot
191    uid: int | None = None
192    tokentype: str | None = None
193    token: str | None = None
194    rruid: str | None = None
195    region: str | None = None
196    countrycode: str | None = None
197    country: str | None = None
198    nickname: str | None = None
199    tuya_device_state: int | None = None
200    avatarurl: str | None = None
201
202
203@dataclass
204class HomeDataProductSchema(RoborockBase):
205    id: Any | None = None
206    name: Any | None = None
207    code: Any | None = None
208    mode: Any | None = None
209    type: Any | None = None
210    product_property: Any | None = None
211    property: Any | None = None
212    desc: Any | None = None
213
214
215@dataclass
216class HomeDataProduct(RoborockBase):
217    id: str
218    name: str
219    model: str
220    category: RoborockCategory
221    code: str | None = None
222    icon_url: str | None = None
223    attribute: Any | None = None
224    capability: int | None = None
225    schema: list[HomeDataProductSchema] | None = None
226
227    @property
228    def product_nickname(self) -> RoborockProductNickname:
229        return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
230
231    def summary_info(self) -> str:
232        """Return a string with key product information for logging purposes."""
233        return f"{self.name} (model={self.model}, category={self.category})"
234
235
236@dataclass
237class HomeDataDevice(RoborockBase):
238    duid: str
239    name: str
240    local_key: str
241    product_id: str
242    fv: str | None = None
243    attribute: Any | None = None
244    active_time: int | None = None
245    runtime_env: Any | None = None
246    time_zone_id: str | None = None
247    icon_url: str | None = None
248    lon: Any | None = None
249    lat: Any | None = None
250    share: Any | None = None
251    share_time: Any | None = None
252    online: bool | None = None
253    pv: str | None = None
254    room_id: Any | None = None
255    tuya_uuid: Any | None = None
256    tuya_migrated: bool | None = None
257    extra: Any | None = None
258    sn: str | None = None
259    feature_set: str | None = None
260    new_feature_set: str | None = None
261    device_status: dict | None = None
262    silent_ota_switch: bool | None = None
263    setting: Any | None = None
264    f: bool | None = None
265    create_time: int | None = None
266    cid: str | None = None
267    share_type: Any | None = None
268    share_expired_time: int | None = None
269
270    def summary_info(self) -> str:
271        """Return a string with key device information for logging purposes."""
272        return f"{self.name} (pv={self.pv}, fv={self.fv}, online={self.online})"
273
274
275@dataclass
276class HomeDataRoom(RoborockBase):
277    id: int
278    name: str
279
280
281@dataclass
282class HomeDataScene(RoborockBase):
283    id: int
284    name: str
285
286
287@dataclass
288class HomeDataSchedule(RoborockBase):
289    id: int
290    cron: str
291    repeated: bool
292    enabled: bool
293    param: dict | None = None
294
295
296@dataclass
297class HomeData(RoborockBase):
298    id: int
299    name: str
300    products: list[HomeDataProduct] = field(default_factory=lambda: [])
301    devices: list[HomeDataDevice] = field(default_factory=lambda: [])
302    received_devices: list[HomeDataDevice] = field(default_factory=lambda: [])
303    lon: Any | None = None
304    lat: Any | None = None
305    geo_name: Any | None = None
306    rooms: list[HomeDataRoom] = field(default_factory=list)
307
308    def get_all_devices(self) -> list[HomeDataDevice]:
309        devices = []
310        if self.devices is not None:
311            devices += self.devices
312        if self.received_devices is not None:
313            devices += self.received_devices
314        return devices
315
316    @cached_property
317    def product_map(self) -> dict[str, HomeDataProduct]:
318        """Returns a dictionary of product IDs to HomeDataProduct objects."""
319        return {product.id: product for product in self.products}
320
321    @cached_property
322    def device_products(self) -> dict[str, tuple[HomeDataDevice, HomeDataProduct]]:
323        """Returns a dictionary of device DUIDs to HomeDataDeviceProduct objects."""
324        product_map = self.product_map
325        return {
326            device.duid: (device, product)
327            for device in self.get_all_devices()
328            if (product := product_map.get(device.product_id)) is not None
329        }
330
331
332@dataclass
333class LoginData(RoborockBase):
334    user_data: UserData
335    email: str
336    home_data: HomeData | None = None
337
338
339@dataclass
340class DeviceData(RoborockBase):
341    device: HomeDataDevice
342    model: str
343    host: str | None = None
344
345    @property
346    def product_nickname(self) -> RoborockProductNickname:
347        return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
348
349    def __repr__(self) -> str:
350        return _attr_repr(self)
351
352
353@dataclass
354class RoomMapping(RoborockBase):
355    segment_id: int
356    iot_id: str
357
358
359@dataclass
360class NamedRoomMapping(RoomMapping):
361    """Dataclass representing a mapping of a room segment to a name.
362
363    The name information is not provided by the device directly, but is provided
364    from the HomeData based on the iot_id from the room.
365    """
366
367    name: str
368    """The human-readable name of the room, if available."""
369
370
371@dataclass
372class CombinedMapInfo(RoborockBase):
373    """Data structure for caching home information.
374
375    This is not provided directly by the API, but is a combination of map data
376    and room data to provide a more useful structure.
377    """
378
379    map_flag: int
380    """The map identifier."""
381
382    name: str
383    """The name of the map from MultiMapsListMapInfo."""
384
385    rooms: list[NamedRoomMapping]
386    """The list of rooms in the map."""
387
388
389@dataclass
390class BroadcastMessage(RoborockBase):
391    duid: str
392    ip: str
393    version: bytes
394
395
396class ServerTimer(NamedTuple):
397    id: str
398    status: str
399    dontknow: int
400
401
402@dataclass
403class RoborockProductStateValue(RoborockBase):
404    value: list
405    desc: dict
406
407
408@dataclass
409class RoborockProductState(RoborockBase):
410    dps: int
411    desc: dict
412    value: list[RoborockProductStateValue]
413
414
415@dataclass
416class RoborockProductSpec(RoborockBase):
417    state: RoborockProductState
418    battery: dict | None = None
419    dry_countdown: dict | None = None
420    extra: dict | None = None
421    offpeak: dict | None = None
422    countdown: dict | None = None
423    mode: dict | None = None
424    ota_nfo: dict | None = None
425    pause: dict | None = None
426    program: dict | None = None
427    shutdown: dict | None = None
428    washing_left: dict | None = None
429
430
431@dataclass
432class RoborockProduct(RoborockBase):
433    id: int | None = None
434    name: str | None = None
435    model: str | None = None
436    packagename: str | None = None
437    ssid: str | None = None
438    picurl: str | None = None
439    cardpicurl: str | None = None
440    mediumCardpicurl: str | None = None
441    resetwifipicurl: str | None = None
442    configPicUrl: str | None = None
443    pluginPicUrl: str | None = None
444    resetwifitext: dict | None = None
445    tuyaid: str | None = None
446    status: int | None = None
447    rriotid: str | None = None
448    pictures: list | None = None
449    ncMode: str | None = None
450    scope: str | None = None
451    product_tags: list | None = None
452    agreements: list | None = None
453    cardspec: str | None = None
454    plugin_pic_url: str | None = None
455
456    @property
457    def product_nickname(self) -> RoborockProductNickname | None:
458        if self.cardspec:
459            return RoborockProductSpec.from_dict(json.loads(self.cardspec).get("data"))
460        return None
461
462    def __repr__(self) -> str:
463        return _attr_repr(self)
464
465
466@dataclass
467class RoborockProductCategory(RoborockBase):
468    id: int
469    display_name: str
470    icon_url: str
471
472
473@dataclass
474class RoborockCategoryDetail(RoborockBase):
475    category: RoborockProductCategory
476    product_list: list[RoborockProduct]
477
478
479@dataclass
480class ProductResponse(RoborockBase):
481    category_detail_list: list[RoborockCategoryDetail]
@dataclass(repr=False)
class RoborockBase:
 64@dataclass(repr=False)
 65class RoborockBase:
 66    """Base class for all Roborock data classes."""
 67
 68    _missing_logged: ClassVar[set[str]] = set()
 69
 70    @staticmethod
 71    def _convert_to_class_obj(class_type: type, value):
 72        if get_origin(class_type) is list:
 73            sub_type = get_args(class_type)[0]
 74            return [RoborockBase._convert_to_class_obj(sub_type, obj) for obj in value]
 75        if get_origin(class_type) is dict:
 76            key_type, value_type = get_args(class_type)
 77            if key_type is not None:
 78                return {key_type(k): RoborockBase._convert_to_class_obj(value_type, v) for k, v in value.items()}
 79            return {k: RoborockBase._convert_to_class_obj(value_type, v) for k, v in value.items()}
 80        if inspect.isclass(class_type):
 81            if issubclass(class_type, RoborockBase):
 82                return class_type.from_dict(value)
 83            if issubclass(class_type, RoborockModeEnum):
 84                return class_type.from_code(value)
 85        if class_type is Any or type(class_type) is str:
 86            return value
 87        return class_type(value)  # type: ignore[call-arg]
 88
 89    @classmethod
 90    def from_dict(cls, data: dict[str, Any]):
 91        """Create an instance of the class from a dictionary."""
 92        if not isinstance(data, dict):
 93            return None
 94        field_types = {field.name: field.type for field in dataclasses.fields(cls)}
 95        result: dict[str, Any] = {}
 96        for orig_key, value in data.items():
 97            key = _decamelize(orig_key)
 98            if (field_type := field_types.get(key)) is None:
 99                if (log_key := f"{cls.__name__}.{key}") not in RoborockBase._missing_logged:
100                    _LOGGER.debug(
101                        "Key '%s' (decamelized: '%s') not found in %s fields, skipping",
102                        orig_key,
103                        key,
104                        cls.__name__,
105                    )
106                    RoborockBase._missing_logged.add(log_key)
107                continue
108            if value == "None" or value is None:
109                result[key] = None
110                continue
111            if isinstance(field_type, types.UnionType):
112                for subtype in get_args(field_type):
113                    if subtype is types.NoneType:
114                        continue
115                    try:
116                        result[key] = RoborockBase._convert_to_class_obj(subtype, value)
117                        break
118                    except Exception:
119                        _LOGGER.exception(f"Failed to convert {key} with value {value} to type {subtype}")
120                        continue
121            else:
122                try:
123                    result[key] = RoborockBase._convert_to_class_obj(field_type, value)
124                except Exception:
125                    _LOGGER.exception(f"Failed to convert {key} with value {value} to type {field_type}")
126                    continue
127
128        return cls(**result)
129
130    def as_dict(self) -> dict:
131        return asdict(
132            self,
133            dict_factory=lambda _fields: {
134                _camelize(key): value.value if isinstance(value, Enum) else value
135                for (key, value) in _fields
136                if value is not None
137            },
138        )

Base class for all Roborock data classes.

@classmethod
def from_dict(cls, data: dict[str, typing.Any]):
 89    @classmethod
 90    def from_dict(cls, data: dict[str, Any]):
 91        """Create an instance of the class from a dictionary."""
 92        if not isinstance(data, dict):
 93            return None
 94        field_types = {field.name: field.type for field in dataclasses.fields(cls)}
 95        result: dict[str, Any] = {}
 96        for orig_key, value in data.items():
 97            key = _decamelize(orig_key)
 98            if (field_type := field_types.get(key)) is None:
 99                if (log_key := f"{cls.__name__}.{key}") not in RoborockBase._missing_logged:
100                    _LOGGER.debug(
101                        "Key '%s' (decamelized: '%s') not found in %s fields, skipping",
102                        orig_key,
103                        key,
104                        cls.__name__,
105                    )
106                    RoborockBase._missing_logged.add(log_key)
107                continue
108            if value == "None" or value is None:
109                result[key] = None
110                continue
111            if isinstance(field_type, types.UnionType):
112                for subtype in get_args(field_type):
113                    if subtype is types.NoneType:
114                        continue
115                    try:
116                        result[key] = RoborockBase._convert_to_class_obj(subtype, value)
117                        break
118                    except Exception:
119                        _LOGGER.exception(f"Failed to convert {key} with value {value} to type {subtype}")
120                        continue
121            else:
122                try:
123                    result[key] = RoborockBase._convert_to_class_obj(field_type, value)
124                except Exception:
125                    _LOGGER.exception(f"Failed to convert {key} with value {value} to type {field_type}")
126                    continue
127
128        return cls(**result)

Create an instance of the class from a dictionary.

def as_dict(self) -> dict:
130    def as_dict(self) -> dict:
131        return asdict(
132            self,
133            dict_factory=lambda _fields: {
134                _camelize(key): value.value if isinstance(value, Enum) else value
135                for (key, value) in _fields
136                if value is not None
137            },
138        )
@dataclass
class RoborockBaseTimer(RoborockBase):
141@dataclass
142class RoborockBaseTimer(RoborockBase):
143    start_hour: int | None = None
144    start_minute: int | None = None
145    end_hour: int | None = None
146    end_minute: int | None = None
147    enabled: int | None = None
148
149    @property
150    def start_time(self) -> datetime.time | None:
151        return (
152            datetime.time(hour=self.start_hour, minute=self.start_minute)
153            if self.start_hour is not None and self.start_minute is not None
154            else None
155        )
156
157    @property
158    def end_time(self) -> datetime.time | None:
159        return (
160            datetime.time(hour=self.end_hour, minute=self.end_minute)
161            if self.end_hour is not None and self.end_minute is not None
162            else None
163        )
164
165    def as_list(self) -> list:
166        return [self.start_hour, self.start_minute, self.end_hour, self.end_minute]
167
168    def __repr__(self) -> str:
169        return _attr_repr(self)
RoborockBaseTimer( start_hour: int | None = None, start_minute: int | None = None, end_hour: int | None = None, end_minute: int | None = None, enabled: int | None = None)
start_hour: int | None = None
start_minute: int | None = None
end_hour: int | None = None
end_minute: int | None = None
enabled: int | None = None
start_time: datetime.time | None
149    @property
150    def start_time(self) -> datetime.time | None:
151        return (
152            datetime.time(hour=self.start_hour, minute=self.start_minute)
153            if self.start_hour is not None and self.start_minute is not None
154            else None
155        )
end_time: datetime.time | None
157    @property
158    def end_time(self) -> datetime.time | None:
159        return (
160            datetime.time(hour=self.end_hour, minute=self.end_minute)
161            if self.end_hour is not None and self.end_minute is not None
162            else None
163        )
def as_list(self) -> list:
165    def as_list(self) -> list:
166        return [self.start_hour, self.start_minute, self.end_hour, self.end_minute]
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class Reference(RoborockBase):
172@dataclass
173class Reference(RoborockBase):
174    r: str | None = None
175    a: str | None = None
176    m: str | None = None
177    l: str | None = None
Reference( r: str | None = None, a: str | None = None, m: str | None = None, l: str | None = None)
r: str | None = None
a: str | None = None
m: str | None = None
l: str | None = None
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class RRiot(RoborockBase):
180@dataclass
181class RRiot(RoborockBase):
182    u: str
183    s: str
184    h: str
185    k: str
186    r: Reference
RRiot( u: str, s: str, h: str, k: str, r: Reference)
u: str
s: str
h: str
k: str
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class UserData(RoborockBase):
189@dataclass
190class UserData(RoborockBase):
191    rriot: RRiot
192    uid: int | None = None
193    tokentype: str | None = None
194    token: str | None = None
195    rruid: str | None = None
196    region: str | None = None
197    countrycode: str | None = None
198    country: str | None = None
199    nickname: str | None = None
200    tuya_device_state: int | None = None
201    avatarurl: str | None = None
UserData( rriot: RRiot, uid: int | None = None, tokentype: str | None = None, token: str | None = None, rruid: str | None = None, region: str | None = None, countrycode: str | None = None, country: str | None = None, nickname: str | None = None, tuya_device_state: int | None = None, avatarurl: str | None = None)
rriot: RRiot
uid: int | None = None
tokentype: str | None = None
token: str | None = None
rruid: str | None = None
region: str | None = None
countrycode: str | None = None
country: str | None = None
nickname: str | None = None
tuya_device_state: int | None = None
avatarurl: str | None = None
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class HomeDataProductSchema(RoborockBase):
204@dataclass
205class HomeDataProductSchema(RoborockBase):
206    id: Any | None = None
207    name: Any | None = None
208    code: Any | None = None
209    mode: Any | None = None
210    type: Any | None = None
211    product_property: Any | None = None
212    property: Any | None = None
213    desc: Any | None = None
HomeDataProductSchema( id: typing.Any | None = None, name: typing.Any | None = None, code: typing.Any | None = None, mode: typing.Any | None = None, type: typing.Any | None = None, product_property: typing.Any | None = None, property: typing.Any | None = None, desc: typing.Any | None = None)
id: typing.Any | None = None
name: typing.Any | None = None
code: typing.Any | None = None
mode: typing.Any | None = None
type: typing.Any | None = None
product_property: typing.Any | None = None
property: typing.Any | None = None
desc: typing.Any | None = None
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class HomeDataProduct(RoborockBase):
216@dataclass
217class HomeDataProduct(RoborockBase):
218    id: str
219    name: str
220    model: str
221    category: RoborockCategory
222    code: str | None = None
223    icon_url: str | None = None
224    attribute: Any | None = None
225    capability: int | None = None
226    schema: list[HomeDataProductSchema] | None = None
227
228    @property
229    def product_nickname(self) -> RoborockProductNickname:
230        return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
231
232    def summary_info(self) -> str:
233        """Return a string with key product information for logging purposes."""
234        return f"{self.name} (model={self.model}, category={self.category})"
HomeDataProduct( id: str, name: str, model: str, category: roborock.data.code_mappings.RoborockCategory, code: str | None = None, icon_url: str | None = None, attribute: typing.Any | None = None, capability: int | None = None, schema: list[HomeDataProductSchema] | None = None)
id: str
name: str
model: str
code: str | None = None
icon_url: str | None = None
attribute: typing.Any | None = None
capability: int | None = None
schema: list[HomeDataProductSchema] | None = None
228    @property
229    def product_nickname(self) -> RoborockProductNickname:
230        return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
def summary_info(self) -> str:
232    def summary_info(self) -> str:
233        """Return a string with key product information for logging purposes."""
234        return f"{self.name} (model={self.model}, category={self.category})"

Return a string with key product information for logging purposes.

Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class HomeDataDevice(RoborockBase):
237@dataclass
238class HomeDataDevice(RoborockBase):
239    duid: str
240    name: str
241    local_key: str
242    product_id: str
243    fv: str | None = None
244    attribute: Any | None = None
245    active_time: int | None = None
246    runtime_env: Any | None = None
247    time_zone_id: str | None = None
248    icon_url: str | None = None
249    lon: Any | None = None
250    lat: Any | None = None
251    share: Any | None = None
252    share_time: Any | None = None
253    online: bool | None = None
254    pv: str | None = None
255    room_id: Any | None = None
256    tuya_uuid: Any | None = None
257    tuya_migrated: bool | None = None
258    extra: Any | None = None
259    sn: str | None = None
260    feature_set: str | None = None
261    new_feature_set: str | None = None
262    device_status: dict | None = None
263    silent_ota_switch: bool | None = None
264    setting: Any | None = None
265    f: bool | None = None
266    create_time: int | None = None
267    cid: str | None = None
268    share_type: Any | None = None
269    share_expired_time: int | None = None
270
271    def summary_info(self) -> str:
272        """Return a string with key device information for logging purposes."""
273        return f"{self.name} (pv={self.pv}, fv={self.fv}, online={self.online})"
HomeDataDevice( duid: str, name: str, local_key: str, product_id: str, fv: str | None = None, attribute: typing.Any | None = None, active_time: int | None = None, runtime_env: typing.Any | None = None, time_zone_id: str | None = None, icon_url: str | None = None, lon: typing.Any | None = None, lat: typing.Any | None = None, share: typing.Any | None = None, share_time: typing.Any | None = None, online: bool | None = None, pv: str | None = None, room_id: typing.Any | None = None, tuya_uuid: typing.Any | None = None, tuya_migrated: bool | None = None, extra: typing.Any | None = None, sn: str | None = None, feature_set: str | None = None, new_feature_set: str | None = None, device_status: dict | None = None, silent_ota_switch: bool | None = None, setting: typing.Any | None = None, f: bool | None = None, create_time: int | None = None, cid: str | None = None, share_type: typing.Any | None = None, share_expired_time: int | None = None)
duid: str
name: str
local_key: str
product_id: str
fv: str | None = None
attribute: typing.Any | None = None
active_time: int | None = None
runtime_env: typing.Any | None = None
time_zone_id: str | None = None
icon_url: str | None = None
lon: typing.Any | None = None
lat: typing.Any | None = None
share: typing.Any | None = None
share_time: typing.Any | None = None
online: bool | None = None
pv: str | None = None
room_id: typing.Any | None = None
tuya_uuid: typing.Any | None = None
tuya_migrated: bool | None = None
extra: typing.Any | None = None
sn: str | None = None
feature_set: str | None = None
new_feature_set: str | None = None
device_status: dict | None = None
silent_ota_switch: bool | None = None
setting: typing.Any | None = None
f: bool | None = None
create_time: int | None = None
cid: str | None = None
share_type: typing.Any | None = None
share_expired_time: int | None = None
def summary_info(self) -> str:
271    def summary_info(self) -> str:
272        """Return a string with key device information for logging purposes."""
273        return f"{self.name} (pv={self.pv}, fv={self.fv}, online={self.online})"

Return a string with key device information for logging purposes.

Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class HomeDataRoom(RoborockBase):
276@dataclass
277class HomeDataRoom(RoborockBase):
278    id: int
279    name: str
HomeDataRoom(id: int, name: str)
id: int
name: str
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class HomeDataScene(RoborockBase):
282@dataclass
283class HomeDataScene(RoborockBase):
284    id: int
285    name: str
HomeDataScene(id: int, name: str)
id: int
name: str
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class HomeDataSchedule(RoborockBase):
288@dataclass
289class HomeDataSchedule(RoborockBase):
290    id: int
291    cron: str
292    repeated: bool
293    enabled: bool
294    param: dict | None = None
HomeDataSchedule( id: int, cron: str, repeated: bool, enabled: bool, param: dict | None = None)
id: int
cron: str
repeated: bool
enabled: bool
param: dict | None = None
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class HomeData(RoborockBase):
297@dataclass
298class HomeData(RoborockBase):
299    id: int
300    name: str
301    products: list[HomeDataProduct] = field(default_factory=lambda: [])
302    devices: list[HomeDataDevice] = field(default_factory=lambda: [])
303    received_devices: list[HomeDataDevice] = field(default_factory=lambda: [])
304    lon: Any | None = None
305    lat: Any | None = None
306    geo_name: Any | None = None
307    rooms: list[HomeDataRoom] = field(default_factory=list)
308
309    def get_all_devices(self) -> list[HomeDataDevice]:
310        devices = []
311        if self.devices is not None:
312            devices += self.devices
313        if self.received_devices is not None:
314            devices += self.received_devices
315        return devices
316
317    @cached_property
318    def product_map(self) -> dict[str, HomeDataProduct]:
319        """Returns a dictionary of product IDs to HomeDataProduct objects."""
320        return {product.id: product for product in self.products}
321
322    @cached_property
323    def device_products(self) -> dict[str, tuple[HomeDataDevice, HomeDataProduct]]:
324        """Returns a dictionary of device DUIDs to HomeDataDeviceProduct objects."""
325        product_map = self.product_map
326        return {
327            device.duid: (device, product)
328            for device in self.get_all_devices()
329            if (product := product_map.get(device.product_id)) is not None
330        }
HomeData( id: int, name: str, products: list[HomeDataProduct] = <factory>, devices: list[HomeDataDevice] = <factory>, received_devices: list[HomeDataDevice] = <factory>, lon: typing.Any | None = None, lat: typing.Any | None = None, geo_name: typing.Any | None = None, rooms: list[HomeDataRoom] = <factory>)
id: int
name: str
products: list[HomeDataProduct]
devices: list[HomeDataDevice]
received_devices: list[HomeDataDevice]
lon: typing.Any | None = None
lat: typing.Any | None = None
geo_name: typing.Any | None = None
rooms: list[HomeDataRoom]
def get_all_devices(self) -> list[HomeDataDevice]:
309    def get_all_devices(self) -> list[HomeDataDevice]:
310        devices = []
311        if self.devices is not None:
312            devices += self.devices
313        if self.received_devices is not None:
314            devices += self.received_devices
315        return devices
product_map: dict[str, HomeDataProduct]
317    @cached_property
318    def product_map(self) -> dict[str, HomeDataProduct]:
319        """Returns a dictionary of product IDs to HomeDataProduct objects."""
320        return {product.id: product for product in self.products}

Returns a dictionary of product IDs to HomeDataProduct objects.

device_products: dict[str, tuple[HomeDataDevice, HomeDataProduct]]
322    @cached_property
323    def device_products(self) -> dict[str, tuple[HomeDataDevice, HomeDataProduct]]:
324        """Returns a dictionary of device DUIDs to HomeDataDeviceProduct objects."""
325        product_map = self.product_map
326        return {
327            device.duid: (device, product)
328            for device in self.get_all_devices()
329            if (product := product_map.get(device.product_id)) is not None
330        }

Returns a dictionary of device DUIDs to HomeDataDeviceProduct objects.

Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class LoginData(RoborockBase):
333@dataclass
334class LoginData(RoborockBase):
335    user_data: UserData
336    email: str
337    home_data: HomeData | None = None
LoginData( user_data: UserData, email: str, home_data: HomeData | None = None)
user_data: UserData
email: str
home_data: HomeData | None = None
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class DeviceData(RoborockBase):
340@dataclass
341class DeviceData(RoborockBase):
342    device: HomeDataDevice
343    model: str
344    host: str | None = None
345
346    @property
347    def product_nickname(self) -> RoborockProductNickname:
348        return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
349
350    def __repr__(self) -> str:
351        return _attr_repr(self)
DeviceData( device: HomeDataDevice, model: str, host: str | None = None)
device: HomeDataDevice
model: str
host: str | None = None
346    @property
347    def product_nickname(self) -> RoborockProductNickname:
348        return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class RoomMapping(RoborockBase):
354@dataclass
355class RoomMapping(RoborockBase):
356    segment_id: int
357    iot_id: str
RoomMapping(segment_id: int, iot_id: str)
segment_id: int
iot_id: str
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class NamedRoomMapping(RoomMapping):
360@dataclass
361class NamedRoomMapping(RoomMapping):
362    """Dataclass representing a mapping of a room segment to a name.
363
364    The name information is not provided by the device directly, but is provided
365    from the HomeData based on the iot_id from the room.
366    """
367
368    name: str
369    """The human-readable name of the room, if available."""

Dataclass representing a mapping of a room segment to a name.

The name information is not provided by the device directly, but is provided from the HomeData based on the iot_id from the room.

NamedRoomMapping(segment_id: int, iot_id: str, name: str)
name: str

The human-readable name of the room, if available.

@dataclass
class CombinedMapInfo(RoborockBase):
372@dataclass
373class CombinedMapInfo(RoborockBase):
374    """Data structure for caching home information.
375
376    This is not provided directly by the API, but is a combination of map data
377    and room data to provide a more useful structure.
378    """
379
380    map_flag: int
381    """The map identifier."""
382
383    name: str
384    """The name of the map from MultiMapsListMapInfo."""
385
386    rooms: list[NamedRoomMapping]
387    """The list of rooms in the map."""

Data structure for caching home information.

This is not provided directly by the API, but is a combination of map data and room data to provide a more useful structure.

CombinedMapInfo( map_flag: int, name: str, rooms: list[NamedRoomMapping])
map_flag: int

The map identifier.

name: str

The name of the map from MultiMapsListMapInfo.

rooms: list[NamedRoomMapping]

The list of rooms in the map.

Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class BroadcastMessage(RoborockBase):
390@dataclass
391class BroadcastMessage(RoborockBase):
392    duid: str
393    ip: str
394    version: bytes
BroadcastMessage(duid: str, ip: str, version: bytes)
duid: str
ip: str
version: bytes
Inherited Members
RoborockBase
from_dict
as_dict
class ServerTimer(typing.NamedTuple):
397class ServerTimer(NamedTuple):
398    id: str
399    status: str
400    dontknow: int

ServerTimer(id, status, dontknow)

ServerTimer(id: str, status: str, dontknow: int)

Create new instance of ServerTimer(id, status, dontknow)

id: str

Alias for field number 0

status: str

Alias for field number 1

dontknow: int

Alias for field number 2

@dataclass
class RoborockProductStateValue(RoborockBase):
403@dataclass
404class RoborockProductStateValue(RoborockBase):
405    value: list
406    desc: dict
RoborockProductStateValue(value: list, desc: dict)
value: list
desc: dict
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class RoborockProductState(RoborockBase):
409@dataclass
410class RoborockProductState(RoborockBase):
411    dps: int
412    desc: dict
413    value: list[RoborockProductStateValue]
RoborockProductState( dps: int, desc: dict, value: list[RoborockProductStateValue])
dps: int
desc: dict
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class RoborockProductSpec(RoborockBase):
416@dataclass
417class RoborockProductSpec(RoborockBase):
418    state: RoborockProductState
419    battery: dict | None = None
420    dry_countdown: dict | None = None
421    extra: dict | None = None
422    offpeak: dict | None = None
423    countdown: dict | None = None
424    mode: dict | None = None
425    ota_nfo: dict | None = None
426    pause: dict | None = None
427    program: dict | None = None
428    shutdown: dict | None = None
429    washing_left: dict | None = None
RoborockProductSpec( state: RoborockProductState, battery: dict | None = None, dry_countdown: dict | None = None, extra: dict | None = None, offpeak: dict | None = None, countdown: dict | None = None, mode: dict | None = None, ota_nfo: dict | None = None, pause: dict | None = None, program: dict | None = None, shutdown: dict | None = None, washing_left: dict | None = None)
battery: dict | None = None
dry_countdown: dict | None = None
extra: dict | None = None
offpeak: dict | None = None
countdown: dict | None = None
mode: dict | None = None
ota_nfo: dict | None = None
pause: dict | None = None
program: dict | None = None
shutdown: dict | None = None
washing_left: dict | None = None
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class RoborockProduct(RoborockBase):
432@dataclass
433class RoborockProduct(RoborockBase):
434    id: int | None = None
435    name: str | None = None
436    model: str | None = None
437    packagename: str | None = None
438    ssid: str | None = None
439    picurl: str | None = None
440    cardpicurl: str | None = None
441    mediumCardpicurl: str | None = None
442    resetwifipicurl: str | None = None
443    configPicUrl: str | None = None
444    pluginPicUrl: str | None = None
445    resetwifitext: dict | None = None
446    tuyaid: str | None = None
447    status: int | None = None
448    rriotid: str | None = None
449    pictures: list | None = None
450    ncMode: str | None = None
451    scope: str | None = None
452    product_tags: list | None = None
453    agreements: list | None = None
454    cardspec: str | None = None
455    plugin_pic_url: str | None = None
456
457    @property
458    def product_nickname(self) -> RoborockProductNickname | None:
459        if self.cardspec:
460            return RoborockProductSpec.from_dict(json.loads(self.cardspec).get("data"))
461        return None
462
463    def __repr__(self) -> str:
464        return _attr_repr(self)
RoborockProduct( id: int | None = None, name: str | None = None, model: str | None = None, packagename: str | None = None, ssid: str | None = None, picurl: str | None = None, cardpicurl: str | None = None, mediumCardpicurl: str | None = None, resetwifipicurl: str | None = None, configPicUrl: str | None = None, pluginPicUrl: str | None = None, resetwifitext: dict | None = None, tuyaid: str | None = None, status: int | None = None, rriotid: str | None = None, pictures: list | None = None, ncMode: str | None = None, scope: str | None = None, product_tags: list | None = None, agreements: list | None = None, cardspec: str | None = None, plugin_pic_url: str | None = None)
id: int | None = None
name: str | None = None
model: str | None = None
packagename: str | None = None
ssid: str | None = None
picurl: str | None = None
cardpicurl: str | None = None
mediumCardpicurl: str | None = None
resetwifipicurl: str | None = None
configPicUrl: str | None = None
pluginPicUrl: str | None = None
resetwifitext: dict | None = None
tuyaid: str | None = None
status: int | None = None
rriotid: str | None = None
pictures: list | None = None
ncMode: str | None = None
scope: str | None = None
product_tags: list | None = None
agreements: list | None = None
cardspec: str | None = None
plugin_pic_url: str | None = None
product_nickname: roborock.data.code_mappings.RoborockProductNickname | None
457    @property
458    def product_nickname(self) -> RoborockProductNickname | None:
459        if self.cardspec:
460            return RoborockProductSpec.from_dict(json.loads(self.cardspec).get("data"))
461        return None
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class RoborockProductCategory(RoborockBase):
467@dataclass
468class RoborockProductCategory(RoborockBase):
469    id: int
470    display_name: str
471    icon_url: str
RoborockProductCategory(id: int, display_name: str, icon_url: str)
id: int
display_name: str
icon_url: str
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class RoborockCategoryDetail(RoborockBase):
474@dataclass
475class RoborockCategoryDetail(RoborockBase):
476    category: RoborockProductCategory
477    product_list: list[RoborockProduct]
RoborockCategoryDetail( category: RoborockProductCategory, product_list: list[RoborockProduct])
product_list: list[RoborockProduct]
Inherited Members
RoborockBase
from_dict
as_dict
@dataclass
class ProductResponse(RoborockBase):
480@dataclass
481class ProductResponse(RoborockBase):
482    category_detail_list: list[RoborockCategoryDetail]
ProductResponse( category_detail_list: list[RoborockCategoryDetail])
category_detail_list: list[RoborockCategoryDetail]
Inherited Members
RoborockBase
from_dict
as_dict