python-roborock 2.24.0__tar.gz → 2.25.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.24.0 → python_roborock-2.25.0}/PKG-INFO +1 -1
- {python_roborock-2.24.0 → python_roborock-2.25.0}/pyproject.toml +2 -1
- python_roborock-2.25.0/roborock/protocols/v1_protocol.py +91 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/version_1_apis/roborock_client_v1.py +3 -33
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/version_1_apis/roborock_local_client_v1.py +8 -17
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +7 -4
- {python_roborock-2.24.0 → python_roborock-2.25.0}/LICENSE +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/README.md +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/__init__.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/api.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/cli.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/cloud_api.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/code_mappings.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/command_cache.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/const.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/containers.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/devices/README.md +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/devices/device.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/exceptions.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/local_api.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/protocol.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/py.typed +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/roborock_future.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/roborock_message.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/util.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.25.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"
|
|
@@ -76,3 +76,4 @@ select=["E", "F", "UP", "I"]
|
|
|
76
76
|
asyncio_mode = "auto"
|
|
77
77
|
asyncio_default_fixture_loop_scope = "function"
|
|
78
78
|
timeout = 30
|
|
79
|
+
log_format = "%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Roborock V1 Protocol Encoder."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
import time
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from roborock.roborock_message import MessageRetry, RoborockMessage, RoborockMessageProtocol
|
|
13
|
+
from roborock.roborock_typing import RoborockCommand
|
|
14
|
+
from roborock.util import get_next_int
|
|
15
|
+
|
|
16
|
+
CommandType = RoborockCommand | str
|
|
17
|
+
ParamsType = list | dict | int | None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, kw_only=True)
|
|
21
|
+
class SecurityData:
|
|
22
|
+
"""Security data included in the request for some V1 commands."""
|
|
23
|
+
|
|
24
|
+
endpoint: str
|
|
25
|
+
nonce: bytes
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> dict[str, Any]:
|
|
28
|
+
"""Convert security data to a dictionary for sending in the payload."""
|
|
29
|
+
return {"security": {"endpoint": self.endpoint, "nonce": self.nonce.hex().lower()}}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class RequestMessage:
|
|
34
|
+
"""Data structure for v1 RoborockMessage payloads."""
|
|
35
|
+
|
|
36
|
+
method: RoborockCommand | str
|
|
37
|
+
params: ParamsType
|
|
38
|
+
timestamp: int = field(default_factory=lambda: math.floor(time.time()))
|
|
39
|
+
request_id: int = field(default_factory=lambda: get_next_int(10000, 32767))
|
|
40
|
+
|
|
41
|
+
def as_payload(self, security_data: SecurityData | None) -> bytes:
|
|
42
|
+
"""Convert the request arguments to a dictionary."""
|
|
43
|
+
inner = {
|
|
44
|
+
"id": self.request_id,
|
|
45
|
+
"method": self.method,
|
|
46
|
+
"params": self.params or [],
|
|
47
|
+
**(security_data.to_dict() if security_data else {}),
|
|
48
|
+
}
|
|
49
|
+
return bytes(
|
|
50
|
+
json.dumps(
|
|
51
|
+
{
|
|
52
|
+
"dps": {"101": json.dumps(inner, separators=(",", ":"))},
|
|
53
|
+
"t": self.timestamp,
|
|
54
|
+
},
|
|
55
|
+
separators=(",", ":"),
|
|
56
|
+
).encode()
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def create_mqtt_payload_encoder(security_data: SecurityData) -> Callable[[CommandType, ParamsType], RoborockMessage]:
|
|
61
|
+
"""Create a payload encoder for V1 commands over MQTT."""
|
|
62
|
+
|
|
63
|
+
def _get_payload(method: CommandType, params: ParamsType) -> RoborockMessage:
|
|
64
|
+
"""Build the payload for a V1 command."""
|
|
65
|
+
request = RequestMessage(method=method, params=params)
|
|
66
|
+
payload = request.as_payload(security_data) # always secure
|
|
67
|
+
return RoborockMessage(
|
|
68
|
+
timestamp=request.timestamp,
|
|
69
|
+
protocol=RoborockMessageProtocol.RPC_REQUEST,
|
|
70
|
+
payload=payload,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return _get_payload
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def encode_local_payload(method: CommandType, params: ParamsType) -> RoborockMessage:
|
|
77
|
+
"""Encode payload for V1 commands over local connection."""
|
|
78
|
+
|
|
79
|
+
request = RequestMessage(method=method, params=params)
|
|
80
|
+
payload = request.as_payload(security_data=None)
|
|
81
|
+
|
|
82
|
+
message_retry: MessageRetry | None = None
|
|
83
|
+
if method == RoborockCommand.RETRY_REQUEST and isinstance(params, dict):
|
|
84
|
+
message_retry = MessageRetry(method=method, retry_id=params["retry_id"])
|
|
85
|
+
|
|
86
|
+
return RoborockMessage(
|
|
87
|
+
timestamp=request.timestamp,
|
|
88
|
+
protocol=RoborockMessageProtocol.GENERAL_REQUEST,
|
|
89
|
+
payload=payload,
|
|
90
|
+
message_retry=message_retry,
|
|
91
|
+
)
|
{python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import dataclasses
|
|
3
3
|
import json
|
|
4
|
-
import math
|
|
5
4
|
import struct
|
|
6
5
|
import time
|
|
7
6
|
from abc import ABC, abstractmethod
|
|
@@ -54,15 +53,15 @@ from roborock.roborock_message import (
|
|
|
54
53
|
RoborockMessage,
|
|
55
54
|
RoborockMessageProtocol,
|
|
56
55
|
)
|
|
57
|
-
from roborock.util import RepeatableTask,
|
|
56
|
+
from roborock.util import RepeatableTask, unpack_list
|
|
57
|
+
|
|
58
|
+
CUSTOM_COMMANDS = {RoborockCommand.GET_MAP_CALIBRATION}
|
|
58
59
|
|
|
59
60
|
COMMANDS_SECURED = {
|
|
60
61
|
RoborockCommand.GET_MAP_V1,
|
|
61
62
|
RoborockCommand.GET_MULTI_MAP,
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
CUSTOM_COMMANDS = {RoborockCommand.GET_MAP_CALIBRATION}
|
|
65
|
-
|
|
66
65
|
CLOUD_REQUIRED = COMMANDS_SECURED.union(CUSTOM_COMMANDS)
|
|
67
66
|
|
|
68
67
|
WASH_N_FILL_DOCK = [
|
|
@@ -340,35 +339,6 @@ class RoborockClientV1(RoborockClient, ABC):
|
|
|
340
339
|
"""Load the map into the vacuum's memory."""
|
|
341
340
|
await self.send_command(RoborockCommand.LOAD_MULTI_MAP, [map_flag])
|
|
342
341
|
|
|
343
|
-
def _get_payload(
|
|
344
|
-
self,
|
|
345
|
-
method: RoborockCommand | str,
|
|
346
|
-
params: list | dict | int | None = None,
|
|
347
|
-
secured=False,
|
|
348
|
-
):
|
|
349
|
-
timestamp = math.floor(time.time())
|
|
350
|
-
request_id = get_next_int(10000, 32767)
|
|
351
|
-
inner = {
|
|
352
|
-
"id": request_id,
|
|
353
|
-
"method": method,
|
|
354
|
-
"params": params or [],
|
|
355
|
-
}
|
|
356
|
-
if secured:
|
|
357
|
-
inner["security"] = {
|
|
358
|
-
"endpoint": self._endpoint,
|
|
359
|
-
"nonce": self._nonce.hex().lower(),
|
|
360
|
-
}
|
|
361
|
-
payload = bytes(
|
|
362
|
-
json.dumps(
|
|
363
|
-
{
|
|
364
|
-
"dps": {"101": json.dumps(inner, separators=(",", ":"))},
|
|
365
|
-
"t": timestamp,
|
|
366
|
-
},
|
|
367
|
-
separators=(",", ":"),
|
|
368
|
-
).encode()
|
|
369
|
-
)
|
|
370
|
-
return request_id, timestamp, payload
|
|
371
|
-
|
|
372
342
|
@abstractmethod
|
|
373
343
|
async def _send_command(
|
|
374
344
|
self,
|
|
@@ -4,9 +4,10 @@ from roborock.local_api import RoborockLocalClient
|
|
|
4
4
|
|
|
5
5
|
from .. import CommandVacuumError, DeviceData, RoborockCommand, RoborockException
|
|
6
6
|
from ..exceptions import VacuumError
|
|
7
|
-
from ..
|
|
7
|
+
from ..protocols.v1_protocol import encode_local_payload
|
|
8
|
+
from ..roborock_message import RoborockMessage, RoborockMessageProtocol
|
|
8
9
|
from ..util import RoborockLoggerAdapter
|
|
9
|
-
from .roborock_client_v1 import
|
|
10
|
+
from .roborock_client_v1 import CLOUD_REQUIRED, RoborockClientV1
|
|
10
11
|
|
|
11
12
|
_LOGGER = logging.getLogger(__name__)
|
|
12
13
|
|
|
@@ -21,26 +22,16 @@ class RoborockLocalClientV1(RoborockLocalClient, RoborockClientV1):
|
|
|
21
22
|
self.queue_timeout = queue_timeout
|
|
22
23
|
self._logger = RoborockLoggerAdapter(device_data.device.name, _LOGGER)
|
|
23
24
|
|
|
24
|
-
def build_roborock_message(
|
|
25
|
-
self, method: RoborockCommand | str, params: list | dict | int | None = None
|
|
26
|
-
) -> RoborockMessage:
|
|
27
|
-
secured = True if method in COMMANDS_SECURED else False
|
|
28
|
-
request_id, timestamp, payload = self._get_payload(method, params, secured)
|
|
29
|
-
self._logger.debug("Building message id %s for method %s", request_id, method)
|
|
30
|
-
request_protocol = RoborockMessageProtocol.GENERAL_REQUEST
|
|
31
|
-
message_retry: MessageRetry | None = None
|
|
32
|
-
if method == RoborockCommand.RETRY_REQUEST and isinstance(params, dict):
|
|
33
|
-
message_retry = MessageRetry(method=params["method"], retry_id=params["retry_id"])
|
|
34
|
-
return RoborockMessage(
|
|
35
|
-
timestamp=timestamp, protocol=request_protocol, payload=payload, message_retry=message_retry
|
|
36
|
-
)
|
|
37
|
-
|
|
38
25
|
async def _send_command(
|
|
39
26
|
self,
|
|
40
27
|
method: RoborockCommand | str,
|
|
41
28
|
params: list | dict | int | None = None,
|
|
42
29
|
):
|
|
43
|
-
|
|
30
|
+
if method in CLOUD_REQUIRED:
|
|
31
|
+
raise RoborockException(f"Method {method} is not supported over local connection")
|
|
32
|
+
|
|
33
|
+
roborock_message = encode_local_payload(method, params)
|
|
34
|
+
self._logger.debug("Building message id %s for method %s", roborock_message.get_request_id(), method)
|
|
44
35
|
return await self.send_message(roborock_message)
|
|
45
36
|
|
|
46
37
|
async def send_message(self, roborock_message: RoborockMessage):
|
{python_roborock-2.24.0 → python_roborock-2.25.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
@@ -11,6 +11,7 @@ from roborock.cloud_api import RoborockMqttClient
|
|
|
11
11
|
from ..containers import DeviceData, UserData
|
|
12
12
|
from ..exceptions import CommandVacuumError, RoborockException, VacuumError
|
|
13
13
|
from ..protocol import Utils
|
|
14
|
+
from ..protocols.v1_protocol import SecurityData, create_mqtt_payload_encoder
|
|
14
15
|
from ..roborock_message import (
|
|
15
16
|
RoborockMessage,
|
|
16
17
|
RoborockMessageProtocol,
|
|
@@ -36,6 +37,9 @@ class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
|
|
|
36
37
|
RoborockClientV1.__init__(self, device_info, endpoint)
|
|
37
38
|
self.queue_timeout = queue_timeout
|
|
38
39
|
self._logger = RoborockLoggerAdapter(device_info.device.name, _LOGGER)
|
|
40
|
+
self._payload_encoder = create_mqtt_payload_encoder(
|
|
41
|
+
SecurityData(endpoint=self._endpoint, nonce=self._nonce),
|
|
42
|
+
)
|
|
39
43
|
|
|
40
44
|
async def send_message(self, roborock_message: RoborockMessage):
|
|
41
45
|
await self.validate_connection()
|
|
@@ -78,10 +82,9 @@ class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
|
|
|
78
82
|
if method in CUSTOM_COMMANDS:
|
|
79
83
|
# When we have more custom commands do something more complicated here
|
|
80
84
|
return await self._get_calibration_points()
|
|
81
|
-
|
|
82
|
-
self.
|
|
83
|
-
|
|
84
|
-
roborock_message = RoborockMessage(timestamp=timestamp, protocol=request_protocol, payload=payload)
|
|
85
|
+
|
|
86
|
+
roborock_message = self._payload_encoder(method, params)
|
|
87
|
+
self._logger.debug("Building message id %s for method %s", roborock_message.get_request_id, method)
|
|
85
88
|
return await self.send_message(roborock_message)
|
|
86
89
|
|
|
87
90
|
async def _get_calibration_points(self):
|
|
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.24.0 → python_roborock-2.25.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|