1414import struct
1515import time
1616from random import randint
17- from typing import Any , Callable
17+ from typing import Optional , Any , Callable , Coroutine , Mapping
1818
1919import aiohttp
2020from Crypto .Cipher import AES
2323from roborock .exceptions import (
2424 RoborockException , RoborockTimeout , VacuumError ,
2525)
26- from .code_mappings import RoborockDockTypeCode
26+ from .code_mappings import RoborockDockTypeCode , RoborockEnum
2727from .containers import (
2828 UserData ,
2929 Status ,
@@ -63,29 +63,29 @@ def md5hex(message: str) -> str:
6363
6464
6565class PreparedRequest :
66- def __init__ (self , base_url : str , base_headers : dict = None ) -> None :
66+ def __init__ (self , base_url : str , base_headers : Optional [ dict ] = None ) -> None :
6767 self .base_url = base_url
6868 self .base_headers = base_headers or {}
6969
7070 async def request (
71- self , method : str , url : str , params = None , data = None , headers = None
72- ) -> dict | list :
71+ self , method : str , url : str , params = None , data = None , headers = None
72+ ) -> dict :
7373 _url = "/" .join (s .strip ("/" ) for s in [self .base_url , url ])
7474 _headers = {** self .base_headers , ** (headers or {})}
7575 async with aiohttp .ClientSession () as session :
7676 async with session .request (
77- method ,
78- _url ,
79- params = params ,
80- data = data ,
81- headers = _headers ,
77+ method ,
78+ _url ,
79+ params = params ,
80+ data = data ,
81+ headers = _headers ,
8282 ) as resp :
8383 return await resp .json ()
8484
8585
8686class RoborockClient :
8787
88- def __init__ (self , endpoint : str , devices_info : dict [str , RoborockDeviceInfo ]) -> None :
88+ def __init__ (self , endpoint : str , devices_info : Mapping [str , RoborockDeviceInfo ]) -> None :
8989 self .devices_info = devices_info
9090 self ._endpoint = endpoint
9191 self ._nonce = secrets .token_bytes (16 )
@@ -161,7 +161,7 @@ async def _async_response(self, request_id: int, protocol_id: int = 0) -> tuple[
161161 del self ._waiting_queue [request_id ]
162162
163163 def _get_payload (
164- self , method : RoborockCommand , params : list = None , secured = False
164+ self , method : RoborockCommand , params : Optional [ list ] = None , secured = False
165165 ):
166166 timestamp = math .floor (time .time ())
167167 request_id = randint (10000 , 99999 )
@@ -187,24 +187,26 @@ def _get_payload(
187187 return request_id , timestamp , payload
188188
189189 async def send_command (
190- self , device_id : str , method : RoborockCommand , params : list = None
190+ self , device_id : str , method : RoborockCommand , params : Optional [ list ] = None
191191 ):
192192 raise NotImplementedError
193193
194- async def get_status (self , device_id : str ) -> Status :
194+ async def get_status (self , device_id : str ) -> Status | None :
195195 status = await self .send_command (device_id , RoborockCommand .GET_STATUS )
196196 if isinstance (status , dict ):
197197 return Status .from_dict (status )
198+ return None
198199
199- async def get_dnd_timer (self , device_id : str ) -> DNDTimer :
200+ async def get_dnd_timer (self , device_id : str ) -> DNDTimer | None :
200201 try :
201202 dnd_timer = await self .send_command (device_id , RoborockCommand .GET_DND_TIMER )
202203 if isinstance (dnd_timer , dict ):
203204 return DNDTimer .from_dict (dnd_timer )
204205 except RoborockTimeout as e :
205206 _LOGGER .error (e )
207+ return None
206208
207- async def get_clean_summary (self , device_id : str ) -> CleanSummary :
209+ async def get_clean_summary (self , device_id : str ) -> CleanSummary | None :
208210 try :
209211 clean_summary = await self .send_command (
210212 device_id , RoborockCommand .GET_CLEAN_SUMMARY
@@ -215,8 +217,9 @@ async def get_clean_summary(self, device_id: str) -> CleanSummary:
215217 return CleanSummary (clean_time = int .from_bytes (clean_summary , 'big' ))
216218 except RoborockTimeout as e :
217219 _LOGGER .error (e )
220+ return None
218221
219- async def get_clean_record (self , device_id : str , record_id : int ) -> CleanRecord :
222+ async def get_clean_record (self , device_id : str , record_id : int ) -> CleanRecord | None :
220223 try :
221224 clean_record = await self .send_command (
222225 device_id , RoborockCommand .GET_CLEAN_RECORD , [record_id ]
@@ -225,56 +228,68 @@ async def get_clean_record(self, device_id: str, record_id: int) -> CleanRecord:
225228 return CleanRecord .from_dict (clean_record )
226229 except RoborockTimeout as e :
227230 _LOGGER .error (e )
231+ return None
228232
229- async def get_consumable (self , device_id : str ) -> Consumable :
233+ async def get_consumable (self , device_id : str ) -> Consumable | None :
230234 try :
231235 consumable = await self .send_command (device_id , RoborockCommand .GET_CONSUMABLE )
232236 if isinstance (consumable , dict ):
233237 return Consumable .from_dict (consumable )
234238 except RoborockTimeout as e :
235239 _LOGGER .error (e )
240+ return None
236241
237- async def get_wash_towel_mode (self , device_id : str ) -> WashTowelMode :
242+ async def get_wash_towel_mode (self , device_id : str ) -> WashTowelMode | None :
238243 try :
239244 washing_mode = await self .send_command (device_id , RoborockCommand .GET_WASH_TOWEL_MODE )
240245 if isinstance (washing_mode , dict ):
241246 return WashTowelMode .from_dict (washing_mode )
242247 except RoborockTimeout as e :
243248 _LOGGER .error (e )
249+ return None
244250
245- async def get_dust_collection_mode (self , device_id : str ) -> DustCollectionMode :
251+ async def get_dust_collection_mode (self , device_id : str ) -> DustCollectionMode | None :
246252 try :
247253 dust_collection = await self .send_command (device_id , RoborockCommand .GET_DUST_COLLECTION_MODE )
248254 if isinstance (dust_collection , dict ):
249255 return DustCollectionMode .from_dict (dust_collection )
250256 except RoborockTimeout as e :
251257 _LOGGER .error (e )
258+ return None
252259
253- async def get_smart_wash_params (self , device_id : str ) -> SmartWashParams :
260+ async def get_smart_wash_params (self , device_id : str ) -> SmartWashParams | None :
254261 try :
255262 mop_wash_mode = await self .send_command (device_id , RoborockCommand .GET_SMART_WASH_PARAMS )
256263 if isinstance (mop_wash_mode , dict ):
257264 return SmartWashParams .from_dict (mop_wash_mode )
258265 except RoborockTimeout as e :
259266 _LOGGER .error (e )
267+ return None
260268
261- async def get_dock_summary (self , device_id : str , dock_type : RoborockDockTypeCode ) -> RoborockDockSummary :
269+ async def get_dock_summary (self , device_id : str , dock_type : RoborockEnum ) -> RoborockDockSummary | None :
270+ """Gets the status summary from the dock with the methods available for a given dock.
271+
272+ :param dock_type: RoborockDockTypeCode"""
273+ if RoborockDockTypeCode .name != "RoborockDockTypeCode" :
274+ raise RoborockException ("Invalid enum given for dock type" )
262275 try :
263- commands = [self .get_dust_collection_mode (device_id )]
276+ commands : list [Coroutine [Any , Any , DustCollectionMode | WashTowelMode | SmartWashParams | None ]] = [
277+ self .get_dust_collection_mode (device_id )]
264278 if dock_type == RoborockDockTypeCode ['3' ]:
265279 commands += [self .get_wash_towel_mode (device_id ), self .get_smart_wash_params (device_id )]
266280 [
267281 dust_collection_mode ,
268282 wash_towel_mode ,
269283 smart_wash_params
270284 ] = (
271- list (await asyncio .gather (* commands ))
272- + [None , None ]
285+ list (await asyncio .gather (* commands ))
286+ + [None , None ]
273287 )[:3 ]
274288
275289 return RoborockDockSummary (dust_collection_mode , wash_towel_mode , smart_wash_params )
276290 except RoborockTimeout as e :
277291 _LOGGER .error (e )
292+ return None
278293
279294 async def get_prop (self , device_id : str ) -> RoborockDeviceProp | None :
280295 [status , dnd_timer , clean_summary , consumable ] = await asyncio .gather (
@@ -299,7 +314,7 @@ async def get_prop(self, device_id: str) -> RoborockDeviceProp | None:
299314 )
300315 return None
301316
302- async def get_multi_maps_list (self , device_id ) -> MultiMapsList :
317+ async def get_multi_maps_list (self , device_id ) -> MultiMapsList | None :
303318 try :
304319 multi_maps_list = await self .send_command (
305320 device_id , RoborockCommand .GET_MULTI_MAPS_LIST
@@ -308,14 +323,16 @@ async def get_multi_maps_list(self, device_id) -> MultiMapsList:
308323 return MultiMapsList .from_dict (multi_maps_list )
309324 except RoborockTimeout as e :
310325 _LOGGER .error (e )
326+ return None
311327
312- async def get_networking (self , device_id ) -> NetworkInfo :
328+ async def get_networking (self , device_id ) -> NetworkInfo | None :
313329 try :
314330 networking_info = await self .send_command (device_id , RoborockCommand .GET_NETWORK_INFO )
315331 if isinstance (networking_info , dict ):
316332 return NetworkInfo .from_dict (networking_info )
317333 except RoborockTimeout as e :
318334 _LOGGER .error (e )
335+ return None
319336
320337
321338class RoborockApiClient :
@@ -334,9 +351,14 @@ async def _get_base_url(self) -> str:
334351 "/api/v1/getUrlByEmail" ,
335352 params = {"email" : self ._username , "needtwostepauth" : "false" },
336353 )
354+ if response is None :
355+ raise RoborockException ("get url by email returned None" )
337356 if response .get ("code" ) != 200 :
338357 raise RoborockException (response .get ("error" ))
339- self .base_url = response .get ("data" ).get ("url" )
358+ response_data = response .get ("data" )
359+ if response_data is None :
360+ raise RoborockException ("response does not have 'data'" )
361+ self .base_url = response_data .get ("url" )
340362 return self .base_url
341363
342364 def _get_header_client_id (self ):
@@ -358,7 +380,8 @@ async def request_code(self) -> None:
358380 "type" : "auth" ,
359381 },
360382 )
361-
383+ if code_response is None :
384+ raise RoborockException ("Failed to get a response from send email code" )
362385 if code_response .get ("code" ) != 200 :
363386 raise RoborockException (code_response .get ("msg" ))
364387
@@ -376,10 +399,14 @@ async def pass_login(self, password: str) -> UserData:
376399 "needtwostepauth" : "false" ,
377400 },
378401 )
379-
402+ if login_response is None :
403+ raise RoborockException ("Login response is none" )
380404 if login_response .get ("code" ) != 200 :
381405 raise RoborockException (login_response .get ("msg" ))
382- return UserData .from_dict (login_response .get ("data" ))
406+ user_data = login_response .get ("data" )
407+ if not isinstance (user_data , dict ):
408+ raise RoborockException ("Got unexpected data type for user_data" )
409+ return UserData .from_dict (user_data )
383410
384411 async def code_login (self , code ) -> UserData :
385412 base_url = await self ._get_base_url ()
@@ -395,15 +422,21 @@ async def code_login(self, code) -> UserData:
395422 "verifycodetype" : "AUTH_EMAIL_CODE" ,
396423 },
397424 )
398-
425+ if login_response is None :
426+ raise RoborockException ("Login request response is None" )
399427 if login_response .get ("code" ) != 200 :
400428 raise RoborockException (login_response .get ("msg" ))
401- return UserData .from_dict (login_response .get ("data" ))
429+ user_data = login_response .get ("data" )
430+ if not isinstance (user_data , dict ):
431+ raise RoborockException ("Got unexpected data type for user_data" )
432+ return UserData .from_dict (user_data )
402433
403434 async def get_home_data (self , user_data : UserData ) -> HomeData :
404435 base_url = await self ._get_base_url ()
405436 header_clientid = self ._get_header_client_id ()
406437 rriot = user_data .rriot
438+ if rriot is None :
439+ raise RoborockException ("rriot is none" )
407440 home_id_request = PreparedRequest (
408441 base_url , {"header_clientid" : header_clientid }
409442 )
@@ -412,9 +445,12 @@ async def get_home_data(self, user_data: UserData) -> HomeData:
412445 "/api/v1/getHomeDetail" ,
413446 headers = {"Authorization" : user_data .token },
414447 )
448+ if home_id_response is None :
449+ raise RoborockException ("home_id_response is None" )
415450 if home_id_response .get ("code" ) != 200 :
416451 raise RoborockException (home_id_response .get ("msg" ))
417- home_id = home_id_response .get ("data" ).get ("rrHomeId" )
452+
453+ home_id = home_id_response ['data' ].get ("rrHomeId" )
418454 timestamp = math .floor (time .time ())
419455 nonce = secrets .token_urlsafe (6 )
420456 prestr = ":" .join (
@@ -431,6 +467,8 @@ async def get_home_data(self, user_data: UserData) -> HomeData:
431467 mac = base64 .b64encode (
432468 hmac .new (rriot .h .encode (), prestr .encode (), hashlib .sha256 ).digest ()
433469 ).decode ()
470+ if rriot .r .a is None :
471+ raise RoborockException ("Missing field 'a' in rriot reference" )
434472 home_request = PreparedRequest (
435473 rriot .r .a ,
436474 {
@@ -442,4 +480,7 @@ async def get_home_data(self, user_data: UserData) -> HomeData:
442480 if not home_response .get ("success" ):
443481 raise RoborockException (home_response )
444482 home_data = home_response .get ("result" )
445- return HomeData .from_dict (home_data )
483+ if isinstance (home_data , dict ):
484+ return HomeData .from_dict (home_data )
485+ else :
486+ raise RoborockException ("home_response result was an unexpected type" )
0 commit comments