python-roborock 2.9.0__py3-none-any.whl → 2.9.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.
- {python_roborock-2.9.0.dist-info → python_roborock-2.9.1.dist-info}/METADATA +1 -2
- {python_roborock-2.9.0.dist-info → python_roborock-2.9.1.dist-info}/RECORD +14 -14
- {python_roborock-2.9.0.dist-info → python_roborock-2.9.1.dist-info}/WHEEL +1 -1
- roborock/api.py +27 -21
- roborock/cloud_api.py +78 -74
- roborock/local_api.py +22 -15
- roborock/roborock_future.py +10 -3
- roborock/version_1_apis/roborock_client_v1.py +16 -15
- roborock/version_1_apis/roborock_local_client_v1.py +21 -8
- roborock/version_1_apis/roborock_mqtt_client_v1.py +21 -15
- roborock/version_a01_apis/roborock_client_a01.py +12 -5
- roborock/version_a01_apis/roborock_mqtt_client_a01.py +12 -6
- {python_roborock-2.9.0.dist-info → python_roborock-2.9.1.dist-info}/LICENSE +0 -0
- {python_roborock-2.9.0.dist-info → python_roborock-2.9.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 2.9.
|
|
3
|
+
Version: 2.9.1
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
|
-
Home-page: https://github.com/humbertogontijo/python-roborock
|
|
6
5
|
License: GPL-3.0-only
|
|
7
6
|
Keywords: roborock,vacuum,homeassistant
|
|
8
7
|
Author: humbertogontijo
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
roborock/__init__.py,sha256=AqfxNGj4wILqL7dooJyQQd6w7o2EW02fMAF51wT9VK8,165
|
|
2
|
-
roborock/api.py,sha256=
|
|
2
|
+
roborock/api.py,sha256=ahk1kUTnSefR7lPaHZJZHF-XfCFyjj6fRBVnQgruts4,4383
|
|
3
3
|
roborock/cli.py,sha256=l4dOt9HZ4TZOZNp_btsNCZEDUihM94Dz173KPtUYdm4,6826
|
|
4
|
-
roborock/cloud_api.py,sha256=
|
|
4
|
+
roborock/cloud_api.py,sha256=rExyj_ZLatzHFmmKlwywmUX0CQxz-a_cSjSrTaQO-cY,7109
|
|
5
5
|
roborock/code_mappings.py,sha256=PTNPwWsuVb0nOkg6GpDklGMYlIB6m9uJXcMdPKVqLDY,18432
|
|
6
6
|
roborock/command_cache.py,sha256=E8e7EYzYFZEmw_cIfhPYZXNpBuSJbIAT9KXnE-e5NPg,7912
|
|
7
7
|
roborock/const.py,sha256=7DtkKJJ2KscgV-BKIGHsMNV_fqfVupt02LQ66r7yOyQ,2632
|
|
8
8
|
roborock/containers.py,sha256=hApLhRbZlp2lMCwlecMXC-vfLMHFq7l9kOVW6rVMC3U,32050
|
|
9
9
|
roborock/exceptions.py,sha256=HAe8bI78n-8-aMD_-9ahTm43xh4lc8KX9qeG5GdqtQc,1962
|
|
10
|
-
roborock/local_api.py,sha256=
|
|
10
|
+
roborock/local_api.py,sha256=RcBp1I7sJdUfDddrd4XeJMB1OVX7L77GDeD-bPaSbIs,4652
|
|
11
11
|
roborock/protocol.py,sha256=VaHCceR6zbe0_kQCnGqfkbWpqaKCq96IqAh5hgSI_Kk,12315
|
|
12
12
|
roborock/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
roborock/roborock_future.py,sha256=
|
|
13
|
+
roborock/roborock_future.py,sha256=W8vm5IA1pzaKLsV2fIR9HyYFiANDLwrs8yE-OqR8ITE,1044
|
|
14
14
|
roborock/roborock_message.py,sha256=5wFwaIu5fGZMG_g6PLEEjUu3Avc3Y_dEevuTeQEP9kE,5486
|
|
15
15
|
roborock/roborock_typing.py,sha256=TTWU0wP-4OjdO2Qfp-uA3CySE8eupLbLJmlxTaSaHWg,23933
|
|
16
16
|
roborock/util.py,sha256=CndrrzhWwMOCP1HRGsOMWtzt7EOkGS5Hr5llcAKMEUA,3946
|
|
17
17
|
roborock/version_1_apis/__init__.py,sha256=BDAkBZPOtpyYUPUa6VaZd7Aaza1hbvM2fIzsjIGD9Vc,183
|
|
18
|
-
roborock/version_1_apis/roborock_client_v1.py,sha256=
|
|
19
|
-
roborock/version_1_apis/roborock_local_client_v1.py,sha256=
|
|
20
|
-
roborock/version_1_apis/roborock_mqtt_client_v1.py,sha256=
|
|
18
|
+
roborock/version_1_apis/roborock_client_v1.py,sha256=ATKmedhvwj1fqqCDKfyEb_zSHb5Dr1tfhncWpFI1AYU,22305
|
|
19
|
+
roborock/version_1_apis/roborock_local_client_v1.py,sha256=SuH0yBCR4USeDg2UJdPnzqrQq8YmH39wc5ZL662IbJY,4022
|
|
20
|
+
roborock/version_1_apis/roborock_mqtt_client_v1.py,sha256=qrDNry25AXAk0ZfCbQJdF2UIa5mKy19dBrNfcbBa460,4373
|
|
21
21
|
roborock/version_a01_apis/__init__.py,sha256=i_WTIX0LUCG93YX3RraNaQYNYBMY-xJI7ncWMdUFQVs,111
|
|
22
|
-
roborock/version_a01_apis/roborock_client_a01.py,sha256=
|
|
23
|
-
roborock/version_a01_apis/roborock_mqtt_client_a01.py,sha256=
|
|
22
|
+
roborock/version_a01_apis/roborock_client_a01.py,sha256=84Z1uR5ZzfGOm-g4Ln4rquiL5SzNOLx0OVHUUzvjLIw,7576
|
|
23
|
+
roborock/version_a01_apis/roborock_mqtt_client_a01.py,sha256=5fpUWnnPTj4yvxUwkh7ANHEJHH6RTgN3jEbVXL06blk,3180
|
|
24
24
|
roborock/web_api.py,sha256=Evpql45Kh40vncbW3khK_CrPwvY1wnL6GGeBAEmG4jM,14860
|
|
25
|
-
python_roborock-2.9.
|
|
26
|
-
python_roborock-2.9.
|
|
27
|
-
python_roborock-2.9.
|
|
28
|
-
python_roborock-2.9.
|
|
29
|
-
python_roborock-2.9.
|
|
25
|
+
python_roborock-2.9.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
26
|
+
python_roborock-2.9.1.dist-info/METADATA,sha256=tuHdDtQgY5MzPxXmbAqR5zrz4Yep19wwuWVIhZ8vxiU,4276
|
|
27
|
+
python_roborock-2.9.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
28
|
+
python_roborock-2.9.1.dist-info/entry_points.txt,sha256=3V_nm8VDR5Wg8m9QlRy9R4olW2lC--6JrDjZJpQpDNI,46
|
|
29
|
+
python_roborock-2.9.1.dist-info/RECORD,,
|
roborock/api.py
CHANGED
|
@@ -7,7 +7,7 @@ import base64
|
|
|
7
7
|
import logging
|
|
8
8
|
import secrets
|
|
9
9
|
import time
|
|
10
|
-
from
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
from .containers import (
|
|
@@ -16,24 +16,27 @@ from .containers import (
|
|
|
16
16
|
from .exceptions import (
|
|
17
17
|
RoborockTimeout,
|
|
18
18
|
UnknownMethodError,
|
|
19
|
-
VacuumError,
|
|
20
19
|
)
|
|
21
20
|
from .roborock_future import RoborockFuture
|
|
22
21
|
from .roborock_message import (
|
|
23
22
|
RoborockMessage,
|
|
24
23
|
)
|
|
25
24
|
from .roborock_typing import RoborockCommand
|
|
26
|
-
from .util import
|
|
25
|
+
from .util import get_next_int, get_running_loop_or_create_one
|
|
27
26
|
|
|
28
27
|
_LOGGER = logging.getLogger(__name__)
|
|
29
28
|
KEEPALIVE = 60
|
|
30
29
|
|
|
31
30
|
|
|
32
|
-
class RoborockClient:
|
|
33
|
-
|
|
31
|
+
class RoborockClient(ABC):
|
|
32
|
+
"""Roborock client base class."""
|
|
33
|
+
|
|
34
|
+
_logger: logging.LoggerAdapter
|
|
35
|
+
|
|
36
|
+
def __init__(self, device_info: DeviceData, queue_timeout: int = 4) -> None:
|
|
37
|
+
"""Initialize RoborockClient."""
|
|
34
38
|
self.event_loop = get_running_loop_or_create_one()
|
|
35
39
|
self.device_info = device_info
|
|
36
|
-
self._endpoint = endpoint
|
|
37
40
|
self._nonce = secrets.token_bytes(16)
|
|
38
41
|
self._waiting_queue: dict[int, RoborockFuture] = {}
|
|
39
42
|
self._last_device_msg_in = time.monotonic()
|
|
@@ -42,7 +45,6 @@ class RoborockClient:
|
|
|
42
45
|
self._diagnostic_data: dict[str, dict[str, Any]] = {
|
|
43
46
|
"misc_info": {"Nonce": base64.b64encode(self._nonce).decode("utf-8")}
|
|
44
47
|
}
|
|
45
|
-
self._logger = RoborockLoggerAdapter(device_info.device.name, _LOGGER)
|
|
46
48
|
self.is_available: bool = True
|
|
47
49
|
self.queue_timeout = queue_timeout
|
|
48
50
|
|
|
@@ -59,17 +61,21 @@ class RoborockClient:
|
|
|
59
61
|
def diagnostic_data(self) -> dict:
|
|
60
62
|
return self._diagnostic_data
|
|
61
63
|
|
|
64
|
+
@abstractmethod
|
|
62
65
|
async def async_connect(self):
|
|
63
|
-
|
|
66
|
+
"""Connect to the Roborock device."""
|
|
64
67
|
|
|
68
|
+
@abstractmethod
|
|
65
69
|
def sync_disconnect(self) -> Any:
|
|
66
|
-
|
|
70
|
+
"""Disconnect from the Roborock device."""
|
|
67
71
|
|
|
72
|
+
@abstractmethod
|
|
68
73
|
async def async_disconnect(self) -> Any:
|
|
69
|
-
|
|
74
|
+
"""Disconnect from the Roborock device."""
|
|
70
75
|
|
|
76
|
+
@abstractmethod
|
|
71
77
|
def on_message_received(self, messages: list[RoborockMessage]) -> None:
|
|
72
|
-
|
|
78
|
+
"""Handle received incoming messages from the device."""
|
|
73
79
|
|
|
74
80
|
def on_connection_lost(self, exc: Exception | None) -> None:
|
|
75
81
|
self._last_disconnection = time.monotonic()
|
|
@@ -89,24 +95,22 @@ class RoborockClient:
|
|
|
89
95
|
await self.async_disconnect()
|
|
90
96
|
await self.async_connect()
|
|
91
97
|
|
|
92
|
-
async def _wait_response(self, request_id: int, queue: RoborockFuture) ->
|
|
98
|
+
async def _wait_response(self, request_id: int, queue: RoborockFuture) -> Any:
|
|
93
99
|
try:
|
|
94
|
-
|
|
100
|
+
response = await queue.async_get(self.queue_timeout)
|
|
95
101
|
if response == "unknown_method":
|
|
96
102
|
raise UnknownMethodError("Unknown method")
|
|
97
|
-
return response
|
|
103
|
+
return response
|
|
98
104
|
except (asyncio.TimeoutError, asyncio.CancelledError):
|
|
99
105
|
raise RoborockTimeout(f"id={request_id} Timeout after {self.queue_timeout} seconds") from None
|
|
100
106
|
finally:
|
|
101
107
|
self._waiting_queue.pop(request_id, None)
|
|
102
108
|
|
|
103
|
-
def _async_response(
|
|
104
|
-
self, request_id: int, protocol_id: int = 0
|
|
105
|
-
) -> Coroutine[Any, Any, tuple[Any, VacuumError | None]]:
|
|
109
|
+
def _async_response(self, request_id: int, protocol_id: int = 0) -> Any:
|
|
106
110
|
queue = RoborockFuture(protocol_id)
|
|
107
111
|
if request_id in self._waiting_queue:
|
|
108
112
|
new_id = get_next_int(10000, 32767)
|
|
109
|
-
|
|
113
|
+
self._logger.warning(
|
|
110
114
|
"Attempting to create a future with an existing id %s (%s)... New id is %s. "
|
|
111
115
|
"Code may not function properly.",
|
|
112
116
|
request_id,
|
|
@@ -115,14 +119,16 @@ class RoborockClient:
|
|
|
115
119
|
)
|
|
116
120
|
request_id = new_id
|
|
117
121
|
self._waiting_queue[request_id] = queue
|
|
118
|
-
return self._wait_response(request_id, queue)
|
|
122
|
+
return asyncio.ensure_future(self._wait_response(request_id, queue))
|
|
119
123
|
|
|
124
|
+
@abstractmethod
|
|
120
125
|
async def send_message(self, roborock_message: RoborockMessage):
|
|
121
|
-
|
|
126
|
+
"""Send a message to the Roborock device."""
|
|
122
127
|
|
|
128
|
+
@abstractmethod
|
|
123
129
|
async def _send_command(
|
|
124
130
|
self,
|
|
125
131
|
method: RoborockCommand | str,
|
|
126
132
|
params: list | dict | int | None = None,
|
|
127
133
|
):
|
|
128
|
-
|
|
134
|
+
"""Send a command to the Roborock device."""
|
roborock/cloud_api.py
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
|
-
import base64
|
|
5
3
|
import logging
|
|
6
4
|
import threading
|
|
7
|
-
import typing
|
|
8
5
|
import uuid
|
|
9
|
-
from
|
|
6
|
+
from abc import ABC
|
|
7
|
+
from asyncio import Lock
|
|
10
8
|
from typing import Any
|
|
11
9
|
from urllib.parse import urlparse
|
|
12
10
|
|
|
@@ -15,31 +13,52 @@ import paho.mqtt.client as mqtt
|
|
|
15
13
|
from .api import KEEPALIVE, RoborockClient
|
|
16
14
|
from .containers import DeviceData, UserData
|
|
17
15
|
from .exceptions import RoborockException, VacuumError
|
|
18
|
-
from .protocol import MessageParser,
|
|
16
|
+
from .protocol import MessageParser, md5hex
|
|
19
17
|
from .roborock_future import RoborockFuture
|
|
20
|
-
from .roborock_message import RoborockMessage
|
|
21
|
-
from .roborock_typing import RoborockCommand
|
|
22
|
-
from .util import RoborockLoggerAdapter
|
|
23
18
|
|
|
24
|
-
if typing.TYPE_CHECKING:
|
|
25
|
-
pass
|
|
26
19
|
_LOGGER = logging.getLogger(__name__)
|
|
27
20
|
CONNECT_REQUEST_ID = 0
|
|
28
21
|
DISCONNECT_REQUEST_ID = 1
|
|
29
22
|
|
|
30
23
|
|
|
31
|
-
class
|
|
24
|
+
class _Mqtt(mqtt.Client):
|
|
25
|
+
"""Internal MQTT client.
|
|
26
|
+
|
|
27
|
+
This is a subclass of the Paho MQTT client that adds some additional functionality
|
|
28
|
+
for error cases where things get stuck.
|
|
29
|
+
"""
|
|
30
|
+
|
|
32
31
|
_thread: threading.Thread
|
|
33
32
|
_client_id: str
|
|
34
33
|
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
"""Initialize the MQTT client."""
|
|
36
|
+
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
|
+
|
|
43
|
+
def maybe_restart_loop(self) -> None:
|
|
44
|
+
"""Ensure that the MQTT loop is running in case it previously exited."""
|
|
45
|
+
if not self._thread or not self._thread.is_alive():
|
|
46
|
+
if self._thread:
|
|
47
|
+
_LOGGER.info("Stopping mqtt loop")
|
|
48
|
+
super().loop_stop()
|
|
49
|
+
_LOGGER.info("Starting mqtt loop")
|
|
50
|
+
super().loop_start()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RoborockMqttClient(RoborockClient, ABC):
|
|
54
|
+
"""Roborock MQTT client base class."""
|
|
55
|
+
|
|
35
56
|
def __init__(self, user_data: UserData, device_info: DeviceData, queue_timeout: int = 10) -> None:
|
|
57
|
+
"""Initialize the Roborock MQTT client."""
|
|
36
58
|
rriot = user_data.rriot
|
|
37
59
|
if rriot is None:
|
|
38
60
|
raise RoborockException("Got no rriot data from user_data")
|
|
39
|
-
|
|
40
|
-
RoborockClient.__init__(self, endpoint, device_info, queue_timeout)
|
|
41
|
-
mqtt.Client.__init__(self, protocol=mqtt.MQTTv5)
|
|
42
|
-
self._logger = RoborockLoggerAdapter(device_info.device.name, _LOGGER)
|
|
61
|
+
RoborockClient.__init__(self, device_info, queue_timeout)
|
|
43
62
|
self._mqtt_user = rriot.u
|
|
44
63
|
self._hashed_user = md5hex(self._mqtt_user + ":" + rriot.k)[2:10]
|
|
45
64
|
url = urlparse(rriot.r.m)
|
|
@@ -48,39 +67,43 @@ class RoborockMqttClient(RoborockClient, mqtt.Client):
|
|
|
48
67
|
self._mqtt_host = str(url.hostname)
|
|
49
68
|
self._mqtt_port = url.port
|
|
50
69
|
self._mqtt_ssl = url.scheme == "ssl"
|
|
70
|
+
|
|
71
|
+
self._mqtt_client = _Mqtt()
|
|
72
|
+
self._mqtt_client.on_connect = self._mqtt_on_connect
|
|
73
|
+
self._mqtt_client.on_message = self._mqtt_on_message
|
|
74
|
+
self._mqtt_client.on_disconnect = self._mqtt_on_disconnect
|
|
51
75
|
if self._mqtt_ssl:
|
|
52
|
-
|
|
76
|
+
self._mqtt_client.tls_set()
|
|
77
|
+
|
|
53
78
|
self._mqtt_password = rriot.s
|
|
54
79
|
self._hashed_password = md5hex(self._mqtt_password + ":" + rriot.k)[16:]
|
|
55
|
-
|
|
56
|
-
self._endpoint = base64.b64encode(Utils.md5(rriot.k.encode())[8:14]).decode()
|
|
80
|
+
self._mqtt_client.username_pw_set(self._hashed_user, self._hashed_password)
|
|
57
81
|
self._waiting_queue: dict[int, RoborockFuture] = {}
|
|
58
82
|
self._mutex = Lock()
|
|
59
|
-
self.update_client_id()
|
|
60
83
|
|
|
61
|
-
def
|
|
84
|
+
def _mqtt_on_connect(self, *args, **kwargs):
|
|
62
85
|
_, __, ___, rc, ____ = args
|
|
63
86
|
connection_queue = self._waiting_queue.get(CONNECT_REQUEST_ID)
|
|
64
87
|
if rc != mqtt.MQTT_ERR_SUCCESS:
|
|
65
88
|
message = f"Failed to connect ({mqtt.error_string(rc)})"
|
|
66
89
|
self._logger.error(message)
|
|
67
90
|
if connection_queue:
|
|
68
|
-
connection_queue.
|
|
91
|
+
connection_queue.set_exception(VacuumError(message))
|
|
69
92
|
return
|
|
70
93
|
self._logger.info(f"Connected to mqtt {self._mqtt_host}:{self._mqtt_port}")
|
|
71
94
|
topic = f"rr/m/o/{self._mqtt_user}/{self._hashed_user}/{self.device_info.device.duid}"
|
|
72
|
-
(result, mid) = self.subscribe(topic)
|
|
95
|
+
(result, mid) = self._mqtt_client.subscribe(topic)
|
|
73
96
|
if result != 0:
|
|
74
97
|
message = f"Failed to subscribe ({mqtt.error_string(rc)})"
|
|
75
98
|
self._logger.error(message)
|
|
76
99
|
if connection_queue:
|
|
77
|
-
connection_queue.
|
|
100
|
+
connection_queue.set_exception(VacuumError(message))
|
|
78
101
|
return
|
|
79
102
|
self._logger.info(f"Subscribed to topic {topic}")
|
|
80
103
|
if connection_queue:
|
|
81
|
-
connection_queue.
|
|
104
|
+
connection_queue.set_result(True)
|
|
82
105
|
|
|
83
|
-
def
|
|
106
|
+
def _mqtt_on_message(self, *args, **kwargs):
|
|
84
107
|
client, __, msg = args
|
|
85
108
|
try:
|
|
86
109
|
messages, _ = MessageParser.parse(msg.payload, local_key=self.device_info.device.local_key)
|
|
@@ -88,93 +111,74 @@ class RoborockMqttClient(RoborockClient, mqtt.Client):
|
|
|
88
111
|
except Exception as ex:
|
|
89
112
|
self._logger.exception(ex)
|
|
90
113
|
|
|
91
|
-
def
|
|
114
|
+
def _mqtt_on_disconnect(self, *args, **kwargs):
|
|
92
115
|
_, __, rc, ___ = args
|
|
93
116
|
try:
|
|
94
117
|
exc = RoborockException(mqtt.error_string(rc)) if rc != mqtt.MQTT_ERR_SUCCESS else None
|
|
95
118
|
super().on_connection_lost(exc)
|
|
96
119
|
if rc == mqtt.MQTT_ERR_PROTOCOL:
|
|
97
|
-
self.
|
|
120
|
+
self._mqtt_client.reset_client_id()
|
|
98
121
|
connection_queue = self._waiting_queue.get(DISCONNECT_REQUEST_ID)
|
|
99
122
|
if connection_queue:
|
|
100
|
-
connection_queue.
|
|
123
|
+
connection_queue.set_result(True)
|
|
101
124
|
except Exception as ex:
|
|
102
125
|
self._logger.exception(ex)
|
|
103
126
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def sync_stop_loop(self) -> None:
|
|
108
|
-
if self._thread:
|
|
109
|
-
self._logger.info("Stopping mqtt loop")
|
|
110
|
-
super().loop_stop()
|
|
111
|
-
|
|
112
|
-
def sync_start_loop(self) -> None:
|
|
113
|
-
if not self._thread or not self._thread.is_alive():
|
|
114
|
-
self.sync_stop_loop()
|
|
115
|
-
self._logger.info("Starting mqtt loop")
|
|
116
|
-
super().loop_start()
|
|
127
|
+
def is_connected(self) -> bool:
|
|
128
|
+
"""Check if the mqtt client is connected."""
|
|
129
|
+
return self._mqtt_client.is_connected()
|
|
117
130
|
|
|
118
|
-
def sync_disconnect(self) ->
|
|
131
|
+
def sync_disconnect(self) -> Any:
|
|
119
132
|
if not self.is_connected():
|
|
120
|
-
return
|
|
133
|
+
return None
|
|
121
134
|
|
|
122
135
|
self._logger.info("Disconnecting from mqtt")
|
|
123
|
-
disconnected_future =
|
|
124
|
-
rc =
|
|
136
|
+
disconnected_future = self._async_response(DISCONNECT_REQUEST_ID)
|
|
137
|
+
rc = self._mqtt_client.disconnect()
|
|
125
138
|
|
|
126
139
|
if rc == mqtt.MQTT_ERR_NO_CONN:
|
|
127
140
|
disconnected_future.cancel()
|
|
128
|
-
return
|
|
141
|
+
return None
|
|
129
142
|
|
|
130
143
|
if rc != mqtt.MQTT_ERR_SUCCESS:
|
|
131
144
|
disconnected_future.cancel()
|
|
132
145
|
raise RoborockException(f"Failed to disconnect ({mqtt.error_string(rc)})")
|
|
133
146
|
|
|
134
|
-
return
|
|
147
|
+
return disconnected_future
|
|
135
148
|
|
|
136
|
-
def sync_connect(self) ->
|
|
149
|
+
def sync_connect(self) -> Any:
|
|
137
150
|
if self.is_connected():
|
|
138
|
-
self.
|
|
139
|
-
return
|
|
151
|
+
self._mqtt_client.maybe_restart_loop()
|
|
152
|
+
return None
|
|
140
153
|
|
|
141
154
|
if self._mqtt_port is None or self._mqtt_host is None:
|
|
142
155
|
raise RoborockException("Mqtt information was not entered. Cannot connect.")
|
|
143
156
|
|
|
144
157
|
self._logger.debug("Connecting to mqtt")
|
|
145
|
-
connected_future =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return True, connected_future
|
|
158
|
+
connected_future = self._async_response(CONNECT_REQUEST_ID)
|
|
159
|
+
self._mqtt_client.connect(host=self._mqtt_host, port=self._mqtt_port, keepalive=KEEPALIVE)
|
|
160
|
+
self._mqtt_client.maybe_restart_loop()
|
|
161
|
+
return connected_future
|
|
150
162
|
|
|
151
163
|
async def async_disconnect(self) -> None:
|
|
152
164
|
async with self._mutex:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
165
|
+
if disconnected_future := self.sync_disconnect():
|
|
166
|
+
try:
|
|
167
|
+
await disconnected_future
|
|
168
|
+
except VacuumError as err:
|
|
157
169
|
raise RoborockException(err) from err
|
|
158
170
|
|
|
159
171
|
async def async_connect(self) -> None:
|
|
160
172
|
async with self._mutex:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
173
|
+
if connected_future := self.sync_connect():
|
|
174
|
+
try:
|
|
175
|
+
await connected_future
|
|
176
|
+
except VacuumError as err:
|
|
165
177
|
raise RoborockException(err) from err
|
|
166
178
|
|
|
167
179
|
def _send_msg_raw(self, msg: bytes) -> None:
|
|
168
|
-
info = self.publish(
|
|
180
|
+
info = self._mqtt_client.publish(
|
|
181
|
+
f"rr/m/i/{self._mqtt_user}/{self._hashed_user}/{self.device_info.device.duid}", msg
|
|
182
|
+
)
|
|
169
183
|
if info.rc != mqtt.MQTT_ERR_SUCCESS:
|
|
170
184
|
raise RoborockException(f"Failed to publish ({mqtt.error_string(info.rc)})")
|
|
171
|
-
|
|
172
|
-
async def send_message(self, roborock_message: RoborockMessage):
|
|
173
|
-
raise NotImplementedError
|
|
174
|
-
|
|
175
|
-
async def _send_command(
|
|
176
|
-
self,
|
|
177
|
-
method: RoborockCommand | str,
|
|
178
|
-
params: list | dict | int | None = None,
|
|
179
|
-
):
|
|
180
|
-
raise NotImplementedError
|
roborock/local_api.py
CHANGED
|
@@ -2,7 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
from abc import ABC
|
|
5
6
|
from asyncio import Lock, TimerHandle, Transport
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from dataclasses import dataclass
|
|
6
9
|
|
|
7
10
|
import async_timeout
|
|
8
11
|
|
|
@@ -11,14 +14,23 @@ from .api import RoborockClient
|
|
|
11
14
|
from .exceptions import RoborockConnectionException, RoborockException
|
|
12
15
|
from .protocol import MessageParser
|
|
13
16
|
from .roborock_message import RoborockMessage, RoborockMessageProtocol
|
|
14
|
-
from .roborock_typing import RoborockCommand
|
|
15
|
-
from .util import RoborockLoggerAdapter
|
|
16
17
|
|
|
17
18
|
_LOGGER = logging.getLogger(__name__)
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
@dataclass
|
|
22
|
+
class _LocalProtocol(asyncio.Protocol):
|
|
23
|
+
"""Callbacks for the Roborock local client transport."""
|
|
24
|
+
|
|
25
|
+
messages_cb: Callable[[bytes], None]
|
|
26
|
+
connection_lost_cb: Callable[[Exception | None], None]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RoborockLocalClient(RoborockClient, ABC):
|
|
30
|
+
"""Roborock local client base class."""
|
|
31
|
+
|
|
21
32
|
def __init__(self, device_data: DeviceData, queue_timeout: int = 4):
|
|
33
|
+
"""Initialize the Roborock local client."""
|
|
22
34
|
if device_data.host is None:
|
|
23
35
|
raise RoborockException("Host is required")
|
|
24
36
|
self.host = device_data.host
|
|
@@ -28,17 +40,19 @@ class RoborockLocalClient(RoborockClient, asyncio.Protocol):
|
|
|
28
40
|
self.transport: Transport | None = None
|
|
29
41
|
self._mutex = Lock()
|
|
30
42
|
self.keep_alive_task: TimerHandle | None = None
|
|
31
|
-
self
|
|
32
|
-
|
|
43
|
+
RoborockClient.__init__(self, device_data, queue_timeout)
|
|
44
|
+
self._local_protocol = _LocalProtocol(self._data_received, self._connection_lost)
|
|
33
45
|
|
|
34
|
-
def
|
|
46
|
+
def _data_received(self, message):
|
|
47
|
+
"""Called when data is received from the transport."""
|
|
35
48
|
if self.remaining:
|
|
36
49
|
message = self.remaining + message
|
|
37
50
|
self.remaining = b""
|
|
38
51
|
parser_msg, self.remaining = MessageParser.parse(message, local_key=self.device_info.device.local_key)
|
|
39
52
|
self.on_message_received(parser_msg)
|
|
40
53
|
|
|
41
|
-
def
|
|
54
|
+
def _connection_lost(self, exc: Exception | None):
|
|
55
|
+
"""Called when the transport connection is lost."""
|
|
42
56
|
self.sync_disconnect()
|
|
43
57
|
self.on_connection_lost(exc)
|
|
44
58
|
|
|
@@ -61,7 +75,7 @@ class RoborockLocalClient(RoborockClient, asyncio.Protocol):
|
|
|
61
75
|
async with async_timeout.timeout(self.queue_timeout):
|
|
62
76
|
self._logger.debug(f"Connecting to {self.host}")
|
|
63
77
|
self.transport, _ = await self.event_loop.create_connection( # type: ignore
|
|
64
|
-
lambda: self, self.host, 58867
|
|
78
|
+
lambda: self._local_protocol, self.host, 58867
|
|
65
79
|
)
|
|
66
80
|
self._logger.info(f"Connected to {self.host}")
|
|
67
81
|
should_ping = True
|
|
@@ -107,13 +121,6 @@ class RoborockLocalClient(RoborockClient, asyncio.Protocol):
|
|
|
107
121
|
)
|
|
108
122
|
)
|
|
109
123
|
|
|
110
|
-
async def _send_command(
|
|
111
|
-
self,
|
|
112
|
-
method: RoborockCommand | str,
|
|
113
|
-
params: list | dict | int | None = None,
|
|
114
|
-
):
|
|
115
|
-
raise NotImplementedError
|
|
116
|
-
|
|
117
124
|
def _send_msg_raw(self, data: bytes):
|
|
118
125
|
try:
|
|
119
126
|
if not self.transport:
|
roborock/roborock_future.py
CHANGED
|
@@ -14,12 +14,19 @@ class RoborockFuture:
|
|
|
14
14
|
self.fut: Future = Future()
|
|
15
15
|
self.loop = self.fut.get_loop()
|
|
16
16
|
|
|
17
|
-
def
|
|
17
|
+
def _set_result(self, item: Any) -> None:
|
|
18
18
|
if not self.fut.cancelled():
|
|
19
19
|
self.fut.set_result(item)
|
|
20
20
|
|
|
21
|
-
def
|
|
22
|
-
self.loop.call_soon_threadsafe(self.
|
|
21
|
+
def set_result(self, item: Any) -> None:
|
|
22
|
+
self.loop.call_soon_threadsafe(self._set_result, item)
|
|
23
|
+
|
|
24
|
+
def _set_exception(self, exc: VacuumError) -> None:
|
|
25
|
+
if not self.fut.cancelled():
|
|
26
|
+
self.fut.set_exception(exc)
|
|
27
|
+
|
|
28
|
+
def set_exception(self, exc: VacuumError) -> None:
|
|
29
|
+
self.loop.call_soon_threadsafe(self._set_exception, exc)
|
|
23
30
|
|
|
24
31
|
async def async_get(self, timeout: float | int) -> tuple[Any, VacuumError | None]:
|
|
25
32
|
try:
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import math
|
|
5
5
|
import struct
|
|
6
6
|
import time
|
|
7
|
+
from abc import ABC
|
|
7
8
|
from collections.abc import Callable, Coroutine
|
|
8
9
|
from typing import Any, TypeVar, final
|
|
9
10
|
|
|
@@ -142,19 +143,22 @@ class ListenerModel:
|
|
|
142
143
|
cache: dict[CacheableAttribute, AttributeCache]
|
|
143
144
|
|
|
144
145
|
|
|
145
|
-
class RoborockClientV1(RoborockClient):
|
|
146
|
+
class RoborockClientV1(RoborockClient, ABC):
|
|
147
|
+
"""Roborock client base class for version 1 devices."""
|
|
148
|
+
|
|
146
149
|
_listeners: dict[str, ListenerModel] = {}
|
|
147
150
|
|
|
148
|
-
def __init__(self, device_info: DeviceData,
|
|
149
|
-
|
|
151
|
+
def __init__(self, device_info: DeviceData, endpoint: str):
|
|
152
|
+
"""Initializes the Roborock client."""
|
|
153
|
+
super().__init__(device_info)
|
|
150
154
|
self._status_type: type[Status] = ModelStatus.get(device_info.model, S7MaxVStatus)
|
|
151
|
-
self._logger = logger
|
|
152
155
|
self.cache: dict[CacheableAttribute, AttributeCache] = {
|
|
153
156
|
cacheable_attribute: AttributeCache(attr, self) for cacheable_attribute, attr in get_cache_map().items()
|
|
154
157
|
}
|
|
155
158
|
if device_info.device.duid not in self._listeners:
|
|
156
159
|
self._listeners[device_info.device.duid] = ListenerModel({}, self.cache)
|
|
157
160
|
self.listener_model = self._listeners[device_info.device.duid]
|
|
161
|
+
self._endpoint = endpoint
|
|
158
162
|
|
|
159
163
|
def release(self):
|
|
160
164
|
super().release()
|
|
@@ -378,20 +382,17 @@ class RoborockClientV1(RoborockClient):
|
|
|
378
382
|
if queue and queue.protocol == protocol:
|
|
379
383
|
error = data_point_response.get("error")
|
|
380
384
|
if error:
|
|
381
|
-
queue.
|
|
382
|
-
(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
error.get("message"),
|
|
387
|
-
),
|
|
388
|
-
)
|
|
385
|
+
queue.set_exception(
|
|
386
|
+
VacuumError(
|
|
387
|
+
error.get("code"),
|
|
388
|
+
error.get("message"),
|
|
389
|
+
),
|
|
389
390
|
)
|
|
390
391
|
else:
|
|
391
392
|
result = data_point_response.get("result")
|
|
392
393
|
if isinstance(result, list) and len(result) == 1:
|
|
393
394
|
result = result[0]
|
|
394
|
-
queue.
|
|
395
|
+
queue.set_result(result)
|
|
395
396
|
else:
|
|
396
397
|
self._logger.debug("Received response for unknown request id %s", request_id)
|
|
397
398
|
else:
|
|
@@ -451,13 +452,13 @@ class RoborockClientV1(RoborockClient):
|
|
|
451
452
|
if queue:
|
|
452
453
|
if isinstance(decompressed, list):
|
|
453
454
|
decompressed = decompressed[0]
|
|
454
|
-
queue.
|
|
455
|
+
queue.set_result(decompressed)
|
|
455
456
|
else:
|
|
456
457
|
self._logger.debug("Received response for unknown request id %s", request_id)
|
|
457
458
|
else:
|
|
458
459
|
queue = self._waiting_queue.get(data.seq)
|
|
459
460
|
if queue:
|
|
460
|
-
queue.
|
|
461
|
+
queue.set_result(data.payload)
|
|
461
462
|
else:
|
|
462
463
|
self._logger.debug("Received response for unknown request id %s", data.seq)
|
|
463
464
|
except Exception as ex:
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import logging
|
|
2
2
|
|
|
3
3
|
from roborock.local_api import RoborockLocalClient
|
|
4
4
|
|
|
5
5
|
from .. import CommandVacuumError, DeviceData, RoborockCommand, RoborockException
|
|
6
|
+
from ..exceptions import VacuumError
|
|
6
7
|
from ..protocol import MessageParser
|
|
7
8
|
from ..roborock_message import MessageRetry, RoborockMessage, RoborockMessageProtocol
|
|
9
|
+
from ..util import RoborockLoggerAdapter
|
|
8
10
|
from .roborock_client_v1 import COMMANDS_SECURED, RoborockClientV1
|
|
9
11
|
|
|
12
|
+
_LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
class RoborockLocalClientV1(RoborockLocalClient, RoborockClientV1):
|
|
16
|
+
"""Roborock local client for v1 devices."""
|
|
17
|
+
|
|
12
18
|
def __init__(self, device_data: DeviceData, queue_timeout: int = 4):
|
|
19
|
+
"""Initialize the Roborock local client."""
|
|
13
20
|
RoborockLocalClient.__init__(self, device_data, queue_timeout)
|
|
14
|
-
RoborockClientV1.__init__(self, device_data,
|
|
21
|
+
RoborockClientV1.__init__(self, device_data, "abc")
|
|
22
|
+
self._logger = RoborockLoggerAdapter(device_data.device.name, _LOGGER)
|
|
15
23
|
|
|
16
24
|
def build_roborock_message(
|
|
17
25
|
self, method: RoborockCommand | str, params: list | dict | int | None = None
|
|
@@ -53,16 +61,21 @@ class RoborockLocalClientV1(RoborockLocalClient, RoborockClientV1):
|
|
|
53
61
|
if method:
|
|
54
62
|
self._logger.debug(f"id={request_id} Requesting method {method} with {params}")
|
|
55
63
|
# Send the command to the Roborock device
|
|
56
|
-
async_response =
|
|
64
|
+
async_response = self._async_response(request_id, response_protocol)
|
|
57
65
|
self._send_msg_raw(msg)
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
diagnostic_key = method if method is not None else "unknown"
|
|
67
|
+
try:
|
|
68
|
+
response = await async_response
|
|
69
|
+
except VacuumError as err:
|
|
70
|
+
self._diagnostic_data[diagnostic_key] = {
|
|
71
|
+
"params": roborock_message.get_params(),
|
|
72
|
+
"error": err,
|
|
73
|
+
}
|
|
74
|
+
raise CommandVacuumError(method, err) from err
|
|
75
|
+
self._diagnostic_data[diagnostic_key] = {
|
|
60
76
|
"params": roborock_message.get_params(),
|
|
61
77
|
"response": response,
|
|
62
|
-
"error": err,
|
|
63
78
|
}
|
|
64
|
-
if err:
|
|
65
|
-
raise CommandVacuumError(method, err) from err
|
|
66
79
|
if roborock_message.protocol == RoborockMessageProtocol.GENERAL_REQUEST:
|
|
67
80
|
self._logger.debug(f"id={request_id} Response from method {roborock_message.get_method()}: {response}")
|
|
68
81
|
if response == "retry":
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import base64
|
|
2
|
+
import logging
|
|
3
3
|
|
|
4
|
-
import paho.mqtt.client as mqtt
|
|
5
4
|
from vacuum_map_parser_base.config.color import ColorsPalette
|
|
6
5
|
from vacuum_map_parser_base.config.image_config import ImageConfig
|
|
7
6
|
from vacuum_map_parser_base.config.size import Sizes
|
|
@@ -10,30 +9,32 @@ from vacuum_map_parser_roborock.map_data_parser import RoborockMapDataParser
|
|
|
10
9
|
from roborock.cloud_api import RoborockMqttClient
|
|
11
10
|
|
|
12
11
|
from ..containers import DeviceData, UserData
|
|
13
|
-
from ..exceptions import CommandVacuumError, RoborockException
|
|
12
|
+
from ..exceptions import CommandVacuumError, RoborockException, VacuumError
|
|
14
13
|
from ..protocol import MessageParser, Utils
|
|
15
14
|
from ..roborock_message import (
|
|
16
15
|
RoborockMessage,
|
|
17
16
|
RoborockMessageProtocol,
|
|
18
17
|
)
|
|
19
18
|
from ..roborock_typing import RoborockCommand
|
|
19
|
+
from ..util import RoborockLoggerAdapter
|
|
20
20
|
from .roborock_client_v1 import COMMANDS_SECURED, CUSTOM_COMMANDS, RoborockClientV1
|
|
21
21
|
|
|
22
|
+
_LOGGER = logging.getLogger(__name__)
|
|
23
|
+
|
|
22
24
|
|
|
23
25
|
class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
|
|
26
|
+
"""Roborock mqtt client for v1 devices."""
|
|
27
|
+
|
|
24
28
|
def __init__(self, user_data: UserData, device_info: DeviceData, queue_timeout: int = 10) -> None:
|
|
29
|
+
"""Initialize the Roborock mqtt client."""
|
|
25
30
|
rriot = user_data.rriot
|
|
26
31
|
if rriot is None:
|
|
27
32
|
raise RoborockException("Got no rriot data from user_data")
|
|
28
33
|
endpoint = base64.b64encode(Utils.md5(rriot.k.encode())[8:14]).decode()
|
|
29
34
|
|
|
30
35
|
RoborockMqttClient.__init__(self, user_data, device_info, queue_timeout)
|
|
31
|
-
RoborockClientV1.__init__(self, device_info,
|
|
32
|
-
|
|
33
|
-
def _send_msg_raw(self, msg: bytes) -> None:
|
|
34
|
-
info = self.publish(f"rr/m/i/{self._mqtt_user}/{self._hashed_user}/{self.device_info.device.duid}", msg)
|
|
35
|
-
if info.rc != mqtt.MQTT_ERR_SUCCESS:
|
|
36
|
-
raise RoborockException(f"Failed to publish ({mqtt.error_string(info.rc)})")
|
|
36
|
+
RoborockClientV1.__init__(self, device_info, endpoint)
|
|
37
|
+
self._logger = RoborockLoggerAdapter(device_info.device.name, _LOGGER)
|
|
37
38
|
|
|
38
39
|
async def send_message(self, roborock_message: RoborockMessage):
|
|
39
40
|
await self.validate_connection()
|
|
@@ -49,16 +50,21 @@ class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
|
|
|
49
50
|
local_key = self.device_info.device.local_key
|
|
50
51
|
msg = MessageParser.build(roborock_message, local_key, False)
|
|
51
52
|
self._logger.debug(f"id={request_id} Requesting method {method} with {params}")
|
|
52
|
-
async_response =
|
|
53
|
+
async_response = self._async_response(request_id, response_protocol)
|
|
53
54
|
self._send_msg_raw(msg)
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
diagnostic_key = method if method is not None else "unknown"
|
|
56
|
+
try:
|
|
57
|
+
response = await async_response
|
|
58
|
+
except VacuumError as err:
|
|
59
|
+
self._diagnostic_data[diagnostic_key] = {
|
|
60
|
+
"params": roborock_message.get_params(),
|
|
61
|
+
"error": err,
|
|
62
|
+
}
|
|
63
|
+
raise CommandVacuumError(method, err) from err
|
|
64
|
+
self._diagnostic_data[diagnostic_key] = {
|
|
56
65
|
"params": roborock_message.get_params(),
|
|
57
66
|
"response": response,
|
|
58
|
-
"error": err,
|
|
59
67
|
}
|
|
60
|
-
if err:
|
|
61
|
-
raise CommandVacuumError(method, err) from err
|
|
62
68
|
if response_protocol == RoborockMessageProtocol.MAP_RESPONSE:
|
|
63
69
|
self._logger.debug(f"id={request_id} Response from {method}: {len(response)} bytes")
|
|
64
70
|
else:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import json
|
|
3
|
+
import logging
|
|
3
4
|
import typing
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
4
6
|
from collections.abc import Callable
|
|
5
7
|
from datetime import time
|
|
6
8
|
|
|
@@ -38,6 +40,8 @@ from roborock.roborock_message import (
|
|
|
38
40
|
RoborockZeoProtocol,
|
|
39
41
|
)
|
|
40
42
|
|
|
43
|
+
_LOGGER = logging.getLogger(__name__)
|
|
44
|
+
|
|
41
45
|
|
|
42
46
|
@dataclasses.dataclass
|
|
43
47
|
class A01ProtocolCacheEntry:
|
|
@@ -101,9 +105,12 @@ zeo_data_protocol_entries = {
|
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
|
|
104
|
-
class RoborockClientA01(RoborockClient):
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
class RoborockClientA01(RoborockClient, ABC):
|
|
109
|
+
"""Roborock client base class for A01 devices."""
|
|
110
|
+
|
|
111
|
+
def __init__(self, device_info: DeviceData, category: RoborockCategory, queue_timeout: int = 4):
|
|
112
|
+
"""Initialize the Roborock client."""
|
|
113
|
+
super().__init__(device_info, queue_timeout)
|
|
107
114
|
self.category = category
|
|
108
115
|
|
|
109
116
|
def on_message_received(self, messages: list[RoborockMessage]) -> None:
|
|
@@ -135,10 +142,10 @@ class RoborockClientA01(RoborockClient):
|
|
|
135
142
|
converted_response = entries[data_point_protocol].post_process_fn(data_point)
|
|
136
143
|
queue = self._waiting_queue.get(int(data_point_number))
|
|
137
144
|
if queue and queue.protocol == protocol:
|
|
138
|
-
queue.
|
|
145
|
+
queue.set_result(converted_response)
|
|
139
146
|
|
|
147
|
+
@abstractmethod
|
|
140
148
|
async def update_values(
|
|
141
149
|
self, dyad_data_protocols: list[RoborockDyadDataProtocol | RoborockZeoProtocol]
|
|
142
150
|
) -> dict[RoborockDyadDataProtocol | RoborockZeoProtocol, typing.Any]:
|
|
143
151
|
"""This should handle updating for each given protocol."""
|
|
144
|
-
raise NotImplementedError
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import base64
|
|
3
2
|
import json
|
|
3
|
+
import logging
|
|
4
4
|
import typing
|
|
5
5
|
|
|
6
6
|
from Crypto.Cipher import AES
|
|
@@ -9,7 +9,7 @@ from Crypto.Util.Padding import pad, unpad
|
|
|
9
9
|
from roborock.cloud_api import RoborockMqttClient
|
|
10
10
|
from roborock.containers import DeviceData, RoborockCategory, UserData
|
|
11
11
|
from roborock.exceptions import RoborockException
|
|
12
|
-
from roborock.protocol import MessageParser
|
|
12
|
+
from roborock.protocol import MessageParser
|
|
13
13
|
from roborock.roborock_message import (
|
|
14
14
|
RoborockDyadDataProtocol,
|
|
15
15
|
RoborockMessage,
|
|
@@ -17,20 +17,26 @@ from roborock.roborock_message import (
|
|
|
17
17
|
RoborockZeoProtocol,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
+
from ..util import RoborockLoggerAdapter
|
|
20
21
|
from .roborock_client_a01 import RoborockClientA01
|
|
21
22
|
|
|
23
|
+
_LOGGER = logging.getLogger(__name__)
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
class RoborockMqttClientA01(RoborockMqttClient, RoborockClientA01):
|
|
27
|
+
"""Roborock mqtt client for A01 devices."""
|
|
28
|
+
|
|
24
29
|
def __init__(
|
|
25
30
|
self, user_data: UserData, device_info: DeviceData, category: RoborockCategory, queue_timeout: int = 10
|
|
26
31
|
) -> None:
|
|
32
|
+
"""Initialize the Roborock mqtt client."""
|
|
27
33
|
rriot = user_data.rriot
|
|
28
34
|
if rriot is None:
|
|
29
35
|
raise RoborockException("Got no rriot data from user_data")
|
|
30
|
-
endpoint = base64.b64encode(Utils.md5(rriot.k.encode())[8:14]).decode()
|
|
31
36
|
|
|
32
37
|
RoborockMqttClient.__init__(self, user_data, device_info, queue_timeout)
|
|
33
|
-
RoborockClientA01.__init__(self,
|
|
38
|
+
RoborockClientA01.__init__(self, device_info, category, queue_timeout)
|
|
39
|
+
self._logger = RoborockLoggerAdapter(device_info.device.name, _LOGGER)
|
|
34
40
|
|
|
35
41
|
async def send_message(self, roborock_message: RoborockMessage):
|
|
36
42
|
await self.validate_connection()
|
|
@@ -43,7 +49,7 @@ class RoborockMqttClientA01(RoborockMqttClient, RoborockClientA01):
|
|
|
43
49
|
futures = []
|
|
44
50
|
if "10000" in payload["dps"]:
|
|
45
51
|
for dps in json.loads(payload["dps"]["10000"]):
|
|
46
|
-
futures.append(
|
|
52
|
+
futures.append(self._async_response(dps, response_protocol))
|
|
47
53
|
self._send_msg_raw(m)
|
|
48
54
|
responses = await asyncio.gather(*futures, return_exceptions=True)
|
|
49
55
|
dps_responses: dict[int, typing.Any] = {}
|
|
@@ -54,7 +60,7 @@ class RoborockMqttClientA01(RoborockMqttClient, RoborockClientA01):
|
|
|
54
60
|
self._logger.warning("Timed out get req for %s after %s s", dps, self.queue_timeout)
|
|
55
61
|
dps_responses[dps] = None
|
|
56
62
|
else:
|
|
57
|
-
dps_responses[dps] = response
|
|
63
|
+
dps_responses[dps] = response
|
|
58
64
|
return dps_responses
|
|
59
65
|
|
|
60
66
|
async def update_values(
|
|
File without changes
|
|
File without changes
|