11from __future__ import annotations
22
33import asyncio
4+ import hashlib
45import json
56import logging
67from asyncio import BaseTransport , Lock
78
89from construct import ( # type: ignore
910 Bytes ,
1011 Checksum ,
12+ GreedyBytes ,
1113 Int16ub ,
1214 Int32ub ,
15+ Prefixed ,
1316 RawCopy ,
1417 Struct ,
1518)
19+ from cryptography .hazmat .primitives .ciphers .aead import AESGCM
1620
1721from roborock .containers import BroadcastMessage
1822from roborock .protocol import EncryptionAdapter , Utils , _Parser
@@ -29,14 +33,37 @@ def __init__(self, timeout: int = 5):
2933 self .devices_found : list [BroadcastMessage ] = []
3034 self ._mutex = Lock ()
3135
32- def datagram_received (self , data , _ ):
33- [broadcast_message ], _ = BroadcastParser .parse (data )
34- if broadcast_message .payload :
35- parsed_message = BroadcastMessage .from_dict (json .loads (broadcast_message .payload ))
36- _LOGGER .debug (f"Received broadcast: { parsed_message } " )
37- self .devices_found .append (parsed_message )
36+ def datagram_received (self , data : bytes , _ ):
37+ """Handle incoming broadcast datagrams."""
38+ try :
39+ version = data [:3 ]
40+ if version == b"L01" :
41+ [parsed_msg ], _ = L01Parser .parse (data )
42+ encrypted_payload = parsed_msg .payload
3843
39- async def discover (self ):
44+ key = hashlib .sha256 (BROADCAST_TOKEN ).digest ()
45+ iv_digest_input = data [:9 ]
46+ digest = hashlib .sha256 (iv_digest_input ).digest ()
47+ iv = digest [:12 ]
48+
49+ cipher = AESGCM (key )
50+ decrypted_payload_bytes = cipher .decrypt (iv , encrypted_payload , None )
51+ json_payload = json .loads (decrypted_payload_bytes )
52+ parsed_message = BroadcastMessage (duid = json_payload ["duid" ], ip = json_payload ["ip" ], version = version )
53+ _LOGGER .debug (f"Received L01 broadcast: { parsed_message } " )
54+ self .devices_found .append (parsed_message )
55+ else :
56+ # Fallback to the original protocol parser for other versions
57+ [broadcast_message ], _ = BroadcastParser .parse (data )
58+ if broadcast_message .payload :
59+ json_payload = json .loads (broadcast_message .payload )
60+ parsed_message = BroadcastMessage (duid = json_payload ["duid" ], ip = json_payload ["ip" ], version = version )
61+ _LOGGER .debug (f"Received broadcast: { parsed_message } " )
62+ self .devices_found .append (parsed_message )
63+ except Exception as e :
64+ _LOGGER .warning (f"Failed to decode message: { bytes } . Error: { e } " )
65+
66+ async def discover (self ) -> list [BroadcastMessage ]:
4067 async with self ._mutex :
4168 try :
4269 loop = asyncio .get_event_loop ()
@@ -64,5 +91,19 @@ def close(self):
6491 "checksum" / Checksum (Int32ub , Utils .crc , lambda ctx : ctx .message .data ),
6592)
6693
94+ _L01BroadcastMessage = Struct (
95+ "message"
96+ / RawCopy (
97+ Struct (
98+ "version" / Bytes (3 ),
99+ "field1" / Bytes (4 ), # Unknown field
100+ "field2" / Bytes (2 ), # Unknown field
101+ "payload" / Prefixed (Int16ub , GreedyBytes ), # Encrypted payload with length prefix
102+ )
103+ ),
104+ "checksum" / Checksum (Int32ub , Utils .crc , lambda ctx : ctx .message .data ),
105+ )
106+
67107
68108BroadcastParser : _Parser = _Parser (_BroadcastMessage , False )
109+ L01Parser : _Parser = _Parser (_L01BroadcastMessage , False )
0 commit comments