Skip to content

Commit 3b490fe

Browse files
committed
Fix #289 and other misc enhancements
1 parent c49038a commit 3b490fe

14 files changed

+190
-105
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ Version 1.5.0
1010
* Move framers from transaction.py to respective modules
1111
* Fix modbus payload builder and decoder
1212
* Async servers can now have an option to defer `reactor.run()` when using `Start<Tcp/Serial/Udo>Server(...,defer_reactor_run=True)`
13-
* Fix UDP client issue while handling MEI messages (ReadDeviceInformationRequest)
13+
* Fix UDP client issue while handling MEI messages (ReadDeviceInformationRequest)
14+
* Add expected response lengths for WriteMultipleCoilRequest and WriteMultipleRegisterRequest
15+
* Fix struct errors while decoding stray response
1416
* Fix Misc examples
1517

1618
Version 1.4.0

examples/common/synchronous_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# import the various server implementations
1818
# --------------------------------------------------------------------------- #
1919
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
20-
#from pymodbus.client.sync import ModbusUdpClient as ModbusClient
20+
# from pymodbus.client.sync import ModbusUdpClient as ModbusClient
2121
# from pymodbus.client.sync import ModbusSerialClient as ModbusClient
2222

2323
# --------------------------------------------------------------------------- #
@@ -62,6 +62,7 @@ def run_sync_client():
6262
# client = ModbusClient('localhost', retries=3, retry_on_empty=True)
6363
# ------------------------------------------------------------------------#
6464
client = ModbusClient('localhost', port=5020)
65+
# from pymodbus.transaction import ModbusRtuFramer
6566
# client = ModbusClient('localhost', port=5020, framer=ModbusRtuFramer)
6667
# client = ModbusClient(method='binary', port='/dev/ptyp0', timeout=1)
6768
# client = ModbusClient(method='ascii', port='/dev/ptyp0', timeout=1)
@@ -76,6 +77,7 @@ def run_sync_client():
7677
# individual request. This can be done by specifying the `unit` parameter
7778
# which defaults to `0x00`
7879
# ----------------------------------------------------------------------- #
80+
rr = client.read_holding_registers(0, 130, unit=UNIT)
7981
log.debug("Reading Coils")
8082
rr = client.read_coils(1, 1, unit=UNIT)
8183
log.debug(rr)

examples/common/synchronous_client_ext.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# from pymodbus.client.sync import ModbusTcpClient as ModbusClient
1414
# from pymodbus.client.sync import ModbusUdpClient as ModbusClient
1515
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
16-
from pymodbus.transaction import ModbusRtuFramer
16+
1717

1818
# --------------------------------------------------------------------------- #
1919
# import the extended messages to perform
@@ -51,6 +51,8 @@ def execute_extended_requests():
5151
# client = ModbusClient(method='ascii', port="/dev/ptyp0")
5252
# client = ModbusClient(method='binary', port="/dev/ptyp0")
5353
# client = ModbusClient('127.0.0.1', port=5020)
54+
# from pymodbus.transaction import ModbusRtuFramer
55+
# client = ModbusClient('127.0.0.1', port=5020, framer=ModbusRtuFramer)
5456
client.connect()
5557

5658
# ----------------------------------------------------------------------- #

pymodbus/bit_write_message.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,13 @@ def __str__(self):
214214
params = (self.address, len(self.values))
215215
return "WriteNCoilRequest (%d) => %d " % params
216216

217+
def get_response_pdu_size(self):
218+
"""
219+
Func_code (1 byte) + Output Address (2 byte) + Quantity of Outputs (2 Bytes)
220+
:return:
221+
"""
222+
return 1 + 2 + 2
223+
217224

218225
class WriteMultipleCoilsResponse(ModbusResponse):
219226
'''

pymodbus/client/sync.py

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,35 @@ def _recv(self, size):
230230
"""
231231
if not self.socket:
232232
raise ConnectionException(self.__str__())
233-
return self.socket.recv(size)
233+
# socket.recv(size) waits until it gets some data from the host but
234+
# not necessarily the entire response that can be fragmented in
235+
# many packets.
236+
# To avoid the splitted responses to be recognized as invalid
237+
# messages and to be discarded, loops socket.recv until full data
238+
# is received or timeout is expired.
239+
# If timeout expires returns the read data, also if its length is
240+
# less than the expected size.
241+
self.socket.setblocking(0)
242+
begin = time.time()
243+
244+
data = b''
245+
if size is not None:
246+
while len(data) < size:
247+
try:
248+
data += self.socket.recv(size - len(data))
249+
except socket.error:
250+
pass
251+
if not self.timeout or (time.time() - begin > self.timeout):
252+
break
253+
else:
254+
while True:
255+
try:
256+
data += self.socket.recv(1)
257+
except socket.error:
258+
pass
259+
if not self.timeout or (time.time() - begin > self.timeout):
260+
break
261+
return data
234262

235263
def is_socket_open(self):
236264
return True if self.socket is not None else False
@@ -423,6 +451,16 @@ def close(self):
423451
self.socket.close()
424452
self.socket = None
425453

454+
def _in_waiting(self):
455+
in_waiting = ("in_waiting" if hasattr(
456+
self.socket, "in_waiting") else "inWaiting")
457+
458+
if in_waiting == "in_waiting":
459+
waitingbytes = getattr(self.socket, in_waiting)
460+
else:
461+
waitingbytes = getattr(self.socket, in_waiting)()
462+
return waitingbytes
463+
426464
def _send(self, request):
427465
""" Sends data on the underlying socket
428466
@@ -438,13 +476,7 @@ def _send(self, request):
438476
raise ConnectionException(self.__str__())
439477
if request:
440478
try:
441-
in_waiting = ("in_waiting" if hasattr(
442-
self.socket, "in_waiting") else "inWaiting")
443-
444-
if in_waiting == "in_waiting":
445-
waitingbytes = getattr(self.socket, in_waiting)
446-
else:
447-
waitingbytes = getattr(self.socket, in_waiting)()
479+
waitingbytes = self._in_waiting()
448480
if waitingbytes:
449481
result = self.socket.read(waitingbytes)
450482
if _logger.isEnabledFor(logging.WARNING):
@@ -465,6 +497,8 @@ def _recv(self, size):
465497
"""
466498
if not self.socket:
467499
raise ConnectionException(self.__str__())
500+
if size is None:
501+
size = self._in_waiting()
468502
result = self.socket.read(size)
469503
return result
470504

pymodbus/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def __init__(self, string=""):
7878
ModbusException.__init__(self, message)
7979

8080

81-
class InvalidMessageRecievedException(ModbusException):
81+
class InvalidMessageReceivedException(ModbusException):
8282
"""
8383
Error resulting from invalid response received or decoded
8484
"""

pymodbus/factory.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ def decode(self, message):
223223
return self._helper(message)
224224
except ModbusException as er:
225225
_logger.error("Unable to decode response %s" % er)
226+
227+
except Exception as ex:
228+
_logger.error(ex)
226229
return None
227230

228231
def _helper(self, data):
@@ -234,8 +237,13 @@ def _helper(self, data):
234237
:param data: The response packet to decode
235238
:returns: The decoded request or an exception response object
236239
'''
237-
function_code = byte2int(data[0])
238-
_logger.debug("Factory Response[%d]" % function_code)
240+
fc_string = function_code = byte2int(data[0])
241+
if function_code in self.__lookup:
242+
fc_string = "%s: %s" % (
243+
str(self.__lookup[function_code]).split('.')[-1].rstrip("'>"),
244+
function_code
245+
)
246+
_logger.debug("Factory Response[%s]" % fc_string)
239247
response = self.__lookup.get(function_code, lambda: None)()
240248
if function_code > 0x80:
241249
code = function_code & 0x7f # strip error portion

pymodbus/framer/rtu_framer.py

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import time
33

44
from pymodbus.exceptions import ModbusIOException
5-
from pymodbus.exceptions import InvalidMessageRecievedException
5+
from pymodbus.exceptions import InvalidMessageReceivedException
66
from pymodbus.utilities import checkCRC, computeCRC
77
from pymodbus.utilities import hexlify_packets, ModbusTransactionState
88
from pymodbus.compat import byte2int
@@ -213,27 +213,16 @@ def processIncomingPacket(self, data, callback, unit, **kwargs):
213213
unit = [unit]
214214
self.addToFrame(data)
215215
single = kwargs.get("single", False)
216-
while True:
217-
if self.isFrameReady():
218-
if self.checkFrame():
219-
if self._validate_unit_id(unit, single):
220-
self._process(callback)
221-
else:
222-
_logger.debug("Not a valid unit id - {}, "
223-
"ignoring!!".format(self._header['uid']))
224-
self.resetFrame()
225-
216+
if self.isFrameReady():
217+
if self.checkFrame():
218+
if self._validate_unit_id(unit, single):
219+
self._process(callback)
226220
else:
227-
# Could be an error response
228-
if len(self._buffer):
229-
# Possible error ???
230-
self._process(callback, error=True)
231-
else:
232-
if len(self._buffer):
233-
# Possible error ???
234-
if self._header.get('len', 0) < 2:
235-
self._process(callback, error=True)
236-
break
221+
_logger.debug("Not a valid unit id - {}, "
222+
"ignoring!!".format(self._header['uid']))
223+
self.resetFrame()
224+
else:
225+
_logger.debug("Frame - [{}] not ready".format(data))
237226

238227
def buildPacket(self, message):
239228
"""
@@ -258,7 +247,7 @@ def sendPacket(self, message):
258247
# ModbusTransactionState.to_string(self.client.state))
259248
# )
260249
while self.client.state != ModbusTransactionState.IDLE:
261-
if self.client.state == ModbusTransactionState.TRANSCATION_COMPLETE:
250+
if self.client.state == ModbusTransactionState.TRANSACTION_COMPLETE:
262251
ts = round(time.time(), 6)
263252
_logger.debug("Changing state to IDLE - Last Frame End - {}, "
264253
"Current Time stamp - {}".format(
@@ -296,11 +285,6 @@ def recvPacket(self, size):
296285
:return:
297286
"""
298287
result = self.client.recv(size)
299-
# if self.client.state != ModbusTransactionState.PROCESSING_REPLY:
300-
# _logger.debug("Changing transaction state from "
301-
# "'WAITING FOR REPLY' to 'PROCESSING REPLY'")
302-
# self.client.state = ModbusTransactionState.PROCESSING_REPLY
303-
304288
self.client.last_frame_end = round(time.time(), 6)
305289
return result
306290

@@ -313,7 +297,7 @@ def _process(self, callback, error=False):
313297
if result is None:
314298
raise ModbusIOException("Unable to decode request")
315299
elif error and result.function_code < 0x80:
316-
raise InvalidMessageRecievedException(result)
300+
raise InvalidMessageReceivedException(result)
317301
else:
318302
self.populateResult(result)
319303
self.advanceFrame()

pymodbus/framer/socket_framer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import struct
22
from pymodbus.exceptions import ModbusIOException
3-
from pymodbus.exceptions import InvalidMessageRecievedException
3+
from pymodbus.exceptions import InvalidMessageReceivedException
44
from pymodbus.utilities import hexlify_packets
55
from pymodbus.framer import ModbusFramer, SOCKET_FRAME_HEADER
66

@@ -174,7 +174,7 @@ def _process(self, callback, error=False):
174174
if result is None:
175175
raise ModbusIOException("Unable to decode request")
176176
elif error and result.function_code < 0x80:
177-
raise InvalidMessageRecievedException(result)
177+
raise InvalidMessageReceivedException(result)
178178
else:
179179
self.populateResult(result)
180180
self.advanceFrame()

pymodbus/register_write_message.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,13 @@ def execute(self, context):
192192
context.setValues(self.function_code, self.address, self.values)
193193
return WriteMultipleRegistersResponse(self.address, self.count)
194194

195+
def get_response_pdu_size(self):
196+
"""
197+
Func_code (1 byte) + Starting Address (2 byte) + Quantity of Reggisters (2 Bytes)
198+
:return:
199+
"""
200+
return 1 + 2 + 2
201+
195202
def __str__(self):
196203
''' Returns a string representation of the instance
197204

0 commit comments

Comments
 (0)