Skip to content

Commit 689708a

Browse files
committed
Fix #221 timing enhancements, #188 workarounds
1 parent 713fe5f commit 689708a

File tree

4 files changed

+109
-92
lines changed

4 files changed

+109
-92
lines changed

examples/contrib/message_parser.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,6 @@ def get_options():
144144
help="If the incoming message is in hexadecimal format",
145145
action="store_true", dest="transaction", default=False)
146146

147-
parser.add_option("-t", "--transaction",
148-
help="If the incoming message is in hexadecimal format",
149-
action="store_true", dest="transaction", default=False)
150-
151-
parser.add_option("-t", "--transaction",
152-
help="If the incoming message is in hexadecimal format",
153-
action="store_true", dest="transaction", default=False)
154-
155147
(opt, arg) = parser.parse_args()
156148

157149
if not opt.message and len(arg) > 0:

pymodbus/client/sync.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,12 @@ def __init__(self, method='ascii', **kwargs):
324324
self.baudrate = kwargs.get('baudrate', Defaults.Baudrate)
325325
self.timeout = kwargs.get('timeout', Defaults.Timeout)
326326
if self.method == "rtu":
327-
self._last_frame_end = 0.0
327+
self._last_frame_end = None
328328
if self.baudrate > 19200:
329329
self._silent_interval = 1.75/1000 # ms
330330
else:
331331
self._silent_interval = 3.5 * (1 + 8 + 2) / self.baudrate
332+
self._silent_interval = round(self._silent_interval, 6)
332333

333334
@staticmethod
334335
def __implementation(method):
@@ -353,7 +354,8 @@ def connect(self):
353354
354355
:returns: True if connection succeeded, False otherwise
355356
"""
356-
if self.socket: return True
357+
if self.socket:
358+
return True
357359
try:
358360
self.socket = serial.Serial(port=self.port,
359361
timeout=self.timeout,
@@ -390,26 +392,30 @@ def _send(self, request):
390392
"state - {}".format(TransactionStateString[self.state]))
391393
while self.state != ModbusTransactionState.IDLE:
392394
if self.state == ModbusTransactionState.TRANSCATION_COMPLETE:
395+
ts = round(time.time(), 6)
396+
_logger.debug("Changing state to IDLE - Last Frame End - {}, "
397+
"Current Time stamp - {}".format(
398+
self._last_frame_end, ts))
399+
if self.method == "rtu":
400+
if self._last_frame_end:
401+
idle_time = self._last_frame_end + self._silent_interval
402+
if round(ts-idle_time, 6) <= self._silent_interval:
403+
_logger.debug("Waiting for 3.5 char before next "
404+
"send - {} ms".format(
405+
self._silent_interval*1000))
406+
time.sleep(self._silent_interval)
407+
else:
408+
# Recovering from last error ??
409+
time.sleep(self._silent_interval)
393410
self.state = ModbusTransactionState.IDLE
394411
else:
412+
_logger.debug("Sleeping")
395413
time.sleep(self._silent_interval)
396414
_logger.debug("Transaction state 'IDLE', intiating a new transaction")
397415
self.state = ModbusTransactionState.SENDING
398416
if not self.socket:
399417
raise ConnectionException(self.__str__())
400418
if request:
401-
ts = time.time()
402-
if self.method == "rtu":
403-
if self._last_frame_end:
404-
if ts < self._last_frame_end + self._silent_interval:
405-
_logger.debug("waiting for 3.5 char before next "
406-
"send".format(self._silent_interval))
407-
time.sleep(
408-
self._last_frame_end + self._silent_interval - ts
409-
)
410-
_logger.debug("waited for - {} "
411-
"ms".format(self._silent_interval*1000))
412-
413419
try:
414420
in_waiting = ("in_waiting" if hasattr(
415421
self.socket, "in_waiting") else "inWaiting")
@@ -431,7 +437,7 @@ def _send(self, request):
431437
"to 'WAITING FOR REPLY'")
432438
self.state = ModbusTransactionState.WAITING_FOR_REPLY
433439
if self.method == "rtu":
434-
self._last_frame_end = time.time()
440+
self._last_frame_end = round(time.time(), 6)
435441
return size
436442
return 0
437443

@@ -444,11 +450,12 @@ def _recv(self, size):
444450
if not self.socket:
445451
raise ConnectionException(self.__str__())
446452
result = self.socket.read(size)
447-
_logger.debug("Changing transaction state from 'WAITING FOR REPLY' "
448-
"to 'PROCESSING REPLY'")
449-
self.state = ModbusTransactionState.PROCESSING_REPLY
453+
if self.state != ModbusTransactionState.PROCESSING_REPLY:
454+
_logger.debug("Changing transaction state from "
455+
"'WAITING FOR REPLY' to 'PROCESSING REPLY'")
456+
self.state = ModbusTransactionState.PROCESSING_REPLY
450457
if self.method == "rtu":
451-
self._last_frame_end = time.time()
458+
self._last_frame_end = round(time.time(), 6)
452459
return result
453460

454461
def __str__(self):

pymodbus/transaction.py

Lines changed: 81 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -116,62 +116,70 @@ def execute(self, request):
116116
consumer.write(Frame(request))
117117
'''
118118
with self._transaction_lock:
119-
retries = self.retries
120-
request.transaction_id = self.getNextTID()
121-
_logger.debug("Running transaction %d" % request.transaction_id)
122-
_buffer = hexlify_packets(self.client.framer._buffer)
123-
_logger.debug("Current Frame : - {}".format(_buffer))
124-
expected_response_length = None
125-
if not isinstance(self.client.framer, ModbusSocketFramer):
126-
if hasattr(request, "get_response_pdu_size"):
127-
response_pdu_size = request.get_response_pdu_size()
128-
if isinstance(self.client.framer, ModbusAsciiFramer):
129-
response_pdu_size = response_pdu_size * 2
130-
if response_pdu_size:
131-
expected_response_length = self._calculate_response_length(response_pdu_size)
132-
if request.unit_id in self._no_response_devices:
133-
full = True
134-
else:
135-
full = False
136-
response, last_exception = self._transact(request,
137-
expected_response_length,
138-
full=full
139-
)
140-
if not response and (
141-
request.unit_id not in self._no_response_devices):
142-
self._no_response_devices.append(request.unit_id)
143-
elif request.unit_id in self._no_response_devices:
144-
self._no_response_devices.remove(request.unit_id)
145-
if not response and self.retry_on_empty and retries:
146-
while retries > 0:
147-
if hasattr(self.client, "state"):
148-
_logger.debug("RESETTING Transaction state to "
149-
"'IDLE' for retry")
150-
self.client.state = ModbusTransactionState.IDLE
151-
_logger.debug("Retry on empty - {}".format(retries))
152-
response, last_exception = self._transact(
153-
request,
154-
expected_response_length
155-
)
156-
if not response:
157-
retries -= 1
158-
continue
159-
break
160-
self.client.framer.processIncomingPacket(response,
161-
self.addTransaction)
162-
response = self.getTransaction(request.transaction_id)
163-
if not response:
164-
if len(self.transactions):
165-
response = self.getTransaction(tid=0)
119+
try:
120+
retries = self.retries
121+
request.transaction_id = self.getNextTID()
122+
_logger.debug("Running transaction %d" % request.transaction_id)
123+
_buffer = hexlify_packets(self.client.framer._buffer)
124+
if _buffer:
125+
_logger.debug("Clearing current Frame : - {}".format(_buffer))
126+
self.client.framer.resetFrame()
127+
128+
expected_response_length = None
129+
if not isinstance(self.client.framer, ModbusSocketFramer):
130+
if hasattr(request, "get_response_pdu_size"):
131+
response_pdu_size = request.get_response_pdu_size()
132+
if isinstance(self.client.framer, ModbusAsciiFramer):
133+
response_pdu_size = response_pdu_size * 2
134+
if response_pdu_size:
135+
expected_response_length = self._calculate_response_length(response_pdu_size)
136+
if request.unit_id in self._no_response_devices:
137+
full = True
166138
else:
167-
last_exception = last_exception or ("No Response received "
168-
"from the remote unit")
169-
response = ModbusIOException(last_exception)
170-
if hasattr(self.client, "state"):
171-
_logger.debug("Changing transaction state from "
172-
"'PROCESSING REPLY' to 'TRANSCATION_COMPLETE'")
139+
full = False
140+
response, last_exception = self._transact(request,
141+
expected_response_length,
142+
full=full
143+
)
144+
if not response and (
145+
request.unit_id not in self._no_response_devices):
146+
self._no_response_devices.append(request.unit_id)
147+
elif request.unit_id in self._no_response_devices:
148+
self._no_response_devices.remove(request.unit_id)
149+
if not response and self.retry_on_empty and retries:
150+
while retries > 0:
151+
if hasattr(self.client, "state"):
152+
_logger.debug("RESETTING Transaction state to "
153+
"'IDLE' for retry")
154+
self.client.state = ModbusTransactionState.IDLE
155+
_logger.debug("Retry on empty - {}".format(retries))
156+
response, last_exception = self._transact(
157+
request,
158+
expected_response_length
159+
)
160+
if not response:
161+
retries -= 1
162+
continue
163+
break
164+
self.client.framer.processIncomingPacket(response,
165+
self.addTransaction)
166+
response = self.getTransaction(request.transaction_id)
167+
if not response:
168+
if len(self.transactions):
169+
response = self.getTransaction(tid=0)
170+
else:
171+
last_exception = last_exception or ("No Response received "
172+
"from the remote unit")
173+
response = ModbusIOException(last_exception)
174+
if hasattr(self.client, "state"):
175+
_logger.debug("Changing transaction state from "
176+
"'PROCESSING REPLY' to 'TRANSCATION_COMPLETE'")
177+
self.client.state = ModbusTransactionState.TRANSCATION_COMPLETE
178+
return response
179+
except Exception as ex:
180+
_logger.exception(ex)
173181
self.client.state = ModbusTransactionState.TRANSCATION_COMPLETE
174-
return response
182+
raise
175183

176184
def _transact(self, packet, response_length, full=False):
177185
"""
@@ -187,11 +195,11 @@ def _transact(self, packet, response_length, full=False):
187195
self.client.connect()
188196
packet = self.client.framer.buildPacket(packet)
189197
if _logger.isEnabledFor(logging.DEBUG):
190-
_logger.debug("send: " + hexlify_packets(packet))
198+
_logger.debug("SEND: " + hexlify_packets(packet))
191199
self._send(packet)
192200
result = self._recv(response_length or 1024, full)
193201
if _logger.isEnabledFor(logging.DEBUG):
194-
_logger.debug("recv: " + hexlify_packets(result))
202+
_logger.debug("RECV: " + hexlify_packets(result))
195203
except (socket.error, ModbusIOException,
196204
InvalidMessageRecievedException) as msg:
197205
self.client.close()
@@ -240,12 +248,22 @@ def _recv(self, expected_response_length, full):
240248
length = struct.unpack(">H", read_min[4:6])[0] - 1
241249
expected_response_length = h_size + length
242250
expected_response_length -= min_size
251+
total = expected_response_length + min_size
243252
else:
244253
expected_response_length = exception_length - min_size
254+
total = expected_response_length + min_size
255+
else:
256+
total = expected_response_length
245257
else:
246258
read_min = b''
259+
total = expected_response_length
247260
result = self.client._recv(expected_response_length)
248261
result = read_min + result
262+
actual = len(result)
263+
if actual != total:
264+
_logger.debug("Incomplete message received, "
265+
"Expected {} bytes Recieved "
266+
"{} bytes !!!!".format(total, actual))
249267
return result
250268

251269
def addTransaction(self, request, tid=None):
@@ -322,7 +340,7 @@ def addTransaction(self, request, tid=None):
322340
:param tid: The overloaded transaction id to use
323341
'''
324342
tid = tid if tid != None else request.transaction_id
325-
_logger.debug("adding transaction %d" % tid)
343+
_logger.debug("Adding transaction %d" % tid)
326344
self.transactions[tid] = request
327345

328346
def getTransaction(self, tid):
@@ -332,15 +350,15 @@ def getTransaction(self, tid):
332350
333351
:param tid: The transaction to retrieve
334352
'''
335-
_logger.debug("getting transaction %d" % tid)
353+
_logger.debug("Getting transaction %d" % tid)
336354
return self.transactions.pop(tid, None)
337355

338356
def delTransaction(self, tid):
339357
''' Removes a transaction matching the referenced tid
340358
341359
:param tid: The transaction to remove
342360
'''
343-
_logger.debug("deleting transaction %d" % tid)
361+
_logger.debug("Deleting transaction %d" % tid)
344362
self.transactions.pop(tid, None)
345363

346364

@@ -374,7 +392,7 @@ def addTransaction(self, request, tid=None):
374392
:param tid: The overloaded transaction id to use
375393
'''
376394
tid = tid if tid != None else request.transaction_id
377-
_logger.debug("adding transaction %d" % tid)
395+
_logger.debug("Adding transaction %d" % tid)
378396
self.transactions.append(request)
379397

380398
def getTransaction(self, tid):
@@ -384,15 +402,15 @@ def getTransaction(self, tid):
384402
385403
:param tid: The transaction to retrieve
386404
'''
387-
_logger.debug("getting transaction %s" % str(tid))
405+
_logger.debug("Getting transaction %s" % str(tid))
388406
return self.transactions.pop(0) if self.transactions else None
389407

390408
def delTransaction(self, tid):
391409
''' Removes a transaction matching the referenced tid
392410
393411
:param tid: The transaction to remove
394412
'''
395-
_logger.debug("deleting transaction %d" % tid)
413+
_logger.debug("Deleting transaction %d" % tid)
396414
if self.transactions: self.transactions.pop(0)
397415

398416

@@ -713,7 +731,7 @@ def getFrame(self):
713731
if end > 0:
714732
_logger.debug("Getting Frame - {}".format(hexlify_packets(buffer)))
715733
return buffer
716-
return ''
734+
return b''
717735

718736
def populateResult(self, result):
719737
''' Populates the modbus result header

test/test_client_sync.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,9 @@ def testSyncSerialClientInstantiation(self):
204204

205205
def testSyncSerialRTUClientTimeouts(self):
206206
client = ModbusSerialClient(method="rtu", baudrate=9600)
207-
assert client._silent_interval == (3.5 * 11/9600)
207+
assert client._silent_interval == round((3.5 * 11/9600), 6)
208208
client = ModbusSerialClient(method="rtu", baudrate=38400)
209-
assert client._silent_interval == (1.75/1000)
209+
assert client._silent_interval == round((1.75/1000), 6)
210210

211211

212212
@patch("serial.Serial")

0 commit comments

Comments
 (0)