48
48
"Sec-WebSocket-Version" : "13" ,
49
49
"Origin" : "com.universal-devices.websockets.isy" ,
50
50
}
51
- WS_HEARTBEAT = 30
51
+ WS_HEARTBEAT = 60
52
52
WS_TIMEOUT = aiohttp .ClientTimeout (total = 60 , connect = 60 , sock_connect = 60 , sock_read = 60 )
53
53
WS_MAX_RETRIES = 4
54
54
WS_RETRY_BACKOFF = [0.01 , 1 , 10 , 30 , 60 ] # Seconds
@@ -335,6 +335,7 @@ def __init__(
335
335
self .use_https = use_https
336
336
self ._status = ES_NOT_STARTED
337
337
self ._lasthb = None
338
+ self ._hbwait = WS_HEARTBEAT
338
339
self ._sid = None
339
340
self ._program_key = None
340
341
self .websocket_task = None
@@ -349,12 +350,13 @@ def __init__(
349
350
self ._url = "wss://" if self .use_https else "ws://"
350
351
self ._url += f"{ self ._address } :{ self ._port } { self ._webroot } /rest/subscribe"
351
352
352
- def start (self ):
353
+ def start (self , retries = 0 ):
353
354
"""Start the websocket connection."""
354
355
if self .status != ES_CONNECTED :
355
356
_LOGGER .info ("Starting websocket connection." )
356
357
self .status = ES_INITIALIZING
357
- self .websocket_task = self ._loop .create_task (self .websocket ())
358
+ self .websocket_task = self ._loop .create_task (self .websocket (retries ))
359
+ self ._loop .create_task (self ._websocket_guardian ())
358
360
359
361
def stop (self ):
360
362
"""Close websocket connection."""
@@ -366,13 +368,12 @@ async def reconnect(self, delay=RECONNECT_DELAY, retries=0):
366
368
"""Reconnect to a disconnected websocket."""
367
369
if self .status == ES_CONNECTED :
368
370
return
369
- if self .websocket_task is not None and not self .websocket_task .done ():
370
- self .websocket_task .cancel ()
371
+ self .stop ()
371
372
self .status = ES_RECONNECTING
372
373
_LOGGER .info ("PyISY attempting stream reconnect in %ss." , delay )
373
374
await asyncio .sleep (delay )
374
375
retries = (retries + 1 ) if retries < WS_MAX_RETRIES else WS_MAX_RETRIES
375
- self .websocket_task = self . _loop . create_task ( self . websocket ( retries ) )
376
+ self .start ( retries = retries )
376
377
377
378
@property
378
379
def status (self ):
@@ -392,6 +393,23 @@ def last_heartbeat(self):
392
393
"""Return the last received heartbeat time from the ISY."""
393
394
return self ._lasthb
394
395
396
+ @property
397
+ def heartbeat_time (self ):
398
+ """Return the time since the last ISY Heartbeat."""
399
+ if self ._lasthb is not None :
400
+ return (now () - self ._lasthb ).seconds
401
+ return 0.0
402
+
403
+ async def _websocket_guardian (self ):
404
+ """Watch and reset websocket connection if no messages received."""
405
+ while self .status != ES_STOP_UPDATES :
406
+ asyncio .sleep (self ._hbwait )
407
+ if self .heartbeat_time > self ._hbwait :
408
+ _LOGGER .debug ("Websocket missed a heartbeat, resetting connection." )
409
+ self .status = ES_LOST_STREAM_CONNECTION
410
+ await self .reconnect ()
411
+ return
412
+
395
413
async def _route_message (self , msg ):
396
414
"""Route a received message from the event stream."""
397
415
# check xml formatting
0 commit comments