Skip to content

Commit d57a0a7

Browse files
feat: dynamic calculated prefixes
1 parent 7a88a14 commit d57a0a7

File tree

6 files changed

+146
-154
lines changed

6 files changed

+146
-154
lines changed

roborock/cli.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ async def _discover(ctx):
8585
raise Exception("You need to login first")
8686
client = RoborockApiClient(login_data.email)
8787
home_data = await client.get_home_data(login_data.user_data)
88-
context.update(LoginData(**login_data.as_dict(), home_data=home_data))
88+
login_data.home_data = home_data
89+
context.update(login_data)
8990
click.echo(f"Discovered devices {', '.join([device.name for device in home_data.get_all_devices()])}")
9091

9192

@@ -128,7 +129,7 @@ async def command(ctx, cmd, device_id, params):
128129
devices = home_data.devices + home_data.received_devices
129130
device = next(device for device in devices if device.duid == device_id)
130131
model = next(
131-
(product.model for product in home_data.products if device is not None and product.did == device.duid),
132+
(product.model for product in home_data.products if device is not None and product.id == device.product_id),
132133
None,
133134
)
134135
if model is None:

roborock/cloud_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ async def send_command(self, method: RoborockCommand, params: Optional[list | di
156156
response_protocol = 301 if method in COMMANDS_SECURED else 102
157157
roborock_message = RoborockMessage(timestamp=timestamp, protocol=request_protocol, payload=payload)
158158
local_key = self.device_info.device.local_key
159-
msg = MessageParser.build(roborock_message, local_key)
159+
msg = MessageParser.build(roborock_message, local_key, False)
160160
self._send_msg_raw(msg)
161161
(response, err) = await self._async_response(request_id, response_protocol)
162162
if err:

roborock/local_api.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async def async_connect(self) -> None:
6060
raise RoborockConnectionException(f"Failed connecting to {self.host}") from e
6161

6262
def sync_disconnect(self) -> None:
63-
if self.transport:
63+
if self.transport and self.loop.is_running():
6464
self.transport.close()
6565

6666
async def async_disconnect(self) -> None:
@@ -77,18 +77,15 @@ def build_roborock_message(self, method: RoborockCommand, params: Optional[list
7777
command = CommandInfoMap.get(method)
7878
if command is None:
7979
raise RoborockException(f"No prefix found for {method}")
80-
prefix = command.prefix
8180
request_protocol = 4
8281
return RoborockMessage(
83-
prefix=prefix,
8482
timestamp=timestamp,
8583
protocol=request_protocol,
8684
payload=payload,
8785
)
8886

8987
async def ping(self):
90-
command_info = CommandInfoMap[RoborockCommand.NONE]
91-
roborock_message = RoborockMessage(prefix=command_info.prefix, protocol=AP_CONFIG, payload=b"")
88+
roborock_message = RoborockMessage(protocol=AP_CONFIG, payload=b"")
9289
return (await self.send_message(roborock_message))[0]
9390

9491
async def send_command(self, method: RoborockCommand, params: Optional[list | dict] = None):

roborock/protocol.py

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
RawCopy,
2626
Struct,
2727
bytestringtype,
28+
stream_seek,
29+
stream_tell,
2830
)
2931
from Crypto.Cipher import AES
3032
from Crypto.Util.Padding import pad, unpad
@@ -223,43 +225,51 @@ def _parse(self, stream, context, path):
223225
return hash1
224226

225227

226-
class OptionalPrefix(Construct):
228+
class PrefixedStruct(Struct):
227229
def _parse(self, stream, context, path):
228230
subcon1 = Peek(Optional(Const(b"1.0")))
229231
peek_version = subcon1.parse_stream(stream, **context)
230232
if peek_version is None:
231233
subcon2 = Bytes(4)
232-
return subcon2.parse_stream(stream, **context)
233-
return b""
234+
subcon2.parse_stream(stream, **context)
235+
return super()._parse(stream, context, path)
234236

235237
def _build(self, obj, stream, context, path):
236-
if obj is not None:
237-
subcon1 = Bytes(4)
238-
subcon1.build_stream(obj, stream, **context)
238+
prefixed = context.search("prefixed")
239+
if not prefixed:
240+
return super()._build(obj, stream, context, path)
241+
offset = stream_tell(stream, path)
242+
stream_seek(stream, offset + 4, 0, path)
243+
super()._build(obj, stream, context, path)
244+
new_offset = stream_tell(stream, path)
245+
subcon1 = Bytes(4)
246+
stream_seek(stream, offset, 0, path)
247+
subcon1.build_stream(new_offset - offset - subcon1.sizeof(**context), stream, **context)
248+
stream_seek(stream, new_offset + 4, 0, path)
239249
return obj
240250

241251

252+
_Message = RawCopy(
253+
Struct(
254+
"version" / Const(b"1.0"),
255+
"seq" / Int32ub,
256+
"random" / Int32ub,
257+
"timestamp" / Int32ub,
258+
"protocol" / Int16ub,
259+
"payload"
260+
/ EncryptionAdapter(
261+
lambda ctx: Utils.md5(
262+
Utils.encode_timestamp(ctx.timestamp) + Utils.ensure_bytes(ctx.search("local_key")) + SALT
263+
),
264+
),
265+
)
266+
)
267+
242268
_Messages = Struct(
243269
"messages"
244270
/ GreedyRange(
245-
Struct(
246-
"prefix" / OptionalPrefix(),
247-
"message"
248-
/ RawCopy(
249-
Struct(
250-
"version" / Const(b"1.0"),
251-
"seq" / Int32ub,
252-
"random" / Int32ub,
253-
"timestamp" / Int32ub,
254-
"protocol" / Int16ub,
255-
"payload"
256-
/ EncryptionAdapter(
257-
lambda ctx: Utils.md5(
258-
Utils.encode_timestamp(ctx.timestamp) + Utils.ensure_bytes(ctx.search("local_key")) + SALT
259-
),
260-
),
261-
)
262-
),
271+
PrefixedStruct(
272+
"message" / _Message,
263273
"checksum" / OptionalChecksum(Optional(Int32ub), Utils.crc, lambda ctx: ctx.message.data),
264274
)
265275
),
@@ -294,7 +304,6 @@ def parse(self, data: bytes, local_key: str | None = None) -> tuple[list[Roboroc
294304
for message in parsed_messages:
295305
messages.append(
296306
RoborockMessage(
297-
prefix=message.get("prefix"),
298307
version=message.message.value.version,
299308
seq=message.message.value.seq,
300309
random=message.message.value.get("random"),
@@ -306,14 +315,15 @@ def parse(self, data: bytes, local_key: str | None = None) -> tuple[list[Roboroc
306315
remaining = parsed.get("remaining") or b""
307316
return messages, remaining
308317

309-
def build(self, roborock_messages: list[RoborockMessage] | RoborockMessage, local_key: str) -> bytes:
318+
def build(
319+
self, roborock_messages: list[RoborockMessage] | RoborockMessage, local_key: str, prefixed: bool = True
320+
) -> bytes:
310321
if isinstance(roborock_messages, RoborockMessage):
311322
roborock_messages = [roborock_messages]
312323
messages = []
313324
for roborock_message in roborock_messages:
314325
messages.append(
315326
{
316-
"prefix": roborock_message.prefix,
317327
"message": {
318328
"value": {
319329
"version": roborock_message.version,
@@ -326,7 +336,7 @@ def build(self, roborock_messages: list[RoborockMessage] | RoborockMessage, loca
326336
},
327337
}
328338
)
329-
return self.con.build({"messages": [message for message in messages]}, local_key=local_key)
339+
return self.con.build({"messages": [message for message in messages]}, local_key=local_key, prefixed=prefixed)
330340

331341

332342
MessageParser: _Parser = _Parser(_Messages, True)

roborock/roborock_message.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import time
66
from dataclasses import dataclass
77
from random import randint
8-
from typing import Optional
98

109
from .roborock_typing import RoborockCommand
1110

@@ -15,7 +14,6 @@ class RoborockMessage:
1514
protocol: int
1615
payload: bytes
1716
seq: int = randint(100000, 999999)
18-
prefix: Optional[bytes] = None
1917
version: bytes = b"1.0"
2018
random: int = randint(10000, 99999)
2119
timestamp: int = math.floor(time.time())

0 commit comments

Comments
 (0)