roborock-cli 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- roborock_cli/__init__.py +3 -0
- roborock_cli/__main__.py +76 -0
- roborock_cli/_vendor/VERSION +6 -0
- roborock_cli/_vendor/__init__.py +0 -0
- roborock_cli/_vendor/roborock/__init__.py +27 -0
- roborock_cli/_vendor/roborock/broadcast_protocol.py +114 -0
- roborock_cli/_vendor/roborock/callbacks.py +130 -0
- roborock_cli/_vendor/roborock/cli.py +1338 -0
- roborock_cli/_vendor/roborock/const.py +84 -0
- roborock_cli/_vendor/roborock/data/__init__.py +9 -0
- roborock_cli/_vendor/roborock/data/b01_q10/__init__.py +2 -0
- roborock_cli/_vendor/roborock/data/b01_q10/b01_q10_code_mappings.py +213 -0
- roborock_cli/_vendor/roborock/data/b01_q10/b01_q10_containers.py +102 -0
- roborock_cli/_vendor/roborock/data/b01_q7/__init__.py +2 -0
- roborock_cli/_vendor/roborock/data/b01_q7/b01_q7_code_mappings.py +303 -0
- roborock_cli/_vendor/roborock/data/b01_q7/b01_q7_containers.py +302 -0
- roborock_cli/_vendor/roborock/data/code_mappings.py +198 -0
- roborock_cli/_vendor/roborock/data/containers.py +530 -0
- roborock_cli/_vendor/roborock/data/dyad/__init__.py +2 -0
- roborock_cli/_vendor/roborock/data/dyad/dyad_code_mappings.py +102 -0
- roborock_cli/_vendor/roborock/data/dyad/dyad_containers.py +28 -0
- roborock_cli/_vendor/roborock/data/v1/__init__.py +3 -0
- roborock_cli/_vendor/roborock/data/v1/v1_clean_modes.py +192 -0
- roborock_cli/_vendor/roborock/data/v1/v1_code_mappings.py +644 -0
- roborock_cli/_vendor/roborock/data/v1/v1_containers.py +800 -0
- roborock_cli/_vendor/roborock/data/zeo/__init__.py +2 -0
- roborock_cli/_vendor/roborock/data/zeo/zeo_code_mappings.py +138 -0
- roborock_cli/_vendor/roborock/data/zeo/zeo_containers.py +0 -0
- roborock_cli/_vendor/roborock/device_features.py +668 -0
- roborock_cli/_vendor/roborock/devices/README.md +41 -0
- roborock_cli/_vendor/roborock/devices/__init__.py +11 -0
- roborock_cli/_vendor/roborock/devices/cache.py +143 -0
- roborock_cli/_vendor/roborock/devices/device.py +240 -0
- roborock_cli/_vendor/roborock/devices/device_manager.py +269 -0
- roborock_cli/_vendor/roborock/devices/file_cache.py +79 -0
- roborock_cli/_vendor/roborock/devices/rpc/__init__.py +14 -0
- roborock_cli/_vendor/roborock/devices/rpc/a01_channel.py +94 -0
- roborock_cli/_vendor/roborock/devices/rpc/b01_q10_channel.py +57 -0
- roborock_cli/_vendor/roborock/devices/rpc/b01_q7_channel.py +101 -0
- roborock_cli/_vendor/roborock/devices/rpc/v1_channel.py +457 -0
- roborock_cli/_vendor/roborock/devices/traits/__init__.py +28 -0
- roborock_cli/_vendor/roborock/devices/traits/a01/__init__.py +191 -0
- roborock_cli/_vendor/roborock/devices/traits/b01/__init__.py +12 -0
- roborock_cli/_vendor/roborock/devices/traits/b01/q10/__init__.py +76 -0
- roborock_cli/_vendor/roborock/devices/traits/b01/q10/command.py +32 -0
- roborock_cli/_vendor/roborock/devices/traits/b01/q10/common.py +115 -0
- roborock_cli/_vendor/roborock/devices/traits/b01/q10/status.py +32 -0
- roborock_cli/_vendor/roborock/devices/traits/b01/q10/vacuum.py +81 -0
- roborock_cli/_vendor/roborock/devices/traits/b01/q7/__init__.py +136 -0
- roborock_cli/_vendor/roborock/devices/traits/b01/q7/clean_summary.py +75 -0
- roborock_cli/_vendor/roborock/devices/traits/traits_mixin.py +64 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/__init__.py +344 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/child_lock.py +29 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/clean_summary.py +83 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/command.py +38 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/common.py +172 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/consumeable.py +48 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/device_features.py +74 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/do_not_disturb.py +41 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/dust_collection_mode.py +13 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/flow_led_status.py +29 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/home.py +285 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/led_status.py +43 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/map_content.py +83 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/maps.py +80 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/network_info.py +55 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/rooms.py +105 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/routines.py +26 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/smart_wash_params.py +13 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/status.py +101 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/valley_electricity_timer.py +44 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/volume.py +27 -0
- roborock_cli/_vendor/roborock/devices/traits/v1/wash_towel_mode.py +13 -0
- roborock_cli/_vendor/roborock/devices/transport/__init__.py +8 -0
- roborock_cli/_vendor/roborock/devices/transport/channel.py +32 -0
- roborock_cli/_vendor/roborock/devices/transport/local_channel.py +295 -0
- roborock_cli/_vendor/roborock/devices/transport/mqtt_channel.py +118 -0
- roborock_cli/_vendor/roborock/diagnostics.py +166 -0
- roborock_cli/_vendor/roborock/exceptions.py +95 -0
- roborock_cli/_vendor/roborock/map/__init__.py +7 -0
- roborock_cli/_vendor/roborock/map/map_parser.py +123 -0
- roborock_cli/_vendor/roborock/mqtt/__init__.py +10 -0
- roborock_cli/_vendor/roborock/mqtt/health_manager.py +60 -0
- roborock_cli/_vendor/roborock/mqtt/roborock_session.py +463 -0
- roborock_cli/_vendor/roborock/mqtt/session.py +108 -0
- roborock_cli/_vendor/roborock/protocol.py +558 -0
- roborock_cli/_vendor/roborock/protocols/__init__.py +3 -0
- roborock_cli/_vendor/roborock/protocols/a01_protocol.py +74 -0
- roborock_cli/_vendor/roborock/protocols/b01_q10_protocol.py +87 -0
- roborock_cli/_vendor/roborock/protocols/b01_q7_protocol.py +81 -0
- roborock_cli/_vendor/roborock/protocols/v1_protocol.py +271 -0
- roborock_cli/_vendor/roborock/py.typed +0 -0
- roborock_cli/_vendor/roborock/roborock_message.py +246 -0
- roborock_cli/_vendor/roborock/roborock_typing.py +382 -0
- roborock_cli/_vendor/roborock/util.py +54 -0
- roborock_cli/_vendor/roborock/web_api.py +761 -0
- roborock_cli/cli.py +715 -0
- roborock_cli/connection.py +202 -0
- roborock_cli/helpers.py +71 -0
- roborock_cli/server.py +759 -0
- roborock_cli/setup_auth.py +92 -0
- roborock_cli-0.1.1.dist-info/METADATA +172 -0
- roborock_cli-0.1.1.dist-info/RECORD +106 -0
- roborock_cli-0.1.1.dist-info/WHEEL +4 -0
- roborock_cli-0.1.1.dist-info/entry_points.txt +2 -0
- roborock_cli-0.1.1.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import datetime
|
|
3
|
+
import inspect
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
import types
|
|
8
|
+
from dataclasses import asdict, dataclass, field
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from functools import cached_property
|
|
11
|
+
from typing import Any, ClassVar, NamedTuple, get_args, get_origin
|
|
12
|
+
|
|
13
|
+
from .code_mappings import (
|
|
14
|
+
SHORT_MODEL_TO_ENUM,
|
|
15
|
+
RoborockCategory,
|
|
16
|
+
RoborockModeEnum,
|
|
17
|
+
RoborockProductNickname,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
_LOGGER = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _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
|
+
|
|
30
|
+
def _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
|
+
|
|
41
|
+
def _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)
|
|
64
|
+
class 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
|
+
normalized_data: dict[str, Any] = {}
|
|
95
|
+
for orig_key, value in data.items():
|
|
96
|
+
key = _decamelize(orig_key)
|
|
97
|
+
if 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
|
+
normalized_data[key] = value
|
|
108
|
+
|
|
109
|
+
result = RoborockBase.convert_dict(field_types, normalized_data)
|
|
110
|
+
return cls(**result)
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def convert_dict(types_map: dict[Any, type], data: dict[Any, Any]) -> dict[Any, Any]:
|
|
114
|
+
"""Generic helper to convert a dictionary of values based on a schema map of types.
|
|
115
|
+
|
|
116
|
+
This is meant to be used by traits that use dataclass reflection similar to
|
|
117
|
+
`Roborock.from_dict` to merge in new data updates.
|
|
118
|
+
"""
|
|
119
|
+
result: dict[Any, Any] = {}
|
|
120
|
+
for key, value in data.items():
|
|
121
|
+
if key not in types_map:
|
|
122
|
+
continue
|
|
123
|
+
field_type = types_map[key]
|
|
124
|
+
if value == "None" or value is None:
|
|
125
|
+
result[key] = None
|
|
126
|
+
continue
|
|
127
|
+
if isinstance(field_type, types.UnionType):
|
|
128
|
+
for subtype in get_args(field_type):
|
|
129
|
+
if subtype is types.NoneType:
|
|
130
|
+
continue
|
|
131
|
+
try:
|
|
132
|
+
result[key] = RoborockBase._convert_to_class_obj(subtype, value)
|
|
133
|
+
break
|
|
134
|
+
except Exception:
|
|
135
|
+
_LOGGER.exception(f"Failed to convert {key} with value {value} to type {subtype}")
|
|
136
|
+
continue
|
|
137
|
+
else:
|
|
138
|
+
try:
|
|
139
|
+
result[key] = RoborockBase._convert_to_class_obj(field_type, value)
|
|
140
|
+
except Exception:
|
|
141
|
+
_LOGGER.exception(f"Failed to convert {key} with value {value} to type {field_type}")
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
return result
|
|
145
|
+
|
|
146
|
+
def as_dict(self) -> dict:
|
|
147
|
+
return asdict(
|
|
148
|
+
self,
|
|
149
|
+
dict_factory=lambda _fields: {
|
|
150
|
+
_camelize(key): value.value if isinstance(value, Enum) else value
|
|
151
|
+
for (key, value) in _fields
|
|
152
|
+
if value is not None
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class RoborockBaseTimer(RoborockBase):
|
|
159
|
+
start_hour: int | None = None
|
|
160
|
+
start_minute: int | None = None
|
|
161
|
+
end_hour: int | None = None
|
|
162
|
+
end_minute: int | None = None
|
|
163
|
+
enabled: int | None = None
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def start_time(self) -> datetime.time | None:
|
|
167
|
+
return (
|
|
168
|
+
datetime.time(hour=self.start_hour, minute=self.start_minute)
|
|
169
|
+
if self.start_hour is not None and self.start_minute is not None
|
|
170
|
+
else None
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def end_time(self) -> datetime.time | None:
|
|
175
|
+
return (
|
|
176
|
+
datetime.time(hour=self.end_hour, minute=self.end_minute)
|
|
177
|
+
if self.end_hour is not None and self.end_minute is not None
|
|
178
|
+
else None
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def as_list(self) -> list:
|
|
182
|
+
return [self.start_hour, self.start_minute, self.end_hour, self.end_minute]
|
|
183
|
+
|
|
184
|
+
def __repr__(self) -> str:
|
|
185
|
+
return _attr_repr(self)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass
|
|
189
|
+
class Reference(RoborockBase):
|
|
190
|
+
r: str | None = None
|
|
191
|
+
a: str | None = None
|
|
192
|
+
m: str | None = None
|
|
193
|
+
l: str | None = None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass
|
|
197
|
+
class RRiot(RoborockBase):
|
|
198
|
+
u: str
|
|
199
|
+
s: str
|
|
200
|
+
h: str
|
|
201
|
+
k: str
|
|
202
|
+
r: Reference
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
class UserData(RoborockBase):
|
|
207
|
+
rriot: RRiot
|
|
208
|
+
uid: int | None = None
|
|
209
|
+
tokentype: str | None = None
|
|
210
|
+
token: str | None = None
|
|
211
|
+
rruid: str | None = None
|
|
212
|
+
region: str | None = None
|
|
213
|
+
countrycode: str | None = None
|
|
214
|
+
country: str | None = None
|
|
215
|
+
nickname: str | None = None
|
|
216
|
+
tuya_device_state: int | None = None
|
|
217
|
+
avatarurl: str | None = None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@dataclass
|
|
221
|
+
class HomeDataProductSchema(RoborockBase):
|
|
222
|
+
id: Any | None = None
|
|
223
|
+
name: Any | None = None
|
|
224
|
+
code: Any | None = None
|
|
225
|
+
mode: Any | None = None
|
|
226
|
+
type: Any | None = None
|
|
227
|
+
product_property: Any | None = None
|
|
228
|
+
property: Any | None = None
|
|
229
|
+
desc: Any | None = None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@dataclass
|
|
233
|
+
class HomeDataProduct(RoborockBase):
|
|
234
|
+
id: str
|
|
235
|
+
name: str
|
|
236
|
+
model: str
|
|
237
|
+
category: RoborockCategory
|
|
238
|
+
code: str | None = None
|
|
239
|
+
icon_url: str | None = None
|
|
240
|
+
attribute: Any | None = None
|
|
241
|
+
capability: int | None = None
|
|
242
|
+
schema: list[HomeDataProductSchema] | None = None
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def product_nickname(self) -> RoborockProductNickname:
|
|
246
|
+
return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
|
|
247
|
+
|
|
248
|
+
def summary_info(self) -> str:
|
|
249
|
+
"""Return a string with key product information for logging purposes."""
|
|
250
|
+
return f"{self.name} (model={self.model}, category={self.category})"
|
|
251
|
+
|
|
252
|
+
@cached_property
|
|
253
|
+
def supported_schema_codes(self) -> set[str]:
|
|
254
|
+
"""Return a set of fields that are supported by the device."""
|
|
255
|
+
if self.schema is None:
|
|
256
|
+
return set()
|
|
257
|
+
return {schema.code for schema in self.schema if schema.code is not None}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@dataclass
|
|
261
|
+
class HomeDataDevice(RoborockBase):
|
|
262
|
+
duid: str
|
|
263
|
+
name: str
|
|
264
|
+
local_key: str
|
|
265
|
+
product_id: str
|
|
266
|
+
fv: str | None = None
|
|
267
|
+
attribute: Any | None = None
|
|
268
|
+
active_time: int | None = None
|
|
269
|
+
runtime_env: Any | None = None
|
|
270
|
+
time_zone_id: str | None = None
|
|
271
|
+
icon_url: str | None = None
|
|
272
|
+
lon: Any | None = None
|
|
273
|
+
lat: Any | None = None
|
|
274
|
+
share: Any | None = None
|
|
275
|
+
share_time: Any | None = None
|
|
276
|
+
online: bool | None = None
|
|
277
|
+
pv: str | None = None
|
|
278
|
+
room_id: Any | None = None
|
|
279
|
+
tuya_uuid: Any | None = None
|
|
280
|
+
tuya_migrated: bool | None = None
|
|
281
|
+
extra: Any | None = None
|
|
282
|
+
sn: str | None = None
|
|
283
|
+
feature_set: str | None = None
|
|
284
|
+
new_feature_set: str | None = None
|
|
285
|
+
device_status: dict | None = None
|
|
286
|
+
silent_ota_switch: bool | None = None
|
|
287
|
+
setting: Any | None = None
|
|
288
|
+
f: bool | None = None
|
|
289
|
+
create_time: int | None = None
|
|
290
|
+
cid: str | None = None
|
|
291
|
+
share_type: Any | None = None
|
|
292
|
+
share_expired_time: int | None = None
|
|
293
|
+
|
|
294
|
+
def summary_info(self) -> str:
|
|
295
|
+
"""Return a string with key device information for logging purposes."""
|
|
296
|
+
return f"{self.name} (pv={self.pv}, fv={self.fv}, online={self.online})"
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@dataclass
|
|
300
|
+
class HomeDataRoom(RoborockBase):
|
|
301
|
+
id: int
|
|
302
|
+
name: str
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def iot_id(self) -> str:
|
|
306
|
+
"""Return the room's ID as a string IOT ID."""
|
|
307
|
+
return str(self.id)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@dataclass
|
|
311
|
+
class HomeDataScene(RoborockBase):
|
|
312
|
+
id: int
|
|
313
|
+
name: str
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@dataclass
|
|
317
|
+
class HomeDataSchedule(RoborockBase):
|
|
318
|
+
id: int
|
|
319
|
+
cron: str
|
|
320
|
+
repeated: bool
|
|
321
|
+
enabled: bool
|
|
322
|
+
param: dict | None = None
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@dataclass
|
|
326
|
+
class HomeData(RoborockBase):
|
|
327
|
+
id: int
|
|
328
|
+
name: str
|
|
329
|
+
products: list[HomeDataProduct] = field(default_factory=lambda: [])
|
|
330
|
+
devices: list[HomeDataDevice] = field(default_factory=lambda: [])
|
|
331
|
+
received_devices: list[HomeDataDevice] = field(default_factory=lambda: [])
|
|
332
|
+
lon: Any | None = None
|
|
333
|
+
lat: Any | None = None
|
|
334
|
+
geo_name: Any | None = None
|
|
335
|
+
rooms: list[HomeDataRoom] = field(default_factory=list)
|
|
336
|
+
|
|
337
|
+
def get_all_devices(self) -> list[HomeDataDevice]:
|
|
338
|
+
devices = []
|
|
339
|
+
if self.devices is not None:
|
|
340
|
+
devices += self.devices
|
|
341
|
+
if self.received_devices is not None:
|
|
342
|
+
devices += self.received_devices
|
|
343
|
+
return devices
|
|
344
|
+
|
|
345
|
+
@cached_property
|
|
346
|
+
def product_map(self) -> dict[str, HomeDataProduct]:
|
|
347
|
+
"""Returns a dictionary of product IDs to HomeDataProduct objects."""
|
|
348
|
+
return {product.id: product for product in self.products}
|
|
349
|
+
|
|
350
|
+
@cached_property
|
|
351
|
+
def device_products(self) -> dict[str, tuple[HomeDataDevice, HomeDataProduct]]:
|
|
352
|
+
"""Returns a dictionary of device DUIDs to HomeDataDeviceProduct objects."""
|
|
353
|
+
product_map = self.product_map
|
|
354
|
+
return {
|
|
355
|
+
device.duid: (device, product)
|
|
356
|
+
for device in self.get_all_devices()
|
|
357
|
+
if (product := product_map.get(device.product_id)) is not None
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def rooms_map(self) -> dict[str, HomeDataRoom]:
|
|
362
|
+
"""Returns a dictionary of Room iot_id to rooms"""
|
|
363
|
+
return {room.iot_id: room for room in self.rooms}
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def rooms_name_map(self) -> dict[str, str]:
|
|
367
|
+
"""Returns a dictionary of Room iot_id to room names."""
|
|
368
|
+
return {room.iot_id: room.name for room in self.rooms}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@dataclass
|
|
372
|
+
class LoginData(RoborockBase):
|
|
373
|
+
user_data: UserData
|
|
374
|
+
email: str
|
|
375
|
+
home_data: HomeData | None = None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@dataclass
|
|
379
|
+
class DeviceData(RoborockBase):
|
|
380
|
+
device: HomeDataDevice
|
|
381
|
+
model: str
|
|
382
|
+
host: str | None = None
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def product_nickname(self) -> RoborockProductNickname:
|
|
386
|
+
return SHORT_MODEL_TO_ENUM.get(self.model.split(".")[-1], RoborockProductNickname.PEARLPLUS)
|
|
387
|
+
|
|
388
|
+
def __repr__(self) -> str:
|
|
389
|
+
return _attr_repr(self)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@dataclass
|
|
393
|
+
class RoomMapping(RoborockBase):
|
|
394
|
+
segment_id: int
|
|
395
|
+
iot_id: str
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@dataclass
|
|
399
|
+
class NamedRoomMapping(RoomMapping):
|
|
400
|
+
"""Dataclass representing a mapping of a room segment to a name.
|
|
401
|
+
|
|
402
|
+
The name information is not provided by the device directly, but is provided
|
|
403
|
+
from the HomeData based on the iot_id from the room.
|
|
404
|
+
"""
|
|
405
|
+
|
|
406
|
+
@property
|
|
407
|
+
def name(self) -> str:
|
|
408
|
+
"""The human-readable name of the room, or a default name if not available."""
|
|
409
|
+
return self.raw_name or f"Room {self.segment_id}"
|
|
410
|
+
|
|
411
|
+
raw_name: str | None = None
|
|
412
|
+
"""The raw name of the room, as provided by the device."""
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@dataclass
|
|
416
|
+
class CombinedMapInfo(RoborockBase):
|
|
417
|
+
"""Data structure for caching home information.
|
|
418
|
+
|
|
419
|
+
This is not provided directly by the API, but is a combination of map data
|
|
420
|
+
and room data to provide a more useful structure.
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
map_flag: int
|
|
424
|
+
"""The map identifier."""
|
|
425
|
+
|
|
426
|
+
name: str
|
|
427
|
+
"""The name of the map from MultiMapsListMapInfo."""
|
|
428
|
+
|
|
429
|
+
rooms: list[NamedRoomMapping]
|
|
430
|
+
"""The list of rooms in the map."""
|
|
431
|
+
|
|
432
|
+
@property
|
|
433
|
+
def rooms_map(self) -> dict[int, NamedRoomMapping]:
|
|
434
|
+
"""Returns a mapping of segment_id to NamedRoomMapping."""
|
|
435
|
+
return {room.segment_id: room for room in self.rooms}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@dataclass
|
|
439
|
+
class BroadcastMessage(RoborockBase):
|
|
440
|
+
duid: str
|
|
441
|
+
ip: str
|
|
442
|
+
version: bytes
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
class ServerTimer(NamedTuple):
|
|
446
|
+
id: str
|
|
447
|
+
status: str
|
|
448
|
+
dontknow: int
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@dataclass
|
|
452
|
+
class RoborockProductStateValue(RoborockBase):
|
|
453
|
+
value: list
|
|
454
|
+
desc: dict
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@dataclass
|
|
458
|
+
class RoborockProductState(RoborockBase):
|
|
459
|
+
dps: int
|
|
460
|
+
desc: dict
|
|
461
|
+
value: list[RoborockProductStateValue]
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@dataclass
|
|
465
|
+
class RoborockProductSpec(RoborockBase):
|
|
466
|
+
state: RoborockProductState
|
|
467
|
+
battery: dict | None = None
|
|
468
|
+
dry_countdown: dict | None = None
|
|
469
|
+
extra: dict | None = None
|
|
470
|
+
offpeak: dict | None = None
|
|
471
|
+
countdown: dict | None = None
|
|
472
|
+
mode: dict | None = None
|
|
473
|
+
ota_nfo: dict | None = None
|
|
474
|
+
pause: dict | None = None
|
|
475
|
+
program: dict | None = None
|
|
476
|
+
shutdown: dict | None = None
|
|
477
|
+
washing_left: dict | None = None
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
@dataclass
|
|
481
|
+
class RoborockProduct(RoborockBase):
|
|
482
|
+
id: int | None = None
|
|
483
|
+
name: str | None = None
|
|
484
|
+
model: str | None = None
|
|
485
|
+
packagename: str | None = None
|
|
486
|
+
ssid: str | None = None
|
|
487
|
+
picurl: str | None = None
|
|
488
|
+
cardpicurl: str | None = None
|
|
489
|
+
mediumCardpicurl: str | None = None
|
|
490
|
+
resetwifipicurl: str | None = None
|
|
491
|
+
configPicUrl: str | None = None
|
|
492
|
+
pluginPicUrl: str | None = None
|
|
493
|
+
resetwifitext: dict | None = None
|
|
494
|
+
tuyaid: str | None = None
|
|
495
|
+
status: int | None = None
|
|
496
|
+
rriotid: str | None = None
|
|
497
|
+
pictures: list | None = None
|
|
498
|
+
ncMode: str | None = None
|
|
499
|
+
scope: str | None = None
|
|
500
|
+
product_tags: list | None = None
|
|
501
|
+
agreements: list | None = None
|
|
502
|
+
cardspec: str | None = None
|
|
503
|
+
plugin_pic_url: str | None = None
|
|
504
|
+
|
|
505
|
+
@property
|
|
506
|
+
def product_nickname(self) -> RoborockProductNickname | None:
|
|
507
|
+
if self.cardspec:
|
|
508
|
+
return RoborockProductSpec.from_dict(json.loads(self.cardspec).get("data"))
|
|
509
|
+
return None
|
|
510
|
+
|
|
511
|
+
def __repr__(self) -> str:
|
|
512
|
+
return _attr_repr(self)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@dataclass
|
|
516
|
+
class RoborockProductCategory(RoborockBase):
|
|
517
|
+
id: int
|
|
518
|
+
display_name: str
|
|
519
|
+
icon_url: str
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
@dataclass
|
|
523
|
+
class RoborockCategoryDetail(RoborockBase):
|
|
524
|
+
category: RoborockProductCategory
|
|
525
|
+
product_list: list[RoborockProduct]
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
@dataclass
|
|
529
|
+
class ProductResponse(RoborockBase):
|
|
530
|
+
category_detail_list: list[RoborockCategoryDetail]
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from ..code_mappings import RoborockEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RoborockDyadStateCode(RoborockEnum):
|
|
5
|
+
unknown = -999
|
|
6
|
+
fetching = -998 # Obtaining Status
|
|
7
|
+
fetch_failed = -997 # Failed to obtain device status. Try again later.
|
|
8
|
+
updating = -996
|
|
9
|
+
washing = 1
|
|
10
|
+
ready = 2
|
|
11
|
+
charging = 3
|
|
12
|
+
mop_washing = 4
|
|
13
|
+
self_clean_cleaning = 5
|
|
14
|
+
self_clean_deep_cleaning = 6
|
|
15
|
+
self_clean_rinsing = 7
|
|
16
|
+
self_clean_dehydrating = 8
|
|
17
|
+
drying = 9
|
|
18
|
+
ventilating = 10 # drying
|
|
19
|
+
reserving = 12
|
|
20
|
+
mop_washing_paused = 13
|
|
21
|
+
dusting_mode = 14
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DyadSelfCleanMode(RoborockEnum):
|
|
25
|
+
self_clean = 1
|
|
26
|
+
self_clean_and_dry = 2
|
|
27
|
+
dry = 3
|
|
28
|
+
ventilation = 4
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DyadSelfCleanLevel(RoborockEnum):
|
|
32
|
+
normal = 1
|
|
33
|
+
deep = 2
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DyadWarmLevel(RoborockEnum):
|
|
37
|
+
normal = 1
|
|
38
|
+
deep = 2
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DyadMode(RoborockEnum):
|
|
42
|
+
wash = 1
|
|
43
|
+
wash_and_dry = 2
|
|
44
|
+
dry = 3
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DyadCleanMode(RoborockEnum):
|
|
48
|
+
auto = 1
|
|
49
|
+
max = 2
|
|
50
|
+
dehydration = 3
|
|
51
|
+
power_saving = 4
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DyadSuction(RoborockEnum):
|
|
55
|
+
l1 = 1
|
|
56
|
+
l2 = 2
|
|
57
|
+
l3 = 3
|
|
58
|
+
l4 = 4
|
|
59
|
+
l5 = 5
|
|
60
|
+
l6 = 6
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class DyadWaterLevel(RoborockEnum):
|
|
64
|
+
l1 = 1
|
|
65
|
+
l2 = 2
|
|
66
|
+
l3 = 3
|
|
67
|
+
l4 = 4
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class DyadBrushSpeed(RoborockEnum):
|
|
71
|
+
l1 = 1
|
|
72
|
+
l2 = 2
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class DyadCleanser(RoborockEnum):
|
|
76
|
+
none = 0
|
|
77
|
+
normal = 1
|
|
78
|
+
deep = 2
|
|
79
|
+
max = 3
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class DyadError(RoborockEnum):
|
|
83
|
+
none = 0
|
|
84
|
+
dirty_tank_full = 20000 # Dirty tank full. Empty it
|
|
85
|
+
water_level_sensor_stuck = 20001 # Water level sensor is stuck. Clean it.
|
|
86
|
+
clean_tank_empty = 20002 # Clean tank empty. Refill now
|
|
87
|
+
clean_head_entangled = 20003 # Check if the cleaning head is entangled with foreign objects.
|
|
88
|
+
clean_head_too_hot = 20004 # Cleaning head temperature protection. Wait for the temperature to return to normal.
|
|
89
|
+
fan_protection_e5 = 10005 # Fan protection (E5). Restart the vacuum cleaner.
|
|
90
|
+
cleaning_head_blocked = 20005 # Remove blockages from the cleaning head and pipes.
|
|
91
|
+
temperature_protection = 20006 # Temperature protection. Wait for the temperature to return to normal
|
|
92
|
+
fan_protection_e4 = 10004 # Fan protection (E4). Restart the vacuum cleaner.
|
|
93
|
+
fan_protection_e9 = 10009 # Fan protection (E9). Restart the vacuum cleaner.
|
|
94
|
+
battery_temperature_protection_e0 = 10000
|
|
95
|
+
battery_temperature_protection = (
|
|
96
|
+
20007 # Battery temperature protection. Wait for the temperature to return to a normal range.
|
|
97
|
+
)
|
|
98
|
+
battery_temperature_protection_2 = 20008
|
|
99
|
+
power_adapter_error = 20009 # Check if the power adapter is working properly.
|
|
100
|
+
dirty_charging_contacts = 10007 # Disconnection between the device and dock. Wipe charging contacts.
|
|
101
|
+
low_battery = 20017 # Low battery level. Charge before starting self-cleaning.
|
|
102
|
+
battery_under_10 = 20018 # Charge until the battery level exceeds 10% before manually starting self-cleaning.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from ..containers import RoborockBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class DyadProductInfo(RoborockBase):
|
|
8
|
+
sn: str
|
|
9
|
+
ssid: str
|
|
10
|
+
timezone: str
|
|
11
|
+
posix_timezone: str
|
|
12
|
+
ip: str
|
|
13
|
+
mac: str
|
|
14
|
+
oba: dict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class DyadSndState(RoborockBase):
|
|
19
|
+
sid_in_use: int
|
|
20
|
+
sid_version: int
|
|
21
|
+
location: str
|
|
22
|
+
bom: str
|
|
23
|
+
language: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class DyadOtaNfo(RoborockBase):
|
|
28
|
+
mqttOtaData: dict
|