33import asyncio
44import logging
55import socket
6+ from asyncio import Lock
67from typing import Callable , Coroutine
78
89import async_timeout
910
10- from roborock .api import RoborockClient
11+ from roborock .api import RoborockClient , SPECIAL_COMMANDS
1112from roborock .exceptions import RoborockTimeout , CommandVacuumError
1213from roborock .typing import RoborockCommand
1314from roborock .util import get_running_loop_or_create_one
1415
1516secured_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
5368class 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