1717_LOGGER = logging .getLogger (__name__ )
1818_PORT = 58867
1919_TIMEOUT = 5.0
20+ _PING_INTERVAL = 10
2021
2122
2223@dataclass
@@ -58,6 +59,7 @@ def __init__(self, host: str, local_key: str):
5859 self ._subscribers : CallbackList [RoborockMessage ] = CallbackList (_LOGGER )
5960 self ._is_connected = False
6061 self ._local_protocol_version : LocalProtocolVersion | None = None
62+ self ._keep_alive_task : asyncio .Task [None ] | None = None
6163 self ._update_encoder_decoder (
6264 LocalChannelParams (local_key = local_key , connect_nonce = get_next_int (10000 , 32767 ), ack_nonce = None )
6365 )
@@ -132,6 +134,28 @@ async def _hello(self):
132134
133135 raise RoborockException ("Failed to connect to device with any known protocol" )
134136
137+ async def _ping (self ) -> None :
138+ ping_message = RoborockMessage (
139+ protocol = RoborockMessageProtocol .PING_REQUEST , version = self .protocol_version .encode ()
140+ )
141+ await self ._send_message (
142+ roborock_message = ping_message ,
143+ request_id = ping_message .seq ,
144+ response_protocol = RoborockMessageProtocol .PING_RESPONSE ,
145+ )
146+
147+ async def _keep_alive_loop (self ) -> None :
148+ while self ._is_connected :
149+ try :
150+ await asyncio .sleep (_PING_INTERVAL )
151+ if self ._is_connected :
152+ await self ._ping ()
153+ except asyncio .CancelledError :
154+ break
155+ except Exception :
156+ _LOGGER .debug ("Keep-alive ping failed" , exc_info = True )
157+ # Retry next interval
158+
135159 @property
136160 def protocol_version (self ) -> LocalProtocolVersion :
137161 """Return the negotiated local protocol version, or a sensible default."""
@@ -166,6 +190,7 @@ async def connect(self) -> None:
166190 # Perform protocol negotiation
167191 try :
168192 await self ._hello ()
193+ self ._keep_alive_task = asyncio .create_task (self ._keep_alive_loop ())
169194 except RoborockException :
170195 # If protocol negotiation fails, clean up the connection state
171196 self .close ()
@@ -177,6 +202,8 @@ def _data_received(self, data: bytes) -> None:
177202
178203 def close (self ) -> None :
179204 """Disconnect from the device."""
205+ if self ._keep_alive_task :
206+ self ._keep_alive_task .cancel ()
180207 if self ._transport :
181208 self ._transport .close ()
182209 else :
@@ -187,6 +214,8 @@ def close(self) -> None:
187214 def _connection_lost (self , exc : Exception | None ) -> None :
188215 """Handle connection loss."""
189216 _LOGGER .warning ("Connection lost to %s" , self ._host , exc_info = exc )
217+ if self ._keep_alive_task :
218+ self ._keep_alive_task .cancel ()
190219 self ._transport = None
191220 self ._is_connected = False
192221
0 commit comments