@@ -32,6 +32,7 @@ def get_mac_address() -> str:
3232 SINRICPRO_SERVER_SSL_PORT ,
3333 WEBSOCKET_PING_INTERVAL ,
3434 WEBSOCKET_PING_TIMEOUT ,
35+ WEBSOCKET_PONG_MISS_MAX ,
3536)
3637from sinricpro .utils .logger import SinricProLogger
3738
@@ -74,7 +75,6 @@ def __init__(self, config: WebSocketConfig) -> None:
7475 self .should_reconnect = True
7576 self .last_ping_time = 0.0
7677 self ._ping_task : asyncio .Task [None ] | None = None
77- self ._pong_timeout_task : asyncio .Task [None ] | None = None
7878 self ._reconnect_task : asyncio .Task [None ] | None = None
7979 self ._message_callbacks : list [Callable [[str ], None ]] = []
8080 self ._connected_callbacks : list [Callable [[], None ]] = []
@@ -166,18 +166,6 @@ async def _handle_messages(self) -> None:
166166 SinricProLogger .debug (f"WebSocket received: { message } " )
167167 for callback in self ._message_callbacks :
168168 callback (message )
169- elif isinstance (message , bytes ):
170- # Handle pong messages
171- latency = int ((time .time () - self .last_ping_time ) * 1000 )
172- SinricProLogger .debug (f"WebSocket pong received (latency: { latency } ms)" )
173-
174- # Cancel pong timeout
175- if self ._pong_timeout_task :
176- self ._pong_timeout_task .cancel ()
177- self ._pong_timeout_task = None
178-
179- for callback in self ._pong_callbacks :
180- callback (latency )
181169
182170 except websockets .exceptions .ConnectionClosed :
183171 SinricProLogger .info ("WebSocket connection closed" )
@@ -226,46 +214,55 @@ def _start_heartbeat(self) -> None:
226214 self ._ping_task = asyncio .create_task (self ._heartbeat_loop ())
227215
228216 async def _heartbeat_loop (self ) -> None :
229- """Heartbeat loop to send pings."""
217+ """Heartbeat loop to send pings and await pong responses."""
218+ consecutive_misses = 0
219+
230220 while self .connected and self .ws :
231221 await asyncio .sleep (WEBSOCKET_PING_INTERVAL / 1000.0 ) # Convert to seconds
232222
233223 if self .ws and self .connected :
234224 try :
235225 self .last_ping_time = time .time ()
236- await self .ws .ping ()
226+ pong_waiter = await self .ws .ping ()
237227 SinricProLogger .debug ("WebSocket ping sent" )
238228
239- # Set timeout for pong
240- self ._pong_timeout_task = asyncio .create_task (self ._pong_timeout ())
229+ # Wait for pong with timeout
230+ await asyncio .wait_for (
231+ pong_waiter ,
232+ timeout = WEBSOCKET_PING_TIMEOUT / 1000.0 ,
233+ )
241234
242- except Exception as e :
243- SinricProLogger .error (f"Error sending ping: { e } " )
235+ latency = int ((time .time () - self .last_ping_time ) * 1000 )
236+ SinricProLogger .debug (f"WebSocket pong received (latency: { latency } ms)" )
237+ consecutive_misses = 0
244238
245- async def _pong_timeout (self ) -> None :
246- """Handle pong timeout."""
247- try :
248- await asyncio .sleep (WEBSOCKET_PING_TIMEOUT / 1000.0 )
249- SinricProLogger .error ("WebSocket pong timeout - connection appears dead" )
239+ for callback in self ._pong_callbacks :
240+ callback (latency )
241+
242+ except asyncio .TimeoutError :
243+ consecutive_misses += 1
244+ SinricProLogger .warn (
245+ f"WebSocket pong timeout ({ consecutive_misses } /{ WEBSOCKET_PONG_MISS_MAX } )"
246+ )
250247
251- # Force close connection
252- if self .ws :
253- await self .ws .close ()
248+ if consecutive_misses >= WEBSOCKET_PONG_MISS_MAX :
249+ SinricProLogger .error ("WebSocket connection appears dead, closing" )
250+ if self .ws :
251+ await self .ws .close ()
252+ return
254253
255- except asyncio .CancelledError :
256- # Pong was received in time
257- pass
254+ except asyncio .CancelledError :
255+ return
256+
257+ except Exception as e :
258+ SinricProLogger .error (f"Error sending ping: { e } " )
258259
259260 def _stop_heartbeat (self ) -> None :
260261 """Stop heartbeat tasks."""
261262 if self ._ping_task :
262263 self ._ping_task .cancel ()
263264 self ._ping_task = None
264265
265- if self ._pong_timeout_task :
266- self ._pong_timeout_task .cancel ()
267- self ._pong_timeout_task = None
268-
269266 def _schedule_reconnect (self ) -> None :
270267 """Schedule automatic reconnection."""
271268 if self ._reconnect_task :
0 commit comments