python-roborock 2.50.0__tar.gz → 2.50.2__tar.gz
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.
- {python_roborock-2.50.0 → python_roborock-2.50.2}/PKG-INFO +1 -1
- {python_roborock-2.50.0 → python_roborock-2.50.2}/pyproject.toml +1 -1
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/const.py +4 -4
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/exceptions.py +4 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/web_api.py +87 -43
- {python_roborock-2.50.0 → python_roborock-2.50.2}/LICENSE +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/README.md +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/__init__.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/api.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/b01_containers.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/callbacks.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/clean_modes.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/cli.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/cloud_api.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/code_mappings.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/command_cache.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/containers.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/device_features.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/README.md +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/__init__.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/cache.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/channel.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/device.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/device_manager.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/local_channel.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/v1_rpc_channel.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/map/map_parser.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/mqtt/session.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/protocol.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/py.typed +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/roborock_future.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/roborock_message.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/util.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
|
@@ -4,10 +4,10 @@ SIDE_BRUSH_REPLACE_TIME = 720000
|
|
|
4
4
|
FILTER_REPLACE_TIME = 540000
|
|
5
5
|
SENSOR_DIRTY_REPLACE_TIME = 108000
|
|
6
6
|
MOP_ROLLER_REPLACE_TIME = 1080000
|
|
7
|
-
STRAINER_REPLACE_TIME =
|
|
8
|
-
CLEANING_BRUSH_REPLACE_TIME =
|
|
9
|
-
DUST_COLLECTION_REPLACE_TIME =
|
|
10
|
-
FLOOR_CLEANER_REPLACE_TIME =
|
|
7
|
+
STRAINER_REPLACE_TIME = 150
|
|
8
|
+
CLEANING_BRUSH_REPLACE_TIME = 300
|
|
9
|
+
DUST_COLLECTION_REPLACE_TIME = 90
|
|
10
|
+
FLOOR_CLEANER_REPLACE_TIME = 300
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
ROBOROCK_V1 = "ROBOROCK.vacuum.v1"
|
|
@@ -77,3 +77,7 @@ class RoborockTooManyRequest(RoborockException):
|
|
|
77
77
|
|
|
78
78
|
class RoborockRateLimit(RoborockException):
|
|
79
79
|
"""Class for our rate limits exceptions."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RoborockNoResponseFromBaseURL(RoborockException):
|
|
83
|
+
"""We could not find an url that had a record of the given account."""
|
|
@@ -8,6 +8,7 @@ import math
|
|
|
8
8
|
import secrets
|
|
9
9
|
import string
|
|
10
10
|
import time
|
|
11
|
+
from dataclasses import dataclass
|
|
11
12
|
|
|
12
13
|
import aiohttp
|
|
13
14
|
from aiohttp import ContentTypeError, FormData
|
|
@@ -22,14 +23,28 @@ from roborock.exceptions import (
|
|
|
22
23
|
RoborockInvalidEmail,
|
|
23
24
|
RoborockInvalidUserAgreement,
|
|
24
25
|
RoborockMissingParameters,
|
|
26
|
+
RoborockNoResponseFromBaseURL,
|
|
25
27
|
RoborockNoUserAgreement,
|
|
26
28
|
RoborockRateLimit,
|
|
27
29
|
RoborockTooFrequentCodeRequests,
|
|
28
|
-
RoborockTooManyRequest,
|
|
29
|
-
RoborockUrlException,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
_LOGGER = logging.getLogger(__name__)
|
|
33
|
+
BASE_URLS = [
|
|
34
|
+
"https://usiot.roborock.com",
|
|
35
|
+
"https://euiot.roborock.com",
|
|
36
|
+
"https://cniot.roborock.com",
|
|
37
|
+
"https://ruiot.roborock.com",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class IotLoginInfo:
|
|
43
|
+
"""Information about the login to the iot server."""
|
|
44
|
+
|
|
45
|
+
base_url: str
|
|
46
|
+
country_code: str
|
|
47
|
+
country: str
|
|
33
48
|
|
|
34
49
|
|
|
35
50
|
class RoborockApiClient:
|
|
@@ -49,41 +64,64 @@ class RoborockApiClient:
|
|
|
49
64
|
_login_limiter = Limiter(_LOGIN_RATES)
|
|
50
65
|
_home_data_limiter = Limiter(_HOME_DATA_RATES)
|
|
51
66
|
|
|
52
|
-
def __init__(
|
|
67
|
+
def __init__(
|
|
68
|
+
self, username: str, base_url: str | None = None, session: aiohttp.ClientSession | None = None
|
|
69
|
+
) -> None:
|
|
53
70
|
"""Sample API Client."""
|
|
54
71
|
self._username = username
|
|
55
|
-
self.
|
|
56
|
-
self.base_url = base_url
|
|
72
|
+
self._base_url = base_url
|
|
57
73
|
self._device_identifier = secrets.token_urlsafe(16)
|
|
58
74
|
self.session = session
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
self._iot_login_info: IotLoginInfo | None = None
|
|
76
|
+
|
|
77
|
+
async def _get_iot_login_info(self) -> IotLoginInfo:
|
|
78
|
+
if self._iot_login_info is None:
|
|
79
|
+
valid_urls = BASE_URLS if self._base_url is None else [self._base_url]
|
|
80
|
+
for iot_url in valid_urls:
|
|
81
|
+
url_request = PreparedRequest(iot_url, self.session)
|
|
82
|
+
response = await url_request.request(
|
|
83
|
+
"post",
|
|
84
|
+
"/api/v1/getUrlByEmail",
|
|
85
|
+
params={"email": self._username, "needtwostepauth": "false"},
|
|
86
|
+
)
|
|
87
|
+
if response is None:
|
|
88
|
+
continue
|
|
89
|
+
response_code = response.get("code")
|
|
90
|
+
if response_code != 200:
|
|
91
|
+
if response_code == 2003:
|
|
92
|
+
raise RoborockInvalidEmail("Your email was incorrectly formatted.")
|
|
93
|
+
elif response_code == 1001:
|
|
94
|
+
raise RoborockMissingParameters(
|
|
95
|
+
"You are missing parameters for this request, are you sure you entered your username?"
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
raise RoborockException(f"{response.get('msg')} - response code: {response_code}")
|
|
99
|
+
if response["data"]["countrycode"] is not None:
|
|
100
|
+
self._iot_login_info = IotLoginInfo(
|
|
101
|
+
base_url=response["data"]["url"],
|
|
102
|
+
country=response["data"]["country"],
|
|
103
|
+
country_code=response["data"]["countrycode"],
|
|
78
104
|
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
return self._iot_login_info
|
|
106
|
+
raise RoborockNoResponseFromBaseURL(
|
|
107
|
+
"No account was found for any base url we tried. Either your email is incorrect or we do not have a"
|
|
108
|
+
" record of the roborock server your device is on."
|
|
109
|
+
)
|
|
110
|
+
return self._iot_login_info
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
async def base_url(self):
|
|
114
|
+
if self._base_url is not None:
|
|
115
|
+
return self._base_url
|
|
116
|
+
return (await self._get_iot_login_info()).base_url
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
async def country(self):
|
|
120
|
+
return (await self._get_iot_login_info()).country
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
async def country_code(self):
|
|
124
|
+
return (await self._get_iot_login_info()).country_code
|
|
87
125
|
|
|
88
126
|
def _get_header_client_id(self):
|
|
89
127
|
md5 = hashlib.md5()
|
|
@@ -167,7 +205,7 @@ class RoborockApiClient:
|
|
|
167
205
|
except BucketFullException as ex:
|
|
168
206
|
_LOGGER.info(ex.meta_info)
|
|
169
207
|
raise RoborockRateLimit("Reached maximum requests for login. Please try again later.") from ex
|
|
170
|
-
base_url = await self.
|
|
208
|
+
base_url = await self.base_url
|
|
171
209
|
header_clientid = self._get_header_client_id()
|
|
172
210
|
code_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
|
|
173
211
|
|
|
@@ -198,7 +236,7 @@ class RoborockApiClient:
|
|
|
198
236
|
except BucketFullException as ex:
|
|
199
237
|
_LOGGER.info(ex.meta_info)
|
|
200
238
|
raise RoborockRateLimit("Reached maximum requests for login. Please try again later.") from ex
|
|
201
|
-
base_url = await self.
|
|
239
|
+
base_url = await self.base_url
|
|
202
240
|
header_clientid = self._get_header_client_id()
|
|
203
241
|
code_request = PreparedRequest(
|
|
204
242
|
base_url,
|
|
@@ -229,7 +267,7 @@ class RoborockApiClient:
|
|
|
229
267
|
|
|
230
268
|
async def _sign_key_v3(self, s: str) -> str:
|
|
231
269
|
"""Sign a randomly generated string."""
|
|
232
|
-
base_url = await self.
|
|
270
|
+
base_url = await self.base_url
|
|
233
271
|
header_clientid = self._get_header_client_id()
|
|
234
272
|
code_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
|
|
235
273
|
|
|
@@ -249,14 +287,20 @@ class RoborockApiClient:
|
|
|
249
287
|
|
|
250
288
|
return code_response["data"]["k"]
|
|
251
289
|
|
|
252
|
-
async def code_login_v4(
|
|
290
|
+
async def code_login_v4(
|
|
291
|
+
self, code: int | str, country: str | None = None, country_code: int | None = None
|
|
292
|
+
) -> UserData:
|
|
253
293
|
"""
|
|
254
294
|
Login via code authentication.
|
|
255
295
|
:param code: The code from the email.
|
|
256
296
|
:param country: The two-character representation of the country, i.e. "US"
|
|
257
297
|
:param country_code: the country phone number code i.e. 1 for US.
|
|
258
298
|
"""
|
|
259
|
-
base_url = await self.
|
|
299
|
+
base_url = await self.base_url
|
|
300
|
+
if country is None:
|
|
301
|
+
country = await self.country
|
|
302
|
+
if country_code is None:
|
|
303
|
+
country_code = await self.country_code
|
|
260
304
|
header_clientid = self._get_header_client_id()
|
|
261
305
|
x_mercy_ks = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
|
|
262
306
|
x_mercy_k = await self._sign_key_v3(x_mercy_ks)
|
|
@@ -304,7 +348,7 @@ class RoborockApiClient:
|
|
|
304
348
|
except BucketFullException as ex:
|
|
305
349
|
_LOGGER.info(ex.meta_info)
|
|
306
350
|
raise RoborockRateLimit("Reached maximum requests for login. Please try again later.") from ex
|
|
307
|
-
base_url = await self.
|
|
351
|
+
base_url = await self.base_url
|
|
308
352
|
header_clientid = self._get_header_client_id()
|
|
309
353
|
|
|
310
354
|
login_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
|
|
@@ -343,7 +387,7 @@ class RoborockApiClient:
|
|
|
343
387
|
raise NotImplementedError("Pass_login_v3 has not yet been implemented")
|
|
344
388
|
|
|
345
389
|
async def code_login(self, code: int | str) -> UserData:
|
|
346
|
-
base_url = await self.
|
|
390
|
+
base_url = await self.base_url
|
|
347
391
|
header_clientid = self._get_header_client_id()
|
|
348
392
|
|
|
349
393
|
login_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
|
|
@@ -376,7 +420,7 @@ class RoborockApiClient:
|
|
|
376
420
|
return UserData.from_dict(user_data)
|
|
377
421
|
|
|
378
422
|
async def _get_home_id(self, user_data: UserData):
|
|
379
|
-
base_url = await self.
|
|
423
|
+
base_url = await self.base_url
|
|
380
424
|
header_clientid = self._get_header_client_id()
|
|
381
425
|
home_id_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
|
|
382
426
|
home_id_response = await home_id_request.request(
|
|
@@ -547,7 +591,7 @@ class RoborockApiClient:
|
|
|
547
591
|
|
|
548
592
|
async def get_products(self, user_data: UserData) -> ProductResponse:
|
|
549
593
|
"""Gets all products and their schemas, good for determining status codes and model numbers."""
|
|
550
|
-
base_url = await self.
|
|
594
|
+
base_url = await self.base_url
|
|
551
595
|
header_clientid = self._get_header_client_id()
|
|
552
596
|
product_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
|
|
553
597
|
product_response = await product_request.request(
|
|
@@ -565,7 +609,7 @@ class RoborockApiClient:
|
|
|
565
609
|
raise RoborockException("product result was an unexpected type")
|
|
566
610
|
|
|
567
611
|
async def download_code(self, user_data: UserData, product_id: int):
|
|
568
|
-
base_url = await self.
|
|
612
|
+
base_url = await self.base_url
|
|
569
613
|
header_clientid = self._get_header_client_id()
|
|
570
614
|
product_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
|
|
571
615
|
request = {"apilevel": 99999, "productids": [product_id], "type": 2}
|
|
@@ -578,7 +622,7 @@ class RoborockApiClient:
|
|
|
578
622
|
return response["data"][0]["url"]
|
|
579
623
|
|
|
580
624
|
async def download_category_code(self, user_data: UserData):
|
|
581
|
-
base_url = await self.
|
|
625
|
+
base_url = await self.base_url
|
|
582
626
|
header_clientid = self._get_header_client_id()
|
|
583
627
|
product_request = PreparedRequest(base_url, self.session, {"header_clientid": header_clientid})
|
|
584
628
|
response = await product_request.request(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/v1/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.50.0 → python_roborock-2.50.2}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|