Skip to content

Commit 29bdb45

Browse files
fix: minor fixes
1 parent aa66e1d commit 29bdb45

File tree

3 files changed

+54
-26
lines changed

3 files changed

+54
-26
lines changed

roborock/api.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@
4747
_LOGGER = logging.getLogger(__name__)
4848
QUEUE_TIMEOUT = 4
4949
MQTT_KEEPALIVE = 60
50-
50+
SPECIAL_COMMANDS = [
51+
RoborockCommand.GET_MAP_V1,
52+
]
5153

5254
def md5hex(message: str) -> str:
5355
md5 = hashlib.md5()
@@ -119,6 +121,12 @@ def _decode_msg(self, msg: bytes, local_key: str) -> dict[str, Any]:
119121
[version, _seq, _random, timestamp, protocol, payload_len] = struct.unpack(
120122
"!3sIIIHH", msg[0:19]
121123
)
124+
if not payload_len:
125+
return {
126+
"version": version,
127+
"timestamp": timestamp,
128+
"protocol": protocol,
129+
}
122130
[payload, expected_crc32] = struct.unpack_from(f"!{payload_len}sI", msg, 19)
123131
# if crc32 != expected_crc32:
124132
# raise RoborockException(f"Wrong CRC32 {crc32}, expected {expected_crc32}")
@@ -158,7 +166,7 @@ def _encode_msg(self, device_id, protocol, timestamp, payload, prefix='') -> byt
158166
msg += struct.pack("!I", crc32)
159167
return msg
160168

161-
async def on_message(self, device_id, msg) -> None:
169+
async def on_message(self, device_id, msg) -> bool:
162170
try:
163171
data = self._decode_msg(msg, self.device_localkey[device_id])
164172
protocol = data.get("protocol")
@@ -184,11 +192,12 @@ async def on_message(self, device_id, msg) -> None:
184192
)
185193
else:
186194
result = data_point_response.get("result")
187-
if isinstance(result, list) and len(result) > 0:
195+
if isinstance(result, list) and len(result) == 1:
188196
result = result[0]
189197
await queue.async_put(
190198
(result, None), timeout=QUEUE_TIMEOUT
191199
)
200+
return True
192201
elif request_id < self._id_counter:
193202
_LOGGER.debug(
194203
f"id={request_id} Ignoring response: {data_point_response}"
@@ -217,6 +226,7 @@ async def on_message(self, device_id, msg) -> None:
217226
if isinstance(decrypted, list):
218227
decrypted = decrypted[0]
219228
await queue.async_put((decrypted, None), timeout=QUEUE_TIMEOUT)
229+
return True
220230
except Exception as ex:
221231
_LOGGER.exception(ex)
222232

@@ -234,7 +244,7 @@ async def _async_response(self, request_id: int, protocol_id: int = 0) -> tuple[
234244
del self._waiting_queue[request_id]
235245

236246
def _get_payload(
237-
self, method: RoborockCommand, params: list = None
247+
self, method: RoborockCommand, params: list = None, secured=False
238248
):
239249
timestamp = math.floor(time.time())
240250
request_id = self._id_counter
@@ -243,11 +253,12 @@ def _get_payload(
243253
"id": request_id,
244254
"method": method,
245255
"params": params or [],
246-
"security": {
247-
"endpoint": self._endpoint,
248-
"nonce": self._nonce.hex().upper(),
249-
},
250256
}
257+
if secured:
258+
inner["security"] = {
259+
"endpoint": self._endpoint,
260+
"nonce": "39344139463753454b4851444f4a4442",
261+
}
251262
payload = bytes(
252263
json.dumps(
253264
{
@@ -328,10 +339,10 @@ async def get_prop(self, device_id: str) -> RoborockDeviceProp:
328339
]
329340
)
330341
last_clean_record = None
331-
if clean_summary and clean_summary.records and len(clean_summary.records) > 0:
332-
last_clean_record = await self.get_clean_record(
333-
device_id, clean_summary.records[0]
334-
)
342+
# if clean_summary and clean_summary.records and len(clean_summary.records) > 0:
343+
# last_clean_record = await self.get_clean_record(
344+
# device_id, clean_summary.records[0]
345+
# )
335346
dock_summary = None
336347
if status and status.dock_type != RoborockDockType.NO_DOCK:
337348
dock_summary = await self.get_dock_summary(device_id, status.dock_type)

roborock/cloud_api.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import paho.mqtt.client as mqtt
1212

13-
from roborock.api import md5hex, md5bin, RoborockClient
13+
from roborock.api import md5hex, md5bin, RoborockClient, SPECIAL_COMMANDS
1414
from roborock.exceptions import (
1515
RoborockException,
1616
CommandVacuumError,
@@ -28,9 +28,6 @@
2828
_LOGGER = logging.getLogger(__name__)
2929
QUEUE_TIMEOUT = 4
3030
MQTT_KEEPALIVE = 60
31-
COMMANDS_WITH_BINARY_RESPONSE = [
32-
RoborockCommand.GET_MAP_V1,
33-
]
3431

3532

3633
class RoborockMqttClient(RoborockClient, mqtt.Client):
@@ -192,10 +189,10 @@ async def send_command(
192189
self, device_id: str, method: RoborockCommand, params: list = None
193190
):
194191
await self.validate_connection()
195-
request_id, timestamp, payload = super()._get_payload(method, params)
192+
request_id, timestamp, payload = super()._get_payload(method, params, True)
196193
_LOGGER.debug(f"id={request_id} Requesting method {method} with {params}")
197194
request_protocol = 101
198-
response_protocol = 301 if method in COMMANDS_WITH_BINARY_RESPONSE else 102
195+
response_protocol = 301 if method in SPECIAL_COMMANDS else 102
199196
msg = super()._encode_msg(device_id, request_protocol, timestamp, payload)
200197
self._send_msg_raw(device_id, msg)
201198
(response, err) = await self._async_response(request_id, response_protocol)

roborock/local_api.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
import asyncio
44
import logging
55
import socket
6+
from asyncio import Lock
67
from typing import Callable, Coroutine
78

89
import async_timeout
910

10-
from roborock.api import RoborockClient
11+
from roborock.api import RoborockClient, SPECIAL_COMMANDS
1112
from roborock.exceptions import RoborockTimeout, CommandVacuumError
1213
from roborock.typing import RoborockCommand
1314
from roborock.util import get_running_loop_or_create_one
1415

1516
secured_prefix = 199
17+
get_prefix = 119
18+
app_prefix = 135
19+
set_prefix = 151
20+
1621
_LOGGER = logging.getLogger(__name__)
1722

1823

@@ -34,9 +39,10 @@ async def async_connect(self):
3439
async def send_command(
3540
self, device_id: str, method: RoborockCommand, params: list = None
3641
):
37-
request_id, timestamp, payload = super()._get_payload(method, params)
42+
secured = True if method in SPECIAL_COMMANDS else False
43+
request_id, timestamp, payload = self._get_payload(method, params, secured)
3844
_LOGGER.debug(f"id={request_id} Requesting method {method} with {params}")
39-
prefix = secured_prefix
45+
prefix = secured_prefix if method in SPECIAL_COMMANDS else get_prefix
4046
protocol = 4
4147
msg = self._encode_msg(device_id, protocol, timestamp, payload, prefix)
4248
_LOGGER.debug(f"Requesting with prefix {prefix} and payload {payload}")
@@ -49,26 +55,38 @@ async def send_command(
4955
_LOGGER.debug(f"id={request_id} Response from {method}: {response}")
5056
return response
5157

58+
class RoborockSocket(socket.socket):
59+
_closed = None
60+
61+
def __init__(self, *args, **kwargs):
62+
super().__init__(*args, **kwargs)
63+
64+
@property
65+
def is_closed(self):
66+
return self._closed
5267

5368
class RoborockSocketListener:
5469
roborock_port = 58867
5570

56-
def __init__(self, ip: str, device_id: str, on_message: Callable[[str, bytes], Coroutine | None],
71+
def __init__(self, ip: str, device_id: str, on_message: Callable[[str, bytes], Coroutine[bool] | bool],
5772
timeout: float | int = 4):
5873
self.ip = ip
5974
self.device_id = device_id
60-
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
75+
self.socket = RoborockSocket(socket.AF_INET, socket.SOCK_STREAM)
6176
self.socket.setblocking(False)
6277
self.loop = get_running_loop_or_create_one()
6378
self.on_message = on_message
6479
self.timeout = timeout
6580
self.is_connected = False
81+
self._lock = Lock()
6682

6783
async def _main_coro(self):
68-
while self.is_connected:
84+
while not self.socket.is_closed:
6985
try:
7086
message = await self.loop.sock_recv(self.socket, 4096)
71-
await self.on_message(self.device_id, message)
87+
accepted = await self.on_message(self.device_id, message)
88+
if accepted:
89+
self._lock.release() if self._lock.locked() else None
7290
except Exception as e:
7391
_LOGGER.exception(e)
7492
self.is_connected = False
@@ -78,14 +96,16 @@ async def connect(self):
7896
async with async_timeout.timeout(self.timeout):
7997
await self.loop.sock_connect(self.socket, (self.ip, 58867))
8098
self.is_connected = True
81-
asyncio.create_task(self._main_coro())
99+
self.loop.create_task(self._main_coro())
82100

83101
async def send_message(self, data: bytes, local_key: str):
84102
response = {}
85103
try:
86104
async with async_timeout.timeout(self.timeout):
105+
await self._lock.acquire()
87106
await self.loop.sock_sendall(self.socket, data)
88107
except (asyncio.TimeoutError, asyncio.CancelledError):
108+
self._lock.release() if self._lock.locked() else None
89109
raise RoborockTimeout(
90110
f"Timeout after {self.timeout} seconds waiting for response"
91111
) from None

0 commit comments

Comments
 (0)