1
1
import asyncio
2
2
from functools import wraps
3
+ import logging
3
4
import re
4
5
from typing import Any , Callable , Dict , List , Optional , Tuple , TypeVar , Union , cast
5
6
20
21
WriteMessageRequest ,
21
22
)
22
23
24
+ logger = logging .getLogger ("vallox" ).getChild (__name__ )
25
+
23
26
KPageSize = 65536
24
27
28
+ WEBSOCKETS_OPEN_TIMEOUT = 1
29
+ WEBSOCKETS_RECV_TIMEOUT = 1
30
+ WEBSOCKET_RETRY_DELAYS = [0.1 , 0.2 , 0.5 , 1 ]
31
+
25
32
26
33
def calculate_offset (aIndex : int ) -> int :
27
34
offset = 0
@@ -146,11 +153,28 @@ def to_kelvin(value: float) -> int:
146
153
FuncT = TypeVar ("FuncT" , bound = Callable [..., Any ])
147
154
148
155
149
- def _websocket_exception_handler (request_fn : FuncT ) -> FuncT :
156
+ def _websocket_retry_wrapper (request_fn : FuncT ) -> FuncT :
157
+ retry_on_exceptions = (
158
+ websockets .InvalidHandshake ,
159
+ websockets .InvalidState ,
160
+ websockets .WebSocketProtocolError ,
161
+ websockets .ConnectionClosed ,
162
+ OSError ,
163
+ asyncio .TimeoutError ,
164
+ )
165
+
150
166
@wraps (request_fn )
151
167
async def wrapped (* args : Any , ** kwargs : Any ) -> Any :
152
168
try :
153
- return await request_fn (* args , ** kwargs )
169
+ delays = WEBSOCKET_RETRY_DELAYS .copy ()
170
+ while len (delays ) >= 0 :
171
+ try :
172
+ return await request_fn (* args , ** kwargs )
173
+ except Exception as e :
174
+ if isinstance (e , retry_on_exceptions ) and len (delays ) > 0 :
175
+ await asyncio .sleep (delays .pop (0 ))
176
+ else :
177
+ raise e
154
178
except websockets .InvalidHandshake as e :
155
179
raise ValloxWebsocketException ("Websocket handshake failed" ) from e
156
180
except websockets .InvalidURI as e :
@@ -161,8 +185,12 @@ async def wrapped(*args: Any, **kwargs: Any) -> Any:
161
185
raise ValloxWebsocketException ("Websocket invalid state" ) from e
162
186
except websockets .WebSocketProtocolError as e :
163
187
raise ValloxWebsocketException ("Websocket protocol error" ) from e
188
+ except websockets .ConnectionClosed as e :
189
+ raise ValloxWebsocketException ("Websocket connection closed" ) from e
164
190
except OSError as e :
165
191
raise ValloxWebsocketException ("Websocket connection failed" ) from e
192
+ except asyncio .TimeoutError as e :
193
+ raise ValloxWebsocketException ("Websocket connection timed out" ) from e
166
194
167
195
return cast (FuncT , wrapped )
168
196
@@ -232,20 +260,26 @@ def _encode_pair(
232
260
233
261
return address , raw_value
234
262
235
- @_websocket_exception_handler
236
263
async def _websocket_request (self , payload : bytes ) -> bytes :
237
- async with websockets .connect (f"ws://{ self .ip_address } /" ) as ws :
238
- await ws .send (payload )
239
- r : bytes = await ws .recv ()
240
- return r
264
+ return (await self ._websocket_request_multiple (payload , 1 ))[0 ]
241
265
242
- @_websocket_exception_handler
266
+ @_websocket_retry_wrapper
243
267
async def _websocket_request_multiple (
244
268
self , payload : bytes , read_packets : int
245
269
) -> List [bytes ]:
246
- async with websockets .connect (f"ws://{ self .ip_address } /" ) as ws :
270
+ async with websockets .connect (
271
+ f"ws://{ self .ip_address } /" ,
272
+ open_timeout = WEBSOCKETS_OPEN_TIMEOUT ,
273
+ logger = logger ,
274
+ ) as ws :
247
275
await ws .send (payload )
248
- return await asyncio .gather (* [ws .recv () for _ in range (0 , read_packets )])
276
+
277
+ async def _get_responses () -> List [bytes ]:
278
+ return [await ws .recv () for _ in range (0 , read_packets )]
279
+
280
+ return await asyncio .wait_for (
281
+ _get_responses (), timeout = WEBSOCKETS_RECV_TIMEOUT * read_packets
282
+ )
249
283
250
284
async def fetch_metrics (
251
285
self , metric_keys : Optional [List [str ]] = None
0 commit comments