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