python-roborock 2.9.2__tar.gz → 2.9.4__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.9.2 → python_roborock-2.9.4}/PKG-INFO +2 -2
- {python_roborock-2.9.2 → python_roborock-2.9.4}/pyproject.toml +6 -2
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/api.py +0 -9
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/cloud_api.py +5 -13
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/roborock_message.py +4 -4
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_1_apis/roborock_client_v1.py +21 -9
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_a01_apis/roborock_client_a01.py +3 -1
- {python_roborock-2.9.2 → python_roborock-2.9.4}/LICENSE +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/README.md +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/__init__.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/cli.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/code_mappings.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/command_cache.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/const.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/containers.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/exceptions.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/local_api.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/protocol.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/py.typed +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/roborock_future.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/util.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 2.9.
|
|
3
|
+
Version: 2.9.4
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
5
|
License: GPL-3.0-only
|
|
6
6
|
Keywords: roborock,vacuum,homeassistant
|
|
@@ -21,7 +21,7 @@ Requires-Dist: aiohttp (>=3.8.2,<4.0.0)
|
|
|
21
21
|
Requires-Dist: async-timeout
|
|
22
22
|
Requires-Dist: click (>=8)
|
|
23
23
|
Requires-Dist: construct (>=2.10.57,<3.0.0)
|
|
24
|
-
Requires-Dist: paho-mqtt (>=1.6.1,<
|
|
24
|
+
Requires-Dist: paho-mqtt (>=1.6.1,<3.0.0)
|
|
25
25
|
Requires-Dist: pycryptodome (>=3.18,<4.0)
|
|
26
26
|
Requires-Dist: pycryptodomex (>=3.18,<4.0) ; sys_platform == "darwin"
|
|
27
27
|
Requires-Dist: vacuum-map-parser-roborock
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "2.9.
|
|
3
|
+
version = "2.9.4"
|
|
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"
|
|
@@ -27,7 +27,7 @@ aiohttp = "^3.8.2"
|
|
|
27
27
|
async-timeout = "*"
|
|
28
28
|
pycryptodome = "^3.18"
|
|
29
29
|
pycryptodomex = {version = "^3.18", markers = "sys_platform == 'darwin'"}
|
|
30
|
-
paho-mqtt = "
|
|
30
|
+
paho-mqtt = ">=1.6.1,<3.0.0"
|
|
31
31
|
construct = "^2.10.57"
|
|
32
32
|
vacuum-map-parser-roborock = "*"
|
|
33
33
|
|
|
@@ -45,6 +45,8 @@ ruff = "*"
|
|
|
45
45
|
codespell = "*"
|
|
46
46
|
pyshark = "^0.6"
|
|
47
47
|
aioresponses = "^0.7.7"
|
|
48
|
+
freezegun = "^1.5.1"
|
|
49
|
+
pytest-timeout = "^2.3.1"
|
|
48
50
|
|
|
49
51
|
[tool.semantic_release]
|
|
50
52
|
branch = "main"
|
|
@@ -70,3 +72,5 @@ select=["E", "F", "UP", "I"]
|
|
|
70
72
|
|
|
71
73
|
[tool.pytest.ini_options]
|
|
72
74
|
asyncio_mode = "auto"
|
|
75
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
76
|
+
timeout = 20
|
|
@@ -21,7 +21,6 @@ from .roborock_future import RoborockFuture
|
|
|
21
21
|
from .roborock_message import (
|
|
22
22
|
RoborockMessage,
|
|
23
23
|
)
|
|
24
|
-
from .roborock_typing import RoborockCommand
|
|
25
24
|
from .util import get_next_int, get_running_loop_or_create_one
|
|
26
25
|
|
|
27
26
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -124,11 +123,3 @@ class RoborockClient(ABC):
|
|
|
124
123
|
@abstractmethod
|
|
125
124
|
async def send_message(self, roborock_message: RoborockMessage):
|
|
126
125
|
"""Send a message to the Roborock device."""
|
|
127
|
-
|
|
128
|
-
@abstractmethod
|
|
129
|
-
async def _send_command(
|
|
130
|
-
self,
|
|
131
|
-
method: RoborockCommand | str,
|
|
132
|
-
params: list | dict | int | None = None,
|
|
133
|
-
):
|
|
134
|
-
"""Send a command to the Roborock device."""
|
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import threading
|
|
5
|
-
import uuid
|
|
6
5
|
from abc import ABC
|
|
7
6
|
from asyncio import Lock
|
|
8
7
|
from typing import Any
|
|
@@ -29,16 +28,10 @@ class _Mqtt(mqtt.Client):
|
|
|
29
28
|
"""
|
|
30
29
|
|
|
31
30
|
_thread: threading.Thread
|
|
32
|
-
_client_id: str
|
|
33
31
|
|
|
34
32
|
def __init__(self) -> None:
|
|
35
33
|
"""Initialize the MQTT client."""
|
|
36
34
|
super().__init__(protocol=mqtt.MQTTv5)
|
|
37
|
-
self.reset_client_id()
|
|
38
|
-
|
|
39
|
-
def reset_client_id(self):
|
|
40
|
-
"""Generate a new client id to make a new session when reconnecting."""
|
|
41
|
-
self._client_id = mqtt.base62(uuid.uuid4().int, padding=22)
|
|
42
35
|
|
|
43
36
|
def maybe_restart_loop(self) -> None:
|
|
44
37
|
"""Ensure that the MQTT loop is running in case it previously exited."""
|
|
@@ -89,6 +82,8 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
89
82
|
self._logger.error(message)
|
|
90
83
|
if connection_queue:
|
|
91
84
|
connection_queue.set_exception(VacuumError(message))
|
|
85
|
+
else:
|
|
86
|
+
self._logger.debug("Failed to notify connect future, not in queue")
|
|
92
87
|
return
|
|
93
88
|
self._logger.info(f"Connected to mqtt {self._mqtt_host}:{self._mqtt_port}")
|
|
94
89
|
topic = f"rr/m/o/{self._mqtt_user}/{self._hashed_user}/{self.device_info.device.duid}"
|
|
@@ -116,8 +111,6 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
116
111
|
try:
|
|
117
112
|
exc = RoborockException(mqtt.error_string(rc)) if rc != mqtt.MQTT_ERR_SUCCESS else None
|
|
118
113
|
super().on_connection_lost(exc)
|
|
119
|
-
if rc == mqtt.MQTT_ERR_PROTOCOL:
|
|
120
|
-
self._mqtt_client.reset_client_id()
|
|
121
114
|
connection_queue = self._waiting_queue.get(DISCONNECT_REQUEST_ID)
|
|
122
115
|
if connection_queue:
|
|
123
116
|
connection_queue.set_result(True)
|
|
@@ -163,10 +156,9 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
163
156
|
async def async_disconnect(self) -> None:
|
|
164
157
|
async with self._mutex:
|
|
165
158
|
if disconnected_future := self.sync_disconnect():
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
raise RoborockException(err) from err
|
|
159
|
+
# There are no errors set on this future
|
|
160
|
+
await disconnected_future
|
|
161
|
+
await self.event_loop.run_in_executor(None, self._mqtt_client.loop_stop)
|
|
170
162
|
|
|
171
163
|
async def async_connect(self) -> None:
|
|
172
164
|
async with self._mutex:
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import math
|
|
5
5
|
import time
|
|
6
|
-
from dataclasses import dataclass
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
7
|
|
|
8
8
|
from roborock import RoborockEnum
|
|
9
9
|
from roborock.util import get_next_int
|
|
@@ -155,10 +155,10 @@ class MessageRetry:
|
|
|
155
155
|
class RoborockMessage:
|
|
156
156
|
protocol: RoborockMessageProtocol
|
|
157
157
|
payload: bytes | None = None
|
|
158
|
-
seq: int = get_next_int(100000, 999999)
|
|
158
|
+
seq: int = field(default_factory=lambda: get_next_int(100000, 999999))
|
|
159
159
|
version: bytes = b"1.0"
|
|
160
|
-
random: int = get_next_int(10000, 99999)
|
|
161
|
-
timestamp: int = math.floor(time.time())
|
|
160
|
+
random: int = field(default_factory=lambda: get_next_int(10000, 99999))
|
|
161
|
+
timestamp: int = field(default_factory=lambda: math.floor(time.time()))
|
|
162
162
|
message_retry: MessageRetry | None = None
|
|
163
163
|
|
|
164
164
|
def get_request_id(self) -> int | None:
|
{python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import math
|
|
5
5
|
import struct
|
|
6
6
|
import time
|
|
7
|
-
from abc import ABC
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
8
|
from collections.abc import Callable, Coroutine
|
|
9
9
|
from typing import Any, TypeVar, final
|
|
10
10
|
|
|
@@ -78,12 +78,15 @@ RT = TypeVar("RT", bound=RoborockBase)
|
|
|
78
78
|
EVICT_TIME = 60
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
_SendCommandT = Callable[[RoborockCommand | str, list | dict | int | None], Any]
|
|
82
|
+
|
|
83
|
+
|
|
81
84
|
class AttributeCache:
|
|
82
|
-
def __init__(self, attribute: RoborockAttribute,
|
|
85
|
+
def __init__(self, attribute: RoborockAttribute, loop: asyncio.AbstractEventLoop, send_command: _SendCommandT):
|
|
83
86
|
self.attribute = attribute
|
|
84
|
-
self.
|
|
87
|
+
self._send_command = send_command
|
|
85
88
|
self.attribute = attribute
|
|
86
|
-
self.task = RepeatableTask(
|
|
89
|
+
self.task = RepeatableTask(loop, self._async_value, EVICT_TIME)
|
|
87
90
|
self._value: Any = None
|
|
88
91
|
self._mutex = asyncio.Lock()
|
|
89
92
|
self.unsupported: bool = False
|
|
@@ -96,7 +99,7 @@ class AttributeCache:
|
|
|
96
99
|
if self.unsupported:
|
|
97
100
|
return None
|
|
98
101
|
try:
|
|
99
|
-
self._value = await self.
|
|
102
|
+
self._value = await self._send_command(self.attribute.get_command, None)
|
|
100
103
|
except UnknownMethodError as err:
|
|
101
104
|
# Limit the amount of times we call unsupported methods
|
|
102
105
|
self.unsupported = True
|
|
@@ -115,21 +118,21 @@ class AttributeCache:
|
|
|
115
118
|
async def update_value(self, params) -> None:
|
|
116
119
|
if self.attribute.set_command is None:
|
|
117
120
|
raise RoborockException(f"{self.attribute.attribute} have no set command")
|
|
118
|
-
response = await self.
|
|
121
|
+
response = await self._send_command(self.attribute.set_command, params)
|
|
119
122
|
await self._async_value()
|
|
120
123
|
return response
|
|
121
124
|
|
|
122
125
|
async def add_value(self, params):
|
|
123
126
|
if self.attribute.add_command is None:
|
|
124
127
|
raise RoborockException(f"{self.attribute.attribute} have no add command")
|
|
125
|
-
response = await self.
|
|
128
|
+
response = await self._send_command(self.attribute.add_command, params)
|
|
126
129
|
await self._async_value()
|
|
127
130
|
return response
|
|
128
131
|
|
|
129
132
|
async def close_value(self, params=None) -> None:
|
|
130
133
|
if self.attribute.close_command is None:
|
|
131
134
|
raise RoborockException(f"{self.attribute.attribute} have no close command")
|
|
132
|
-
response = await self.
|
|
135
|
+
response = await self._send_command(self.attribute.close_command, params)
|
|
133
136
|
await self._async_value()
|
|
134
137
|
return response
|
|
135
138
|
|
|
@@ -153,7 +156,8 @@ class RoborockClientV1(RoborockClient, ABC):
|
|
|
153
156
|
super().__init__(device_info)
|
|
154
157
|
self._status_type: type[Status] = ModelStatus.get(device_info.model, S7MaxVStatus)
|
|
155
158
|
self.cache: dict[CacheableAttribute, AttributeCache] = {
|
|
156
|
-
cacheable_attribute: AttributeCache(attr, self
|
|
159
|
+
cacheable_attribute: AttributeCache(attr, self.event_loop, self._send_command)
|
|
160
|
+
for cacheable_attribute, attr in get_cache_map().items()
|
|
157
161
|
}
|
|
158
162
|
if device_info.device.duid not in self._listeners:
|
|
159
163
|
self._listeners[device_info.device.duid] = ListenerModel({}, self.cache)
|
|
@@ -364,6 +368,14 @@ class RoborockClientV1(RoborockClient, ABC):
|
|
|
364
368
|
)
|
|
365
369
|
return request_id, timestamp, payload
|
|
366
370
|
|
|
371
|
+
@abstractmethod
|
|
372
|
+
async def _send_command(
|
|
373
|
+
self,
|
|
374
|
+
method: RoborockCommand | str,
|
|
375
|
+
params: list | dict | int | None = None,
|
|
376
|
+
) -> Any:
|
|
377
|
+
"""Send a command to the Roborock device."""
|
|
378
|
+
|
|
367
379
|
def on_message_received(self, messages: list[RoborockMessage]) -> None:
|
|
368
380
|
try:
|
|
369
381
|
self._last_device_msg_in = time.monotonic()
|
{python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
@@ -123,11 +123,13 @@ class RoborockClientA01(RoborockClient, ABC):
|
|
|
123
123
|
payload = message.payload
|
|
124
124
|
try:
|
|
125
125
|
payload = unpad(payload, AES.block_size)
|
|
126
|
-
except Exception:
|
|
126
|
+
except Exception as err:
|
|
127
|
+
self._logger.debug("Failed to unpad payload: %s", err)
|
|
127
128
|
continue
|
|
128
129
|
payload_json = json.loads(payload.decode())
|
|
129
130
|
for data_point_number, data_point in payload_json.get("dps").items():
|
|
130
131
|
data_point_protocol: RoborockDyadDataProtocol | RoborockZeoProtocol
|
|
132
|
+
self._logger.debug("received msg with dps, protocol: %s, %s", data_point_number, protocol)
|
|
131
133
|
entries: dict
|
|
132
134
|
if self.category == RoborockCategory.WET_DRY_VAC:
|
|
133
135
|
data_point_protocol = RoborockDyadDataProtocol(int(data_point_number))
|
|
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.9.2 → python_roborock-2.9.4}/roborock/version_1_apis/roborock_local_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-2.9.2 → python_roborock-2.9.4}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|