3434 CleanRecord ,
3535 HomeData ,
3636 MultiMapsList ,
37- SmartWashParameters ,
38- RoborockDeviceInfo , WashTowelMode , DustCollectionMode ,
37+ SmartWashParams ,
38+ RoborockDeviceInfo ,
39+ WashTowelMode ,
40+ DustCollectionMode ,
3941
4042)
4143from .roborock_queue import RoborockQueue
@@ -110,41 +112,51 @@ def add_status_listener(self, callback: Callable[[str, str], None]):
110112 async def async_disconnect (self ) -> Any :
111113 raise NotImplementedError
112114
113- def _decode_msg (self , msg : bytes , local_key : str ) -> dict [str , Any ]:
115+ def _decode_msg (self , msg : bytes , local_key : str ) -> list [dict [str , Any ]]:
116+ prefix = None
114117 if msg [4 :7 ] == "1.0" .encode ():
118+ prefix = int .from_bytes (msg [:4 ], 'big' )
115119 msg = msg [4 :]
116120 elif msg [0 :3 ] != "1.0" .encode ():
117121 raise RoborockException (f"Unknown protocol version { msg [0 :3 ]} " )
118- if len (msg ) == 17 :
119- [version , request_id , _random , timestamp , protocol ] = struct .unpack (
122+ if len (msg ) in [ 17 , 21 , 25 ] :
123+ [version , request_id , random , timestamp , protocol ] = struct .unpack (
120124 "!3sIIIH" , msg [0 :17 ]
121125 )
122- return {
126+ return [{
127+ "prefix" : prefix ,
123128 "version" : version ,
124129 "request_id" : request_id ,
130+ "random" : random ,
125131 "timestamp" : timestamp ,
126132 "protocol" : protocol ,
127- }
128- [version , request_id , _random , timestamp , protocol , payload_len ] = struct .unpack (
129- "!3sIIIHH" , msg [0 :19 ]
133+ }]
134+ index = 0
135+ [version , request_id , random , timestamp , protocol , payload_len ] = struct .unpack (
136+ "!3sIIIHH" , msg [index :index + 19 ]
130137 )
131- extra_len = len (msg ) - 23 - payload_len
132- [payload , expected_crc32 , extra ] = struct .unpack_from (f"!{ payload_len } sI{ extra_len } s" , msg , 19 )
133- if not extra_len :
134- crc32 = binascii .crc32 (msg [0 : 19 + payload_len ])
138+ [payload , expected_crc32 ] = struct .unpack_from (f"!{ payload_len } sI" , msg , index + 19 )
139+ if payload_len == 0 :
140+ index += 21
141+ else :
142+ crc32 = binascii .crc32 (msg [index : index + 19 + payload_len ])
143+ index += 23 + payload_len
135144 if crc32 != expected_crc32 :
136145 raise RoborockException (f"Wrong CRC32 { crc32 } , expected { expected_crc32 } " )
137-
138- aes_key = md5bin (encode_timestamp (timestamp ) + local_key + self ._salt )
139- decipher = AES .new (aes_key , AES .MODE_ECB )
140- decrypted_payload = unpad (decipher .decrypt (payload ), AES .block_size ) if payload else extra
141- return {
146+ decrypted_payload = None
147+ if payload :
148+ aes_key = md5bin (encode_timestamp (timestamp ) + local_key + self ._salt )
149+ decipher = AES .new (aes_key , AES .MODE_ECB )
150+ decrypted_payload = unpad (decipher .decrypt (payload ), AES .block_size )
151+ return [{
152+ "prefix" : prefix ,
142153 "version" : version ,
143154 "request_id" : request_id ,
155+ "random" : random ,
144156 "timestamp" : timestamp ,
145157 "protocol" : protocol ,
146158 "payload" : decrypted_payload
147- }
159+ }] + ( self . _decode_msg ( msg [ index :], local_key ) if index < len ( msg ) else [])
148160
149161 def _encode_msg (self , device_id , request_id , protocol , timestamp , payload , prefix = None ) -> bytes :
150162 local_key = self .devices_info [device_id ].device .local_key
@@ -171,67 +183,77 @@ def _encode_msg(self, device_id, request_id, protocol, timestamp, payload, prefi
171183 msg += struct .pack ("!I" , crc32 )
172184 return msg
173185
174- async def on_message (self , device_id , msg ) -> bool :
186+ async def on_message (self , device_id , msg ) -> None :
175187 try :
176- data = self ._decode_msg (msg , self .devices_info [device_id ].device .local_key )
177- protocol = data .get ("protocol" )
178- if protocol == 102 or protocol == 4 :
179- payload = json .loads (data .get ("payload" ).decode ())
180- for data_point_number , data_point in payload .get ("dps" ).items ():
181- if data_point_number == "102" :
182- data_point_response = json .loads (data_point )
183- request_id = data_point_response .get ("id" )
184- queue = self ._waiting_queue .get (request_id )
185- if queue :
186- if queue .protocol == protocol :
187- error = data_point_response .get ("error" )
188- if error :
189- await queue .async_put (
190- (
191- None ,
192- VacuumError (
193- error .get ("code" ), error .get ("message" )
188+ messages = self ._decode_msg (msg , self .devices_info [device_id ].device .local_key )
189+ for data in messages :
190+ protocol = data .get ("protocol" )
191+ if protocol == 102 or protocol == 4 :
192+ payload = json .loads (data .get ("payload" ).decode ())
193+ for data_point_number , data_point in payload .get ("dps" ).items ():
194+ if data_point_number == "102" :
195+ data_point_response = json .loads (data_point )
196+ request_id = data_point_response .get ("id" )
197+ queue = self ._waiting_queue .get (request_id )
198+ if queue :
199+ if queue .protocol == protocol :
200+ error = data_point_response .get ("error" )
201+ if error :
202+ await queue .async_put (
203+ (
204+ None ,
205+ VacuumError (
206+ error .get ("code" ), error .get ("message" )
207+ ),
194208 ),
195- ),
196- timeout = QUEUE_TIMEOUT ,
197- )
198- else :
199- result = data_point_response .get ("result" )
200- if isinstance (result , list ) and len (result ) == 1 :
201- result = result [0 ]
202- await queue .async_put (
203- (result , None ), timeout = QUEUE_TIMEOUT
204- )
205- return True
206- elif request_id < self ._id_counter :
209+ timeout = QUEUE_TIMEOUT ,
210+ )
211+ else :
212+ result = data_point_response .get ("result" )
213+ if isinstance (result , list ) and len (result ) == 1 :
214+ result = result [0 ]
215+ await queue .async_put (
216+ (result , None ), timeout = QUEUE_TIMEOUT
217+ )
218+ elif request_id < self ._id_counter :
219+ _LOGGER .debug (
220+ f"id={ request_id } Ignoring response: { data_point_response } "
221+ )
222+ elif data_point_number == "121" :
223+ status = STATE_CODE_TO_STATUS .get (data_point )
224+ _LOGGER .debug (f"Status updated to { status } " )
225+ for listener in self ._status_listeners :
226+ listener (device_id , status )
227+ else :
207228 _LOGGER .debug (
208- f"id= { request_id } Ignoring response: { data_point_response } "
229+ f"Unknown data point number received { data_point_number } with { data_point } "
209230 )
210- elif data_point_number == "121" :
211- status = STATE_CODE_TO_STATUS .get (data_point )
212- _LOGGER . debug ( f"Status updated to { status } " )
213- for listener in self ._status_listeners :
214- listener ( device_id , status )
215- else :
216- _LOGGER . debug (
217- f"Unknown data point number received { data_point_number } with { data_point } "
231+ elif protocol == 301 :
232+ payload = data .get ("payload" )[ 0 : 24 ]
233+ [ endpoint , _ , request_id , _ ] = struct . unpack ( "<15sBH6s" , payload )
234+ if endpoint . decode (). startswith ( self ._endpoint ) :
235+ iv = bytes ( AES . block_size )
236+ decipher = AES . new ( self . _nonce , AES . MODE_CBC , iv )
237+ decrypted = unpad (
238+ decipher . decrypt ( data . get ( "payload" )[ 24 :]), AES . block_size
218239 )
219- elif protocol == 301 :
220- payload = data .get ("payload" )[0 :24 ]
221- [endpoint , _ , request_id , _ ] = struct .unpack ("<15sBH6s" , payload )
222- if endpoint .decode ().startswith (self ._endpoint ):
223- iv = bytes (AES .block_size )
224- decipher = AES .new (self ._nonce , AES .MODE_CBC , iv )
225- decrypted = unpad (
226- decipher .decrypt (data .get ("payload" )[24 :]), AES .block_size
227- )
228- decrypted = gzip .decompress (decrypted )
240+ decrypted = gzip .decompress (decrypted )
241+ queue = self ._waiting_queue .get (request_id )
242+ if queue :
243+ if isinstance (decrypted , list ):
244+ decrypted = decrypted [0 ]
245+ await queue .async_put ((decrypted , None ), timeout = QUEUE_TIMEOUT )
246+ elif data .get ('request_id' ):
247+ request_id = data .get ('request_id' )
229248 queue = self ._waiting_queue .get (request_id )
230249 if queue :
231- if isinstance (decrypted , list ):
232- decrypted = decrypted [0 ]
233- await queue .async_put ((decrypted , None ), timeout = QUEUE_TIMEOUT )
234- return True
250+ protocol = data .get ("protocol" )
251+ if queue .protocol == protocol :
252+ await queue .async_put ((None , None ), timeout = QUEUE_TIMEOUT )
253+ elif request_id < self ._id_counter and protocol != 5 :
254+ _LOGGER .debug (
255+ f"id={ request_id } Ignoring response: { data } "
256+ )
235257 except Exception as ex :
236258 _LOGGER .exception (ex )
237259
@@ -262,13 +284,13 @@ def _get_payload(
262284 if secured :
263285 inner ["security" ] = {
264286 "endpoint" : self ._endpoint ,
265- "nonce" : self ._nonce .hex ().upper (),
287+ "nonce" : self ._nonce .hex ().lower (),
266288 }
267289 payload = bytes (
268290 json .dumps (
269291 {
270- "t" : timestamp ,
271292 "dps" : {"101" : json .dumps (inner , separators = ("," , ":" ))},
293+ "t" : timestamp ,
272294 },
273295 separators = ("," , ":" ),
274296 ).encode ()
@@ -323,7 +345,7 @@ async def get_consumable(self, device_id: str) -> Consumable:
323345 except RoborockTimeout as e :
324346 _LOGGER .error (e )
325347
326- async def get_washing_mode (self , device_id : str ) -> WashTowelMode :
348+ async def get_wash_towel_mode (self , device_id : str ) -> WashTowelMode :
327349 try :
328350 washing_mode = await self .send_command (device_id , RoborockCommand .GET_WASH_TOWEL_MODE )
329351 if isinstance (washing_mode , dict ):
@@ -339,22 +361,22 @@ async def get_dust_collection_mode(self, device_id: str) -> DustCollectionMode:
339361 except RoborockTimeout as e :
340362 _LOGGER .error (e )
341363
342- async def get_mop_wash_mode (self , device_id : str ) -> SmartWashParameters :
364+ async def get_smart_wash_params (self , device_id : str ) -> SmartWashParams :
343365 try :
344366 mop_wash_mode = await self .send_command (device_id , RoborockCommand .GET_SMART_WASH_PARAMS )
345367 if isinstance (mop_wash_mode , dict ):
346- return SmartWashParameters (mop_wash_mode )
368+ return SmartWashParams (mop_wash_mode )
347369 except RoborockTimeout as e :
348370 _LOGGER .error (e )
349371
350372 async def get_dock_summary (self , device_id : str , dock_type : RoborockDockType ) -> RoborockDockSummary :
351373 try :
352374 commands = [self .get_dust_collection_mode (device_id )]
353375 if dock_type == RoborockDockType .EMPTY_WASH_FILL_DOCK :
354- commands += [self .get_mop_wash_mode (device_id ), self .get_washing_mode (device_id )]
355- [collection_mode , mop_wash , washing_mode ] = (list (await asyncio .gather (* commands )) + [None , None ])[:3 ]
376+ commands += [self .get_wash_towel_mode (device_id ), self .get_smart_wash_params (device_id )]
377+ [dust_collection_mode , wash_towel_mode , smart_wash_params ] = (list (await asyncio .gather (* commands )) + [None , None ])[:3 ]
356378
357- return RoborockDockSummary (collection_mode , washing_mode , mop_wash )
379+ return RoborockDockSummary (dust_collection_mode , wash_towel_mode , smart_wash_params )
358380 except RoborockTimeout as e :
359381 _LOGGER .error (e )
360382
@@ -367,9 +389,17 @@ async def get_prop(self, device_id: str) -> RoborockDeviceProp:
367389 self .get_consumable (device_id ),
368390 ]
369391 )
392+ last_clean_record = None
393+ if clean_summary and clean_summary .records and len (clean_summary .records ) > 0 :
394+ last_clean_record = await self .get_clean_record (
395+ device_id , clean_summary .records [0 ]
396+ )
397+ dock_summary = None
398+ if status and status .dock_type != RoborockDockType .NO_DOCK :
399+ dock_summary = await self .get_dock_summary (device_id , status .dock_type )
370400 if any ([status , dnd_timer , clean_summary , consumable ]):
371401 return RoborockDeviceProp (
372- status , dnd_timer , clean_summary , consumable
402+ status , dnd_timer , clean_summary , consumable , last_clean_record , dock_summary
373403 )
374404
375405 async def get_multi_maps_list (self , device_id ) -> MultiMapsList :
0 commit comments