python-roborock 2.45.0__tar.gz → 2.46.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.45.0 → python_roborock-2.46.0}/PKG-INFO +1 -1
- {python_roborock-2.45.0 → python_roborock-2.46.0}/pyproject.toml +1 -1
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/api.py +1 -7
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/cloud_api.py +92 -13
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/exceptions.py +1 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_local_client_v1.py +7 -1
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +1 -1
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +1 -1
- {python_roborock-2.45.0 → python_roborock-2.46.0}/LICENSE +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/README.md +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/__init__.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/b01_containers.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/callbacks.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/clean_modes.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/cli.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/code_mappings.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/command_cache.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/const.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/containers.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/device_features.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/README.md +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/cache.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/channel.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/device.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/traits/b01/props.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/traits/clean_summary.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/traits/dnd.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/traits/dyad.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/traits/sound_volume.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/traits/status.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/traits/trait.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/traits/zeo.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/devices/v1_rpc_channel.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/protocol.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/py.typed +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/roborock_future.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/roborock_message.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/util.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/web_api.py +0 -0
|
@@ -23,7 +23,7 @@ from .roborock_message import (
|
|
|
23
23
|
from .util import get_next_int
|
|
24
24
|
|
|
25
25
|
_LOGGER = logging.getLogger(__name__)
|
|
26
|
-
KEEPALIVE =
|
|
26
|
+
KEEPALIVE = 70
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class RoborockClient(ABC):
|
|
@@ -78,12 +78,6 @@ class RoborockClient(ABC):
|
|
|
78
78
|
return False
|
|
79
79
|
return True
|
|
80
80
|
|
|
81
|
-
async def validate_connection(self) -> None:
|
|
82
|
-
if not self.should_keepalive():
|
|
83
|
-
self._logger.info("Resetting Roborock connection due to keepalive timeout")
|
|
84
|
-
await self.async_disconnect()
|
|
85
|
-
await self.async_connect()
|
|
86
|
-
|
|
87
81
|
async def _wait_response(self, request_id: int, queue: RoborockFuture) -> Any:
|
|
88
82
|
try:
|
|
89
83
|
response = await queue.async_get(self.queue_timeout)
|
|
@@ -8,6 +8,10 @@ from asyncio import Lock
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
import paho.mqtt.client as mqtt
|
|
11
|
+
from paho.mqtt.enums import MQTTErrorCode
|
|
12
|
+
|
|
13
|
+
# Mypy is not seeing this for some reason. It wants me to use the depreciated ReasonCodes
|
|
14
|
+
from paho.mqtt.reasoncodes import ReasonCode # type: ignore
|
|
11
15
|
|
|
12
16
|
from .api import KEEPALIVE, RoborockClient
|
|
13
17
|
from .containers import DeviceData, UserData
|
|
@@ -67,7 +71,8 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
67
71
|
self._mqtt_client = _Mqtt()
|
|
68
72
|
self._mqtt_client.on_connect = self._mqtt_on_connect
|
|
69
73
|
self._mqtt_client.on_message = self._mqtt_on_message
|
|
70
|
-
|
|
74
|
+
# Due to the incorrect ReasonCode, it is confused by typing
|
|
75
|
+
self._mqtt_client.on_disconnect = self._mqtt_on_disconnect # type: ignore
|
|
71
76
|
if mqtt_params.tls:
|
|
72
77
|
self._mqtt_client.tls_set()
|
|
73
78
|
|
|
@@ -76,12 +81,20 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
76
81
|
self._mutex = Lock()
|
|
77
82
|
self._decoder: Decoder = create_mqtt_decoder(device_info.device.local_key)
|
|
78
83
|
self._encoder: Encoder = create_mqtt_encoder(device_info.device.local_key)
|
|
84
|
+
self.received_message_since_last_disconnect = False
|
|
85
|
+
self._topic = f"rr/m/o/{self._mqtt_user}/{self._hashed_user}/{self.device_info.device.duid}"
|
|
79
86
|
|
|
80
|
-
def _mqtt_on_connect(
|
|
81
|
-
|
|
87
|
+
def _mqtt_on_connect(
|
|
88
|
+
self,
|
|
89
|
+
client: mqtt.Client,
|
|
90
|
+
userdata: object,
|
|
91
|
+
flags: dict[str, int],
|
|
92
|
+
rc: ReasonCode,
|
|
93
|
+
properties: mqtt.Properties | None = None,
|
|
94
|
+
):
|
|
82
95
|
connection_queue = self._waiting_queue.get(CONNECT_REQUEST_ID)
|
|
83
|
-
if rc
|
|
84
|
-
message = f"Failed to connect ({
|
|
96
|
+
if rc.is_failure:
|
|
97
|
+
message = f"Failed to connect ({rc})"
|
|
85
98
|
self._logger.error(message)
|
|
86
99
|
if connection_queue:
|
|
87
100
|
connection_queue.set_exception(VacuumError(message))
|
|
@@ -89,19 +102,19 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
89
102
|
self._logger.debug("Failed to notify connect future, not in queue")
|
|
90
103
|
return
|
|
91
104
|
self._logger.info(f"Connected to mqtt {self._mqtt_host}:{self._mqtt_port}")
|
|
92
|
-
|
|
93
|
-
(result, mid) = self._mqtt_client.subscribe(topic)
|
|
105
|
+
(result, mid) = self._mqtt_client.subscribe(self._topic)
|
|
94
106
|
if result != 0:
|
|
95
|
-
message = f"Failed to subscribe ({
|
|
107
|
+
message = f"Failed to subscribe ({str(rc)})"
|
|
96
108
|
self._logger.error(message)
|
|
97
109
|
if connection_queue:
|
|
98
110
|
connection_queue.set_exception(VacuumError(message))
|
|
99
111
|
return
|
|
100
|
-
self._logger.info(f"Subscribed to topic {
|
|
112
|
+
self._logger.info(f"Subscribed to topic {self._topic}")
|
|
101
113
|
if connection_queue:
|
|
102
114
|
connection_queue.set_result(True)
|
|
103
115
|
|
|
104
116
|
def _mqtt_on_message(self, *args, **kwargs):
|
|
117
|
+
self.received_message_since_last_disconnect = True
|
|
105
118
|
client, __, msg = args
|
|
106
119
|
try:
|
|
107
120
|
messages = self._decoder(msg.payload)
|
|
@@ -109,10 +122,16 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
109
122
|
except Exception as ex:
|
|
110
123
|
self._logger.exception(ex)
|
|
111
124
|
|
|
112
|
-
def _mqtt_on_disconnect(
|
|
113
|
-
|
|
125
|
+
def _mqtt_on_disconnect(
|
|
126
|
+
self,
|
|
127
|
+
client: mqtt.Client,
|
|
128
|
+
data: object,
|
|
129
|
+
flags: dict[str, int],
|
|
130
|
+
rc: ReasonCode | None,
|
|
131
|
+
properties: mqtt.Properties | None = None,
|
|
132
|
+
):
|
|
114
133
|
try:
|
|
115
|
-
exc = RoborockException(
|
|
134
|
+
exc = RoborockException(str(rc)) if rc is not None and rc.is_failure else None
|
|
116
135
|
super().on_connection_lost(exc)
|
|
117
136
|
connection_queue = self._waiting_queue.get(DISCONNECT_REQUEST_ID)
|
|
118
137
|
if connection_queue:
|
|
@@ -138,7 +157,7 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
138
157
|
|
|
139
158
|
if rc != mqtt.MQTT_ERR_SUCCESS:
|
|
140
159
|
disconnected_future.cancel()
|
|
141
|
-
raise RoborockException(f"Failed to disconnect ({
|
|
160
|
+
raise RoborockException(f"Failed to disconnect ({str(rc)})")
|
|
142
161
|
|
|
143
162
|
return disconnected_future
|
|
144
163
|
|
|
@@ -178,3 +197,63 @@ class RoborockMqttClient(RoborockClient, ABC):
|
|
|
178
197
|
)
|
|
179
198
|
if info.rc != mqtt.MQTT_ERR_SUCCESS:
|
|
180
199
|
raise RoborockException(f"Failed to publish ({mqtt.error_string(info.rc)})")
|
|
200
|
+
|
|
201
|
+
async def _unsubscribe(self) -> MQTTErrorCode:
|
|
202
|
+
"""Unsubscribe from the topic."""
|
|
203
|
+
loop = asyncio.get_running_loop()
|
|
204
|
+
(result, mid) = await loop.run_in_executor(None, self._mqtt_client.unsubscribe, self._topic)
|
|
205
|
+
|
|
206
|
+
if result != 0:
|
|
207
|
+
message = f"Failed to unsubscribe ({mqtt.error_string(result)})"
|
|
208
|
+
self._logger.error(message)
|
|
209
|
+
else:
|
|
210
|
+
self._logger.info(f"Unsubscribed from topic {self._topic}")
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
async def _subscribe(self) -> MQTTErrorCode:
|
|
214
|
+
"""Subscribe to the topic."""
|
|
215
|
+
loop = asyncio.get_running_loop()
|
|
216
|
+
(result, mid) = await loop.run_in_executor(None, self._mqtt_client.subscribe, self._topic)
|
|
217
|
+
|
|
218
|
+
if result != 0:
|
|
219
|
+
message = f"Failed to subscribe ({mqtt.error_string(result)})"
|
|
220
|
+
self._logger.error(message)
|
|
221
|
+
else:
|
|
222
|
+
self._logger.info(f"Subscribed to topic {self._topic}")
|
|
223
|
+
return result
|
|
224
|
+
|
|
225
|
+
async def _reconnect(self) -> None:
|
|
226
|
+
"""Reconnect to the MQTT broker."""
|
|
227
|
+
await self.async_disconnect()
|
|
228
|
+
await self.async_connect()
|
|
229
|
+
|
|
230
|
+
async def _validate_connection(self) -> None:
|
|
231
|
+
"""Override the default validate connection to try to re-subscribe rather than disconnect.
|
|
232
|
+
When something seems to be wrong with our connection, we should follow the following steps:
|
|
233
|
+
1. Try to unsubscribe and resubscribe from the topic.
|
|
234
|
+
2. If we don't end up getting a message, we should completely disconnect and reconnect to the MQTT broker.
|
|
235
|
+
3. We will continue to try to disconnect and reconnect until we get a message.
|
|
236
|
+
4. If we get a message, the next time connection is lost, We will go back to step 1.
|
|
237
|
+
"""
|
|
238
|
+
# If we should no longer keep the current connection alive...
|
|
239
|
+
if not self.should_keepalive():
|
|
240
|
+
self._logger.info("Resetting Roborock connection due to keepalive timeout")
|
|
241
|
+
if not self.received_message_since_last_disconnect:
|
|
242
|
+
# If we have already tried to unsub and resub, and we are still in this state,
|
|
243
|
+
# we should try to reconnect.
|
|
244
|
+
return await self._reconnect()
|
|
245
|
+
try:
|
|
246
|
+
# Mark that we have tried to unsubscribe and resubscribe
|
|
247
|
+
self.received_message_since_last_disconnect = False
|
|
248
|
+
if await self._unsubscribe() != 0:
|
|
249
|
+
# If we fail to unsubscribe, reconnect to the broker
|
|
250
|
+
return await self._reconnect()
|
|
251
|
+
if await self._subscribe() != 0:
|
|
252
|
+
# If we fail to subscribe, reconnected to the broker.
|
|
253
|
+
return await self._reconnect()
|
|
254
|
+
|
|
255
|
+
except Exception: # noqa
|
|
256
|
+
# If we get any errors at all, we should just reconnect.
|
|
257
|
+
return await self._reconnect()
|
|
258
|
+
# Call connect to make sure everything is still in a good state.
|
|
259
|
+
await self.async_connect()
|
|
@@ -138,6 +138,12 @@ class RoborockLocalClientV1(RoborockClientV1, RoborockClient):
|
|
|
138
138
|
response_protocol=RoborockMessageProtocol.PING_RESPONSE,
|
|
139
139
|
)
|
|
140
140
|
|
|
141
|
+
async def _validate_connection(self) -> None:
|
|
142
|
+
if not self.should_keepalive():
|
|
143
|
+
self._logger.info("Resetting Roborock connection due to keepalive timeout")
|
|
144
|
+
await self.async_disconnect()
|
|
145
|
+
await self.async_connect()
|
|
146
|
+
|
|
141
147
|
def _send_msg_raw(self, data: bytes):
|
|
142
148
|
try:
|
|
143
149
|
if not self.transport:
|
|
@@ -172,7 +178,7 @@ class RoborockLocalClientV1(RoborockClientV1, RoborockClient):
|
|
|
172
178
|
method: str | None = None,
|
|
173
179
|
params: list | dict | int | None = None,
|
|
174
180
|
) -> RoborockMessage:
|
|
175
|
-
await self.
|
|
181
|
+
await self._validate_connection()
|
|
176
182
|
msg = self._encoder(roborock_message)
|
|
177
183
|
if method:
|
|
178
184
|
self._logger.debug(f"id={request_id} Requesting method {method} with {params}")
|
{python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
@@ -51,7 +51,7 @@ class RoborockMqttClientV1(RoborockMqttClient, RoborockClientV1):
|
|
|
51
51
|
)
|
|
52
52
|
self._logger.debug("Building message id %s for method %s", request_message.request_id, method)
|
|
53
53
|
|
|
54
|
-
await self.
|
|
54
|
+
await self._validate_connection()
|
|
55
55
|
request_id = request_message.request_id
|
|
56
56
|
response_protocol = (
|
|
57
57
|
RoborockMessageProtocol.MAP_RESPONSE if method in COMMANDS_SECURED else RoborockMessageProtocol.RPC_RESPONSE
|
|
@@ -40,7 +40,7 @@ class RoborockMqttClientA01(RoborockMqttClient, RoborockClientA01):
|
|
|
40
40
|
self._logger = RoborockLoggerAdapter(device_info.device.name, _LOGGER)
|
|
41
41
|
|
|
42
42
|
async def _send_message(self, roborock_message: RoborockMessage):
|
|
43
|
-
await self.
|
|
43
|
+
await self._validate_connection()
|
|
44
44
|
response_protocol = RoborockMessageProtocol.RPC_RESPONSE
|
|
45
45
|
|
|
46
46
|
m = self._encoder(roborock_message)
|
|
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
|
|
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.45.0 → python_roborock-2.46.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.45.0 → python_roborock-2.46.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|