@@ -81,6 +81,9 @@ def __init__(
81
81
name = f"TCPModbusClient._ping_loop_task[{ self .host } :{ self .port } ]" ,
82
82
)
83
83
84
+ # Event that is set when the first ping is received
85
+ self ._first_ping_event : asyncio .Event = asyncio .Event ()
86
+
84
87
# List of CoilWatchStatus objects that are being logged
85
88
self ._log_watches = list [CoilWatchStatus ]()
86
89
@@ -120,9 +123,11 @@ def __repr__(self) -> str:
120
123
async def _ping_loop_task (self ) -> None :
121
124
while True :
122
125
self ._last_ping = await ping_ip (self .host )
126
+
123
127
if self .logger is not None :
124
128
self .logger .debug (f"[{ self } ][_ping_loop_task] ping ping ping" )
125
129
130
+ self ._first_ping_event .set ()
126
131
await asyncio .sleep (self .PING_LOOP_PERIOD )
127
132
128
133
async def _get_tcp_connection (
@@ -204,6 +209,13 @@ async def __aexit__(
204
209
await self .close ()
205
210
206
211
async def close (self ) -> None :
212
+ """
213
+ Permanent close of the TCP connection and ping loop. Only call this on final destruction of the object.
214
+ """
215
+
216
+ if self ._ping_loop is None :
217
+ return
218
+
207
219
await self .clear_tcp_connection ()
208
220
209
221
if self ._ping_loop is not None :
@@ -223,6 +235,9 @@ def log_watch(
223
235
is called.
224
236
"""
225
237
238
+ if self ._ping_loop is None :
239
+ raise RuntimeError ("Cannot log watch on closed TCPModbusClient" )
240
+
226
241
for watch in self ._log_watches :
227
242
if watch .memo_key == memo_key :
228
243
watch .expiry = time .perf_counter () + period
@@ -278,6 +293,14 @@ async def _watch_loop() -> None:
278
293
)
279
294
280
295
async def clear_tcp_connection (self ) -> None :
296
+ """
297
+ Closes the current TCP connection and clears the reader and writer objects.
298
+ On the next send_modbus_message call, a new connection will be created.
299
+ """
300
+
301
+ if self ._ping_loop is None :
302
+ raise RuntimeError ("Cannot clear TCP connection on closed TCPModbusClient" )
303
+
281
304
if self ._writer is not None :
282
305
if self .logger is not None :
283
306
self .logger .warning (
@@ -294,9 +317,13 @@ async def test_connection(
294
317
self , timeout : float | None = DEFAULT_MODBUS_TIMEOUT_SEC
295
318
) -> None :
296
319
"""
320
+ Tests the connection to the device by sending a ReadCoil message (see TEST_CONNECTION_MESSAGE)
297
321
Uses a cached awaitable to prevent spamming the connection on this call
298
322
"""
299
323
324
+ if self ._ping_loop is None :
325
+ raise RuntimeError ("Cannot test connection on closed TCPModbusClient" )
326
+
300
327
try :
301
328
if self ._active_connection_probe is None :
302
329
self ._active_connection_probe = asyncio .create_task (
@@ -308,19 +335,34 @@ async def test_connection(
308
335
finally :
309
336
self ._active_connection_probe = None
310
337
338
+ async def is_pingable (self ) -> bool :
339
+ """
340
+ Returns True if the device is pingable, False if not.
341
+ Will wait for the first ping to be received (or timeout) before returning.
342
+ """
343
+
344
+ if self ._ping_loop is None :
345
+ raise RuntimeError ("Cannot check pingability on closed TCPModbusClient" )
346
+
347
+ if not self ._first_ping_event .is_set ():
348
+ await self ._first_ping_event .wait ()
349
+
350
+ return self ._last_ping is not None
351
+
311
352
async def send_modbus_message (
312
353
self ,
313
354
request_function : ModbusFunctionT ,
314
355
timeout : float | None = DEFAULT_MODBUS_TIMEOUT_SEC ,
315
356
retries : int = 1 ,
316
357
error_on_no_response : bool = True ,
317
358
) -> ModbusFunctionT | None :
318
- """Send ADU over socket to to server and return parsed response.
319
-
320
- :param adu: Request ADU.
321
- :param sock: Socket instance.
322
- :return: Parsed response from server.
323
359
"""
360
+ Sends a modbus message to the device and returns the response.
361
+ Will create a new TCP connection if one does not exist.
362
+ """
363
+
364
+ if self ._ping_loop is None :
365
+ raise RuntimeError ("Cannot send modbus message on closed TCPModbusClient" )
324
366
325
367
request_transaction_id = self ._next_transaction_id
326
368
self ._next_transaction_id = (self ._next_transaction_id + 1 ) % MAX_TRANSACTION_ID
0 commit comments