python-roborock 2.8.4__tar.gz → 2.9.0__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.8.4 → python_roborock-2.9.0}/PKG-INFO +46 -2
- python_roborock-2.9.0/README.md +76 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/pyproject.toml +5 -1
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/api.py +10 -16
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/code_mappings.py +27 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/const.py +1 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/containers.py +34 -20
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/roborock_typing.py +6 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_1_apis/roborock_client_v1.py +14 -1
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_1_apis/roborock_local_client_v1.py +1 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +1 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/web_api.py +31 -11
- python_roborock-2.8.4/README.md +0 -32
- {python_roborock-2.8.4 → python_roborock-2.9.0}/LICENSE +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/__init__.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/cli.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/cloud_api.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/command_cache.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/exceptions.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/local_api.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/protocol.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/py.typed +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/roborock_future.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/roborock_message.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/util.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.0
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
5
|
Home-page: https://github.com/humbertogontijo/python-roborock
|
|
6
6
|
License: GPL-3.0-only
|
|
@@ -53,6 +53,50 @@ Install this via pip (or your favourite package manager):
|
|
|
53
53
|
|
|
54
54
|
You can see all of the commands supported [here]("https://python-roborock.readthedocs.io/en/latest/api_commands.html")
|
|
55
55
|
|
|
56
|
+
## Sending Commands
|
|
57
|
+
|
|
58
|
+
Here is an example that requires no manual intervention and can be done all automatically. You can skip some steps by
|
|
59
|
+
caching values or looking at them and grabbing them manually.
|
|
60
|
+
```python
|
|
61
|
+
import asyncio
|
|
62
|
+
|
|
63
|
+
from roborock import HomeDataProduct, DeviceData, RoborockCommand
|
|
64
|
+
from roborock.version_1_apis import RoborockMqttClientV1, RoborockLocalClientV1
|
|
65
|
+
from roborock.web_api import RoborockApiClient
|
|
66
|
+
|
|
67
|
+
async def main():
|
|
68
|
+
web_api = RoborockApiClient(username="youremailhere")
|
|
69
|
+
# Login via your password
|
|
70
|
+
user_data = await web_api.pass_login(password="pass_here")
|
|
71
|
+
# Or login via a code
|
|
72
|
+
await web_api.request_code()
|
|
73
|
+
code = input("What is the code?")
|
|
74
|
+
user_data = await web_api.code_login(code)
|
|
75
|
+
|
|
76
|
+
# Get home data
|
|
77
|
+
home_data = await web_api.get_home_data_v2(user_data)
|
|
78
|
+
|
|
79
|
+
# Get the device you want
|
|
80
|
+
device = home_data.devices[0]
|
|
81
|
+
|
|
82
|
+
# Get product ids:
|
|
83
|
+
product_info: dict[str, HomeDataProduct] = {
|
|
84
|
+
product.id: product for product in home_data.products
|
|
85
|
+
}
|
|
86
|
+
# Create the Mqtt(aka cloud required) Client
|
|
87
|
+
device_data = DeviceData(device, product_info[device.product_id].model)
|
|
88
|
+
mqtt_client = RoborockMqttClientV1(user_data, device_data)
|
|
89
|
+
networking = await mqtt_client.get_networking()
|
|
90
|
+
local_device_data = DeviceData(device, product_info[device.product_id].model, networking)
|
|
91
|
+
local_client = RoborockLocalClientV1(local_device_data)
|
|
92
|
+
# You can use the send_command to send any command to the device
|
|
93
|
+
status = await local_client.send_command(RoborockCommand.GET_STATUS)
|
|
94
|
+
# Or use existing functions that will give you data classes
|
|
95
|
+
status = await local_client.get_status()
|
|
96
|
+
|
|
97
|
+
asyncio.run(main())
|
|
98
|
+
```
|
|
99
|
+
|
|
56
100
|
## Supported devices
|
|
57
101
|
|
|
58
102
|
You can find what devices are supported
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Roborock
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://pypi.org/project/python-roborock/">
|
|
5
|
+
<img src="https://img.shields.io/pypi/v/python-roborock.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
|
|
6
|
+
</a>
|
|
7
|
+
<img src="https://img.shields.io/pypi/pyversions/python-roborock.svg?style=flat-square&logo=python&logoColor=fff" alt="Supported Python versions">
|
|
8
|
+
<img src="https://img.shields.io/pypi/l/python-roborock.svg?style=flat-square" alt="License">
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
Roborock library for online and offline control of your vacuums.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Install this via pip (or your favourite package manager):
|
|
16
|
+
|
|
17
|
+
`pip install python-roborock`
|
|
18
|
+
|
|
19
|
+
## Functionality
|
|
20
|
+
|
|
21
|
+
You can see all of the commands supported [here]("https://python-roborock.readthedocs.io/en/latest/api_commands.html")
|
|
22
|
+
|
|
23
|
+
## Sending Commands
|
|
24
|
+
|
|
25
|
+
Here is an example that requires no manual intervention and can be done all automatically. You can skip some steps by
|
|
26
|
+
caching values or looking at them and grabbing them manually.
|
|
27
|
+
```python
|
|
28
|
+
import asyncio
|
|
29
|
+
|
|
30
|
+
from roborock import HomeDataProduct, DeviceData, RoborockCommand
|
|
31
|
+
from roborock.version_1_apis import RoborockMqttClientV1, RoborockLocalClientV1
|
|
32
|
+
from roborock.web_api import RoborockApiClient
|
|
33
|
+
|
|
34
|
+
async def main():
|
|
35
|
+
web_api = RoborockApiClient(username="youremailhere")
|
|
36
|
+
# Login via your password
|
|
37
|
+
user_data = await web_api.pass_login(password="pass_here")
|
|
38
|
+
# Or login via a code
|
|
39
|
+
await web_api.request_code()
|
|
40
|
+
code = input("What is the code?")
|
|
41
|
+
user_data = await web_api.code_login(code)
|
|
42
|
+
|
|
43
|
+
# Get home data
|
|
44
|
+
home_data = await web_api.get_home_data_v2(user_data)
|
|
45
|
+
|
|
46
|
+
# Get the device you want
|
|
47
|
+
device = home_data.devices[0]
|
|
48
|
+
|
|
49
|
+
# Get product ids:
|
|
50
|
+
product_info: dict[str, HomeDataProduct] = {
|
|
51
|
+
product.id: product for product in home_data.products
|
|
52
|
+
}
|
|
53
|
+
# Create the Mqtt(aka cloud required) Client
|
|
54
|
+
device_data = DeviceData(device, product_info[device.product_id].model)
|
|
55
|
+
mqtt_client = RoborockMqttClientV1(user_data, device_data)
|
|
56
|
+
networking = await mqtt_client.get_networking()
|
|
57
|
+
local_device_data = DeviceData(device, product_info[device.product_id].model, networking)
|
|
58
|
+
local_client = RoborockLocalClientV1(local_device_data)
|
|
59
|
+
# You can use the send_command to send any command to the device
|
|
60
|
+
status = await local_client.send_command(RoborockCommand.GET_STATUS)
|
|
61
|
+
# Or use existing functions that will give you data classes
|
|
62
|
+
status = await local_client.get_status()
|
|
63
|
+
|
|
64
|
+
asyncio.run(main())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Supported devices
|
|
68
|
+
|
|
69
|
+
You can find what devices are supported
|
|
70
|
+
[here]("https://python-roborock.readthedocs.io/en/latest/supported_devices.html").
|
|
71
|
+
Please note this may not immediately contain the latest devices.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Credits
|
|
75
|
+
|
|
76
|
+
Thanks @rovo89 for https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7 And thanks @PiotrMachowski for https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.9.0"
|
|
4
4
|
description = "A package to control Roborock vacuums."
|
|
5
5
|
authors = ["humbertogontijo <humbertogontijo@users.noreply.github.com>"]
|
|
6
6
|
license = "GPL-3.0-only"
|
|
@@ -45,6 +45,7 @@ mypy = "*"
|
|
|
45
45
|
ruff = "*"
|
|
46
46
|
codespell = "*"
|
|
47
47
|
pyshark = "^0.6"
|
|
48
|
+
aioresponses = "^0.7.7"
|
|
48
49
|
|
|
49
50
|
[tool.semantic_release]
|
|
50
51
|
branch = "main"
|
|
@@ -67,3 +68,6 @@ select=["E", "F", "UP", "I"]
|
|
|
67
68
|
|
|
68
69
|
[tool.ruff.lint.per-file-ignores]
|
|
69
70
|
"*/__init__.py" = ["F401"]
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
asyncio_mode = "auto"
|
|
@@ -7,7 +7,7 @@ import base64
|
|
|
7
7
|
import logging
|
|
8
8
|
import secrets
|
|
9
9
|
import time
|
|
10
|
-
from collections.abc import
|
|
10
|
+
from collections.abc import Coroutine
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
from .containers import (
|
|
@@ -36,8 +36,8 @@ class RoborockClient:
|
|
|
36
36
|
self._endpoint = endpoint
|
|
37
37
|
self._nonce = secrets.token_bytes(16)
|
|
38
38
|
self._waiting_queue: dict[int, RoborockFuture] = {}
|
|
39
|
-
self._last_device_msg_in =
|
|
40
|
-
self._last_disconnection =
|
|
39
|
+
self._last_device_msg_in = time.monotonic()
|
|
40
|
+
self._last_disconnection = time.monotonic()
|
|
41
41
|
self.keep_alive = KEEPALIVE
|
|
42
42
|
self._diagnostic_data: dict[str, dict[str, Any]] = {
|
|
43
43
|
"misc_info": {"Nonce": base64.b64encode(self._nonce).decode("utf-8")}
|
|
@@ -59,15 +59,6 @@ class RoborockClient:
|
|
|
59
59
|
def diagnostic_data(self) -> dict:
|
|
60
60
|
return self._diagnostic_data
|
|
61
61
|
|
|
62
|
-
@property
|
|
63
|
-
def time_func(self) -> Callable[[], float]:
|
|
64
|
-
try:
|
|
65
|
-
# Use monotonic clock if available
|
|
66
|
-
time_func = time.monotonic
|
|
67
|
-
except AttributeError:
|
|
68
|
-
time_func = time.time
|
|
69
|
-
return time_func
|
|
70
|
-
|
|
71
62
|
async def async_connect(self):
|
|
72
63
|
raise NotImplementedError
|
|
73
64
|
|
|
@@ -81,13 +72,13 @@ class RoborockClient:
|
|
|
81
72
|
raise NotImplementedError
|
|
82
73
|
|
|
83
74
|
def on_connection_lost(self, exc: Exception | None) -> None:
|
|
84
|
-
self._last_disconnection =
|
|
75
|
+
self._last_disconnection = time.monotonic()
|
|
85
76
|
self._logger.info("Roborock client disconnected")
|
|
86
77
|
if exc is not None:
|
|
87
78
|
self._logger.warning(exc)
|
|
88
79
|
|
|
89
80
|
def should_keepalive(self) -> bool:
|
|
90
|
-
now =
|
|
81
|
+
now = time.monotonic()
|
|
91
82
|
# noinspection PyUnresolvedReferences
|
|
92
83
|
if now - self._last_disconnection > self.keep_alive**2 and now - self._last_device_msg_in > self.keep_alive:
|
|
93
84
|
return False
|
|
@@ -116,8 +107,11 @@ class RoborockClient:
|
|
|
116
107
|
if request_id in self._waiting_queue:
|
|
117
108
|
new_id = get_next_int(10000, 32767)
|
|
118
109
|
_LOGGER.warning(
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
"Attempting to create a future with an existing id %s (%s)... New id is %s. "
|
|
111
|
+
"Code may not function properly.",
|
|
112
|
+
request_id,
|
|
113
|
+
protocol_id,
|
|
114
|
+
new_id,
|
|
121
115
|
)
|
|
122
116
|
request_id = new_id
|
|
123
117
|
self._waiting_queue[request_id] = queue
|
|
@@ -254,6 +254,15 @@ class RoborockFanSpeedQRevoMaster(RoborockFanPowerCode):
|
|
|
254
254
|
smart_mode = 110
|
|
255
255
|
|
|
256
256
|
|
|
257
|
+
class RoborockFanSpeedQRevoCurv(RoborockFanPowerCode):
|
|
258
|
+
quiet = 101
|
|
259
|
+
balanced = 102
|
|
260
|
+
turbo = 103
|
|
261
|
+
max = 104
|
|
262
|
+
max_plus = 105
|
|
263
|
+
smart_mode = 110
|
|
264
|
+
|
|
265
|
+
|
|
257
266
|
class RoborockFanSpeedP10(RoborockFanPowerCode):
|
|
258
267
|
off = 105
|
|
259
268
|
quiet = 101
|
|
@@ -279,6 +288,14 @@ class RoborockMopModeCode(RoborockEnum):
|
|
|
279
288
|
"""Describes the mop mode of the vacuum cleaner."""
|
|
280
289
|
|
|
281
290
|
|
|
291
|
+
class RoborockMopModeQRevoCurv(RoborockMopModeCode):
|
|
292
|
+
standard = 300
|
|
293
|
+
deep = 301
|
|
294
|
+
deep_plus = 303
|
|
295
|
+
fast = 304
|
|
296
|
+
smart_mode = 306
|
|
297
|
+
|
|
298
|
+
|
|
282
299
|
class RoborockMopModeS7(RoborockMopModeCode):
|
|
283
300
|
"""Describes the mop mode of the vacuum cleaner."""
|
|
284
301
|
|
|
@@ -351,6 +368,15 @@ class RoborockMopIntensityQRevoMaster(RoborockMopIntensityCode):
|
|
|
351
368
|
smart_mode = 209
|
|
352
369
|
|
|
353
370
|
|
|
371
|
+
class RoborockMopIntensityQRevoCurv(RoborockMopIntensityCode):
|
|
372
|
+
off = 200
|
|
373
|
+
low = 201
|
|
374
|
+
medium = 202
|
|
375
|
+
high = 203
|
|
376
|
+
custom_water_flow = 207
|
|
377
|
+
smart_mode = 209
|
|
378
|
+
|
|
379
|
+
|
|
354
380
|
class RoborockMopIntensityP10(RoborockMopIntensityCode):
|
|
355
381
|
"""Describes the mop intensity of the vacuum cleaner."""
|
|
356
382
|
|
|
@@ -431,6 +457,7 @@ class RoborockDockTypeCode(RoborockEnum):
|
|
|
431
457
|
s8_maxv_ultra_dock = 10
|
|
432
458
|
qrevo_master_dock = 14
|
|
433
459
|
qrevo_s_dock = 15
|
|
460
|
+
qrevo_curv_dock = 17
|
|
434
461
|
|
|
435
462
|
|
|
436
463
|
class RoborockDockDustCollectionModeCode(RoborockEnum):
|
|
@@ -31,6 +31,7 @@ ROBOROCK_Q7 = "roborock.vacuum.a40"
|
|
|
31
31
|
ROBOROCK_Q7_MAX = "roborock.vacuum.a38"
|
|
32
32
|
ROBOROCK_Q7PLUS = "roborock.vacuum.a40"
|
|
33
33
|
ROBOROCK_QREVO_MASTER = "roborock.vacuum.a117"
|
|
34
|
+
ROBOROCK_QREVO_CURV = "roborock.vacuum.a135"
|
|
34
35
|
ROBOROCK_Q8_MAX = "roborock.vacuum.a73"
|
|
35
36
|
ROBOROCK_G10S_PRO = "roborock.vacuum.a26"
|
|
36
37
|
ROBOROCK_G10S = "roborock.vacuum.a46"
|
|
@@ -20,6 +20,7 @@ from .code_mappings import (
|
|
|
20
20
|
RoborockFanPowerCode,
|
|
21
21
|
RoborockFanSpeedP10,
|
|
22
22
|
RoborockFanSpeedQ7Max,
|
|
23
|
+
RoborockFanSpeedQRevoCurv,
|
|
23
24
|
RoborockFanSpeedQRevoMaster,
|
|
24
25
|
RoborockFanSpeedS6Pure,
|
|
25
26
|
RoborockFanSpeedS7,
|
|
@@ -30,12 +31,14 @@ from .code_mappings import (
|
|
|
30
31
|
RoborockMopIntensityCode,
|
|
31
32
|
RoborockMopIntensityP10,
|
|
32
33
|
RoborockMopIntensityQ7Max,
|
|
34
|
+
RoborockMopIntensityQRevoCurv,
|
|
33
35
|
RoborockMopIntensityQRevoMaster,
|
|
34
36
|
RoborockMopIntensityS5Max,
|
|
35
37
|
RoborockMopIntensityS6MaxV,
|
|
36
38
|
RoborockMopIntensityS7,
|
|
37
39
|
RoborockMopIntensityS8MaxVUltra,
|
|
38
40
|
RoborockMopModeCode,
|
|
41
|
+
RoborockMopModeQRevoCurv,
|
|
39
42
|
RoborockMopModeQRevoMaster,
|
|
40
43
|
RoborockMopModeS7,
|
|
41
44
|
RoborockMopModeS8MaxVUltra,
|
|
@@ -52,6 +55,7 @@ from .const import (
|
|
|
52
55
|
ROBOROCK_G10S_PRO,
|
|
53
56
|
ROBOROCK_P10,
|
|
54
57
|
ROBOROCK_Q7_MAX,
|
|
58
|
+
ROBOROCK_QREVO_CURV,
|
|
55
59
|
ROBOROCK_QREVO_MASTER,
|
|
56
60
|
ROBOROCK_QREVO_MAXV,
|
|
57
61
|
ROBOROCK_QREVO_PRO,
|
|
@@ -581,6 +585,13 @@ class QRevoMasterStatus(Status):
|
|
|
581
585
|
mop_mode: RoborockMopModeQRevoMaster | None = None
|
|
582
586
|
|
|
583
587
|
|
|
588
|
+
@dataclass
|
|
589
|
+
class QRevoCurvStatus(Status):
|
|
590
|
+
fan_power: RoborockFanSpeedQRevoCurv | None = None
|
|
591
|
+
water_box_mode: RoborockMopIntensityQRevoCurv | None = None
|
|
592
|
+
mop_mode: RoborockMopModeQRevoCurv | None = None
|
|
593
|
+
|
|
594
|
+
|
|
584
595
|
@dataclass
|
|
585
596
|
class S6MaxVStatus(Status):
|
|
586
597
|
fan_power: RoborockFanSpeedS7MaxV | None = None
|
|
@@ -639,6 +650,7 @@ ModelStatus: dict[str, type[Status]] = {
|
|
|
639
650
|
ROBOROCK_S5_MAX: S5MaxStatus,
|
|
640
651
|
ROBOROCK_Q7_MAX: Q7MaxStatus,
|
|
641
652
|
ROBOROCK_QREVO_MASTER: QRevoMasterStatus,
|
|
653
|
+
ROBOROCK_QREVO_CURV: QRevoCurvStatus,
|
|
642
654
|
ROBOROCK_S6: S6PureStatus,
|
|
643
655
|
ROBOROCK_S6_MAXV: S6MaxVStatus,
|
|
644
656
|
ROBOROCK_S6_PURE: S6PureStatus,
|
|
@@ -881,26 +893,28 @@ class RoborockProductSpec(RoborockBase):
|
|
|
881
893
|
|
|
882
894
|
@dataclass
|
|
883
895
|
class RoborockProduct(RoborockBase):
|
|
884
|
-
id: int
|
|
885
|
-
name: str
|
|
886
|
-
model: str
|
|
887
|
-
packagename: str
|
|
888
|
-
ssid: str
|
|
889
|
-
picurl: str
|
|
890
|
-
cardpicurl: str
|
|
891
|
-
|
|
892
|
-
resetwifipicurl: str
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
896
|
+
id: int | None = None
|
|
897
|
+
name: str | None = None
|
|
898
|
+
model: str | None = None
|
|
899
|
+
packagename: str | None = None
|
|
900
|
+
ssid: str | None = None
|
|
901
|
+
picurl: str | None = None
|
|
902
|
+
cardpicurl: str | None = None
|
|
903
|
+
mediumCardpicurl: str | None = None
|
|
904
|
+
resetwifipicurl: str | None = None
|
|
905
|
+
configPicUrl: str | None = None
|
|
906
|
+
pluginPicUrl: str | None = None
|
|
907
|
+
resetwifitext: dict | None = None
|
|
908
|
+
tuyaid: str | None = None
|
|
909
|
+
status: int | None = None
|
|
910
|
+
rriotid: str | None = None
|
|
911
|
+
pictures: list | None = None
|
|
912
|
+
ncMode: str | None = None
|
|
913
|
+
scope: str | None = None
|
|
914
|
+
product_tags: list | None = None
|
|
915
|
+
agreements: list | None = None
|
|
916
|
+
cardspec: str | None = None
|
|
917
|
+
plugin_pic_url: str | None = None
|
|
904
918
|
products_specification: RoborockProductSpec | None = None
|
|
905
919
|
|
|
906
920
|
def __post_init__(self):
|
|
@@ -461,6 +461,11 @@ class DeviceProp(RoborockBase):
|
|
|
461
461
|
consumable: Consumable = field(default_factory=Consumable)
|
|
462
462
|
last_clean_record: CleanRecord | None = None
|
|
463
463
|
dock_summary: DockSummary | None = None
|
|
464
|
+
dust_collection_mode_name: str | None = None
|
|
465
|
+
|
|
466
|
+
def __post_init__(self) -> None:
|
|
467
|
+
if self.dock_summary and self.dock_summary.dust_collection_mode and self.dock_summary.dust_collection_mode.mode:
|
|
468
|
+
self.dust_collection_mode_name = self.dock_summary.dust_collection_mode.mode.name
|
|
464
469
|
|
|
465
470
|
def update(self, device_prop: DeviceProp) -> None:
|
|
466
471
|
if device_prop.status:
|
|
@@ -473,3 +478,4 @@ class DeviceProp(RoborockBase):
|
|
|
473
478
|
self.last_clean_record = device_prop.last_clean_record
|
|
474
479
|
if device_prop.dock_summary:
|
|
475
480
|
self.dock_summary = device_prop.dock_summary
|
|
481
|
+
self.__post_init__()
|
{python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
@@ -71,6 +71,7 @@ WASH_N_FILL_DOCK = [
|
|
|
71
71
|
RoborockDockTypeCode.p10_pro_dock,
|
|
72
72
|
RoborockDockTypeCode.s8_maxv_ultra_dock,
|
|
73
73
|
RoborockDockTypeCode.qrevo_s_dock,
|
|
74
|
+
RoborockDockTypeCode.qrevo_curv_dock,
|
|
74
75
|
]
|
|
75
76
|
RT = TypeVar("RT", bound=RoborockBase)
|
|
76
77
|
EVICT_TIME = 60
|
|
@@ -361,7 +362,7 @@ class RoborockClientV1(RoborockClient):
|
|
|
361
362
|
|
|
362
363
|
def on_message_received(self, messages: list[RoborockMessage]) -> None:
|
|
363
364
|
try:
|
|
364
|
-
self._last_device_msg_in =
|
|
365
|
+
self._last_device_msg_in = time.monotonic()
|
|
365
366
|
for data in messages:
|
|
366
367
|
protocol = data.protocol
|
|
367
368
|
if data.payload and protocol in [
|
|
@@ -391,6 +392,8 @@ class RoborockClientV1(RoborockClient):
|
|
|
391
392
|
if isinstance(result, list) and len(result) == 1:
|
|
392
393
|
result = result[0]
|
|
393
394
|
queue.resolve((result, None))
|
|
395
|
+
else:
|
|
396
|
+
self._logger.debug("Received response for unknown request id %s", request_id)
|
|
394
397
|
else:
|
|
395
398
|
try:
|
|
396
399
|
data_protocol = RoborockDataProtocol(int(data_point_number))
|
|
@@ -418,6 +421,12 @@ class RoborockClientV1(RoborockClient):
|
|
|
418
421
|
consumable = Consumable.from_dict(value)
|
|
419
422
|
for listener in self.listener_model.protocol_handlers.get(data_protocol, []):
|
|
420
423
|
listener(consumable)
|
|
424
|
+
else:
|
|
425
|
+
self._logger.warning(
|
|
426
|
+
f"Unknown data protocol {data_point_number}, please create an "
|
|
427
|
+
f"issue on the python-roborock repository"
|
|
428
|
+
)
|
|
429
|
+
self._logger.info(data)
|
|
421
430
|
return
|
|
422
431
|
except ValueError:
|
|
423
432
|
self._logger.warning(
|
|
@@ -443,10 +452,14 @@ class RoborockClientV1(RoborockClient):
|
|
|
443
452
|
if isinstance(decompressed, list):
|
|
444
453
|
decompressed = decompressed[0]
|
|
445
454
|
queue.resolve((decompressed, None))
|
|
455
|
+
else:
|
|
456
|
+
self._logger.debug("Received response for unknown request id %s", request_id)
|
|
446
457
|
else:
|
|
447
458
|
queue = self._waiting_queue.get(data.seq)
|
|
448
459
|
if queue:
|
|
449
460
|
queue.resolve((data.payload, None))
|
|
461
|
+
else:
|
|
462
|
+
self._logger.debug("Received response for unknown request id %s", data.seq)
|
|
450
463
|
except Exception as ex:
|
|
451
464
|
self._logger.exception(ex)
|
|
452
465
|
|
{python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_1_apis/roborock_local_client_v1.py
RENAMED
|
@@ -18,6 +18,7 @@ class RoborockLocalClientV1(RoborockLocalClient, RoborockClientV1):
|
|
|
18
18
|
) -> RoborockMessage:
|
|
19
19
|
secured = True if method in COMMANDS_SECURED else False
|
|
20
20
|
request_id, timestamp, payload = self._get_payload(method, params, secured)
|
|
21
|
+
self._logger.debug("Building message id %s for method %s", request_id, method)
|
|
21
22
|
request_protocol = RoborockMessageProtocol.GENERAL_REQUEST
|
|
22
23
|
message_retry: MessageRetry | None = None
|
|
23
24
|
if method == RoborockCommand.RETRY_REQUEST and isinstance(params, dict):
|
{python_roborock-2.8.4 → python_roborock-2.9.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
@@ -74,6 +74,7 @@ class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
|
|
|
74
74
|
# When we have more custom commands do something more complicated here
|
|
75
75
|
return await self._get_calibration_points()
|
|
76
76
|
request_id, timestamp, payload = self._get_payload(method, params, True)
|
|
77
|
+
self._logger.debug("Building message id %s for method %s", request_id, method)
|
|
77
78
|
request_protocol = RoborockMessageProtocol.RPC_REQUEST
|
|
78
79
|
roborock_message = RoborockMessage(timestamp=timestamp, protocol=request_protocol, payload=payload)
|
|
79
80
|
return await self.send_message(roborock_message)
|
|
@@ -54,7 +54,7 @@ class RoborockApiClient:
|
|
|
54
54
|
raise RoborockMissingParameters(
|
|
55
55
|
"You are missing parameters for this request, are you sure you " "entered your username?"
|
|
56
56
|
)
|
|
57
|
-
raise RoborockUrlException(response.get(
|
|
57
|
+
raise RoborockUrlException(f"error code: {response_code} msg: {response.get('error')}")
|
|
58
58
|
response_data = response.get("data")
|
|
59
59
|
if response_data is None:
|
|
60
60
|
raise RoborockUrlException("response does not have 'data'")
|
|
@@ -146,7 +146,7 @@ class RoborockApiClient:
|
|
|
146
146
|
"""
|
|
147
147
|
raise NotImplementedError("Pass_login_v3 has not yet been implemented")
|
|
148
148
|
|
|
149
|
-
async def code_login(self, code) -> UserData:
|
|
149
|
+
async def code_login(self, code: int | str) -> UserData:
|
|
150
150
|
base_url = await self._get_base_url()
|
|
151
151
|
header_clientid = self._get_header_client_id()
|
|
152
152
|
|
|
@@ -276,7 +276,7 @@ class RoborockApiClient:
|
|
|
276
276
|
product_request = PreparedRequest(base_url, {"header_clientid": header_clientid})
|
|
277
277
|
product_response = await product_request.request(
|
|
278
278
|
"get",
|
|
279
|
-
"/api/
|
|
279
|
+
"/api/v4/product",
|
|
280
280
|
headers={"Authorization": user_data.token},
|
|
281
281
|
)
|
|
282
282
|
if product_response is None:
|
|
@@ -288,24 +288,44 @@ class RoborockApiClient:
|
|
|
288
288
|
return ProductResponse.from_dict(result)
|
|
289
289
|
raise RoborockException("product result was an unexpected type")
|
|
290
290
|
|
|
291
|
+
async def download_code(self, user_data: UserData, product_id: int):
|
|
292
|
+
base_url = await self._get_base_url()
|
|
293
|
+
header_clientid = self._get_header_client_id()
|
|
294
|
+
product_request = PreparedRequest(base_url, {"header_clientid": header_clientid})
|
|
295
|
+
request = {"apilevel": 99999, "productids": [product_id], "type": 2}
|
|
296
|
+
response = await product_request.request(
|
|
297
|
+
"post",
|
|
298
|
+
"/api/v1/appplugin",
|
|
299
|
+
json=request,
|
|
300
|
+
headers={"Authorization": user_data.token, "Content-Type": "application/json"},
|
|
301
|
+
)
|
|
302
|
+
return response["data"][0]["url"]
|
|
303
|
+
|
|
304
|
+
async def download_category_code(self, user_data: UserData):
|
|
305
|
+
base_url = await self._get_base_url()
|
|
306
|
+
header_clientid = self._get_header_client_id()
|
|
307
|
+
product_request = PreparedRequest(base_url, {"header_clientid": header_clientid})
|
|
308
|
+
response = await product_request.request(
|
|
309
|
+
"get",
|
|
310
|
+
"api/v1/plugins?apiLevel=99999&type=2",
|
|
311
|
+
headers={
|
|
312
|
+
"Authorization": user_data.token,
|
|
313
|
+
},
|
|
314
|
+
)
|
|
315
|
+
return {r["category"]: r["url"] for r in response["data"]["categoryPluginList"]}
|
|
316
|
+
|
|
291
317
|
|
|
292
318
|
class PreparedRequest:
|
|
293
319
|
def __init__(self, base_url: str, base_headers: dict | None = None) -> None:
|
|
294
320
|
self.base_url = base_url
|
|
295
321
|
self.base_headers = base_headers or {}
|
|
296
322
|
|
|
297
|
-
async def request(self, method: str, url: str, params=None, data=None, headers=None) -> dict:
|
|
323
|
+
async def request(self, method: str, url: str, params=None, data=None, headers=None, json=None) -> dict:
|
|
298
324
|
_url = "/".join(s.strip("/") for s in [self.base_url, url])
|
|
299
325
|
_headers = {**self.base_headers, **(headers or {})}
|
|
300
326
|
async with aiohttp.ClientSession() as session:
|
|
301
327
|
try:
|
|
302
|
-
async with session.request(
|
|
303
|
-
method,
|
|
304
|
-
_url,
|
|
305
|
-
params=params,
|
|
306
|
-
data=data,
|
|
307
|
-
headers=_headers,
|
|
308
|
-
) as resp:
|
|
328
|
+
async with session.request(method, _url, params=params, data=data, headers=_headers, json=json) as resp:
|
|
309
329
|
return await resp.json()
|
|
310
330
|
except ContentTypeError as err:
|
|
311
331
|
"""If we get an error, lets log everything for debugging."""
|
python_roborock-2.8.4/README.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Roborock
|
|
2
|
-
|
|
3
|
-
<p align="center">
|
|
4
|
-
<a href="https://pypi.org/project/python-roborock/">
|
|
5
|
-
<img src="https://img.shields.io/pypi/v/python-roborock.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
|
|
6
|
-
</a>
|
|
7
|
-
<img src="https://img.shields.io/pypi/pyversions/python-roborock.svg?style=flat-square&logo=python&logoColor=fff" alt="Supported Python versions">
|
|
8
|
-
<img src="https://img.shields.io/pypi/l/python-roborock.svg?style=flat-square" alt="License">
|
|
9
|
-
</p>
|
|
10
|
-
|
|
11
|
-
Roborock library for online and offline control of your vacuums.
|
|
12
|
-
|
|
13
|
-
## Installation
|
|
14
|
-
|
|
15
|
-
Install this via pip (or your favourite package manager):
|
|
16
|
-
|
|
17
|
-
`pip install python-roborock`
|
|
18
|
-
|
|
19
|
-
## Functionality
|
|
20
|
-
|
|
21
|
-
You can see all of the commands supported [here]("https://python-roborock.readthedocs.io/en/latest/api_commands.html")
|
|
22
|
-
|
|
23
|
-
## Supported devices
|
|
24
|
-
|
|
25
|
-
You can find what devices are supported
|
|
26
|
-
[here]("https://python-roborock.readthedocs.io/en/latest/supported_devices.html").
|
|
27
|
-
Please note this may not immediately contain the latest devices.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
## Credits
|
|
31
|
-
|
|
32
|
-
Thanks @rovo89 for https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7 And thanks @PiotrMachowski for https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor
|
|
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.8.4 → python_roborock-2.9.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|