3
3
from pymodbus .client import ModbusSerialClient
4
4
from pymodbus .pdu import ModbusPDU
5
5
import re
6
+ from threading import Thread
7
+ import asyncio
8
+ import time
6
9
7
10
8
11
class Driver (object ):
@@ -31,8 +34,16 @@ def __init__(self) -> None:
31
34
self .mb_client : ModbusSerialClient | None = None
32
35
self .mb_device_id : int | None = None
33
36
self .connected : bool = False
34
-
35
- def connect (self , protocol : str , port : str , device_id : int | None = None ) -> bool :
37
+ self .polling_thread : Thread = Thread ()
38
+ self .update_cycle : float = 0.05 # sec
39
+
40
+ def connect (
41
+ self ,
42
+ protocol : str ,
43
+ port : str ,
44
+ device_id : int | None = None ,
45
+ update_cycle : float = 0.05 ,
46
+ ) -> bool :
36
47
if protocol not in ["modbus" ]:
37
48
return False
38
49
if not isinstance (port , str ):
@@ -43,6 +54,8 @@ def connect(self, protocol: str, port: str, device_id: int | None = None) -> boo
43
54
return False
44
55
if isinstance (device_id , int ) and device_id < 0 :
45
56
return False
57
+ if update_cycle < 0.001 :
58
+ return False
46
59
if self .connected :
47
60
return False
48
61
@@ -58,14 +71,55 @@ def connect(self, protocol: str, port: str, device_id: int | None = None) -> boo
58
71
trace_pdu = self ._trace_pdu ,
59
72
)
60
73
self .connected = self .mb_client .connect ()
74
+
75
+ if self .connected :
76
+ self .update_cycle = update_cycle
77
+ self .polling_thread = Thread (
78
+ target = asyncio .run ,
79
+ args = (self ._module_update (self .update_cycle ),),
80
+ daemon = True ,
81
+ )
82
+ self .polling_thread .start ()
83
+
61
84
return self .connected
62
85
63
86
def disconnect (self ) -> bool :
87
+ self .connected = False
88
+ if self .polling_thread .is_alive ():
89
+ self .polling_thread .join ()
90
+
64
91
if self .mb_client and self .mb_client .connected :
65
92
self .mb_client .close ()
66
- self .connected = False
67
93
return True
68
94
95
+ async def acknowledge (self ) -> bool :
96
+ if not self .connected :
97
+ return False
98
+ self .clear_plc_output ()
99
+ self .send_plc_output ()
100
+
101
+ cmd_toggle_before = self .get_status_bit (bit = 5 )
102
+ self .set_control_bit (bit = 2 , value = True )
103
+ self .send_plc_output ()
104
+
105
+ desired_bits = {"0" : 1 , "5" : cmd_toggle_before ^ 1 }
106
+ return await self .wait_for_status (bits = desired_bits )
107
+
108
+ async def fast_stop (self ) -> bool :
109
+ if not self .connected :
110
+ return False
111
+ self .clear_plc_output ()
112
+ self .send_plc_output ()
113
+
114
+ cmd_toggle_before = self .get_status_bit (bit = 5 )
115
+ self .set_control_bit (
116
+ bit = 0 , value = False
117
+ ) # activate fast stop (inverted behavior)
118
+ self .send_plc_output ()
119
+ desired_bits = {"5" : cmd_toggle_before ^ 1 , "7" : 1 }
120
+ print (f"hello stefan: { self .get_plc_input ()} " )
121
+ return await self .wait_for_status (bits = desired_bits )
122
+
69
123
def send_plc_output (self ) -> bool :
70
124
if self .mb_client and self .mb_client .connected :
71
125
with self .output_buffer_lock :
@@ -102,6 +156,23 @@ def receive_plc_input(self) -> bool:
102
156
return True
103
157
return False
104
158
159
+ async def wait_for_status (
160
+ self , bits : dict [str , int ] = {}, timeout_sec : float = 1.0
161
+ ) -> bool :
162
+ if not timeout_sec > 0.0 :
163
+ return False
164
+ if not bits :
165
+ return False
166
+ max_duration = time .time () + timeout_sec
167
+ while not all (
168
+ [self .get_status_bit (int (bit )) == value for bit , value in bits .items ()]
169
+ ):
170
+ await asyncio .sleep (0.001 )
171
+ self .receive_plc_input ()
172
+ if time .time () > max_duration :
173
+ return False
174
+ return True
175
+
105
176
def contains_non_hex_chars (self , buffer : str ) -> bool :
106
177
return bool (re .search (r"[^0-9a-fA-F]" , buffer ))
107
178
@@ -132,8 +203,10 @@ def get_plc_output(self) -> str:
132
203
return self .plc_output_buffer .hex ().upper ()
133
204
134
205
def clear_plc_output (self ) -> None :
135
- with self .output_buffer_lock :
136
- self .plc_output_buffer = bytearray (bytes .fromhex ("00" * 16 ))
206
+ self .set_plc_output ("00" * 16 )
207
+ self .set_control_bit (
208
+ bit = 0 , value = True
209
+ ) # deactivate fast stop (inverted behavior)
137
210
138
211
def set_control_bit (self , bit : int , value : bool ) -> bool :
139
212
with self .output_buffer_lock :
@@ -251,6 +324,11 @@ def _set_status_bit(self, bit: int, value: bool) -> bool:
251
324
self .plc_input_buffer [byte_index ] &= ~ (1 << bit_index )
252
325
return True
253
326
327
+ async def _module_update (self , update_cycle : float ) -> None :
328
+ while self .connected :
329
+ self .receive_plc_input ()
330
+ await asyncio .sleep (update_cycle )
331
+
254
332
def _trace_packet (self , sending : bool , data : bytes ) -> bytes :
255
333
txt = "REQUEST stream" if sending else "RESPONSE stream"
256
334
print (f"---> { txt } : { data !r} " )
0 commit comments