[Python-modules-commits] [pymodbus] 01/03: import new upstream version 1.3.2
Wolfgang Borgert
debacle at moszumanska.debian.org
Sun Aug 27 22:26:42 UTC 2017
This is an automated email from the git hooks/post-receive script.
debacle pushed a commit to branch master
in repository pymodbus.
commit 3875bc950a323e334bb9175445992e15ef355cc2
Author: W. Martin Borgert <debacle at debian.org>
Date: Mon Aug 28 00:09:21 2017 +0200
import new upstream version 1.3.2
---
.gitignore | 2 +-
CHANGELOG.rst | 9 ++++
README.rst | 5 +-
examples/common/callback-server.py | 6 ++-
examples/common/device-mapping | 2 +
examples/common/modbus-payload-server.py | 8 +--
examples/common/modbus-payload.py | 15 ++++--
examples/common/performance.py | 11 ++--
pymodbus/client/sync.py | 5 +-
pymodbus/diag_message.py | 40 ++++++++++++--
pymodbus/exceptions.py | 14 ++---
pymodbus/other_message.py | 2 +-
pymodbus/payload.py | 8 +++
pymodbus/server/sync.py | 92 ++++++++++++++++++--------------
pymodbus/transaction.py | 88 +++++++++++++++++++++++-------
pymodbus/version.py | 3 +-
test/test_client_sync.py | 7 +++
test/test_diag_messages.py | 16 +++---
test/test_other_messages.py | 5 +-
test/test_payload.py | 10 +++-
test/test_server_sync.py | 27 ++++++----
21 files changed, 266 insertions(+), 109 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3998172..3e5d76b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,7 +12,7 @@ pymodbus.egg-info/
.tox/
doc/api/epydoc/html/
.vscode/
-
+.venv
__pycache__/
pymodbus/__pycache__/
pymodbus/client/__pycache__/
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5ebc9db..1297df4 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,4 +1,13 @@
+Version 1.3.2
+------------------------------------------------------------
+* ModbusSerialServer could now be stopped when running on a seperate thread.
+* Fix issue with server and client where in the frame buffer had values from previous unsuccesful transaction
+* Fix response length calculation for ModbusASCII protocol
+* Fix response length calculation ReportSlaveIdResponse, DiagnosticStatusResponse
+* Fix never ending transaction case when response is recieved without header and CRC
+* Fix tests
+
Version 1.3.1
------------------------------------------------------------
* Recall socket recv until get a complete response
diff --git a/README.rst b/README.rst
index afb4e07..a41f14f 100644
--- a/README.rst
+++ b/README.rst
@@ -125,7 +125,6 @@ like your device tested, I accept devices via mail or by IP address.
That said, the current work mainly involves polishing the library as
I get time doing such tasks as:
- * Add CI support
* Make PEP-8 compatible and flake8 ready
* Fixing bugs/feature requests
* Architecture documentation
@@ -152,6 +151,10 @@ Use make to perform a range of activities
make tox run the tests on all Python versions
make clean cleanup all temporary files
+------------------------------------------------------------
+Contributing
+------------------------------------------------------------
+Just fork the repo and raise your PR against `dev` branch.
------------------------------------------------------------
License Information
diff --git a/examples/common/callback-server.py b/examples/common/callback-server.py
index 374787b..572b138 100755
--- a/examples/common/callback-server.py
+++ b/examples/common/callback-server.py
@@ -16,6 +16,7 @@ from pymodbus.datastore import ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
+
#---------------------------------------------------------------------------#
# import the python libraries we need
#---------------------------------------------------------------------------#
@@ -31,7 +32,8 @@ log.setLevel(logging.DEBUG)
#---------------------------------------------------------------------------#
# create your custom data block with callbacks
-#---------------------------------------------------------------------------#
+#---------------------------------------------------------------------------#
+
class CallbackDataBlock(ModbusSparseDataBlock):
''' A datablock that stores the new value in memory
and passes the operation to a message queue for further
@@ -44,7 +46,7 @@ class CallbackDataBlock(ModbusSparseDataBlock):
self.devices = devices
self.queue = queue
- values = {k:0 for k in devices.iterkeys()}
+ values = {k:0 for k in devices.keys()}
values[0xbeef] = len(values) # the number of devices
super(CallbackDataBlock, self).__init__(values)
diff --git a/examples/common/device-mapping b/examples/common/device-mapping
new file mode 100644
index 0000000..dc7c8b8
--- /dev/null
+++ b/examples/common/device-mapping
@@ -0,0 +1,2 @@
+0x0001,/dev/ptyp0
+0x0002,/dev/ptyp1
\ No newline at end of file
diff --git a/examples/common/modbus-payload-server.py b/examples/common/modbus-payload-server.py
index 3c6d195..b2b55eb 100755
--- a/examples/common/modbus-payload-server.py
+++ b/examples/common/modbus-payload-server.py
@@ -35,10 +35,10 @@ log.setLevel(logging.DEBUG)
# build your payload
#---------------------------------------------------------------------------#
builder = BinaryPayloadBuilder(endian=Endian.Little)
-builder.add_string('abcdefgh')
-builder.add_32bit_float(22.34)
-builder.add_16bit_uint(0x1234)
-builder.add_8bit_int(0x12)
+# builder.add_string('abcdefgh')
+# builder.add_32bit_float(22.34)
+# builder.add_16bit_uint(4660)
+# builder.add_8bit_int(18)
builder.add_bits([0,1,0,1,1,0,1,0])
#---------------------------------------------------------------------------#
diff --git a/examples/common/modbus-payload.py b/examples/common/modbus-payload.py
index 03dbad3..cd34938 100755
--- a/examples/common/modbus-payload.py
+++ b/examples/common/modbus-payload.py
@@ -9,6 +9,7 @@ from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.payload import BinaryPayloadBuilder
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
+from pymodbus.compat import iteritems
#---------------------------------------------------------------------------#
# configure the client logging
@@ -34,6 +35,7 @@ client.connect()
# - a 8 byte string 'abcdefgh'
# - a 32 bit float 22.34
# - a 16 bit unsigned int 0x1234
+# - another 16 bit unsigned int 0x5678
# - an 8 bit int 0x12
# - an 8 bit bitstring [0,1,0,1,1,0,1,0]
#---------------------------------------------------------------------------#
@@ -41,6 +43,7 @@ builder = BinaryPayloadBuilder(endian=Endian.Big)
builder.add_string('abcdefgh')
builder.add_32bit_float(22.34)
builder.add_16bit_uint(0x1234)
+builder.add_16bit_uint(0x5678)
builder.add_8bit_int(0x12)
builder.add_bits([0,1,0,1,1,0,1,0])
payload = builder.build()
@@ -57,6 +60,7 @@ result = client.write_registers(address, payload, skip_encode=True, unit=1)
# - a 8 byte string 'abcdefgh'
# - a 32 bit float 22.34
# - a 16 bit unsigned int 0x1234
+# - another 16 bit unsigned int which we will ignore
# - an 8 bit int 0x12
# - an 8 bit bitstring [0,1,0,1,1,0,1,0]
#---------------------------------------------------------------------------#
@@ -68,15 +72,16 @@ decoded = {
'string': decoder.decode_string(8),
'float': decoder.decode_32bit_float(),
'16uint': decoder.decode_16bit_uint(),
+ 'ignored': decoder.skip_bytes(2),
'8int': decoder.decode_8bit_int(),
'bits': decoder.decode_bits(),
}
-print "-" * 60
-print "Decoded Data"
-print "-" * 60
-for name, value in decoded.iteritems():
- print ("%s\t" % name), value
+print("-" * 60)
+print("Decoded Data")
+print("-" * 60)
+for name, value in iteritems(decoded):
+ print ("%s\t" % name, value)
#---------------------------------------------------------------------------#
# close the client
diff --git a/examples/common/performance.py b/examples/common/performance.py
index 36ce11e..0a193d9 100755
--- a/examples/common/performance.py
+++ b/examples/common/performance.py
@@ -13,7 +13,7 @@ from __future__ import print_function
import logging, os
from time import time
from multiprocessing import log_to_stderr
-from pymodbus.client.sync import ModbusTcpClient
+# from pymodbus.client.sync import ModbusTcpClient
from pymodbus.client.sync import ModbusSerialClient
#---------------------------------------------------------------------------#
@@ -61,9 +61,10 @@ def single_client_test(host, cycles):
client = ModbusSerialClient(method="rtu", port="/dev/ttyp0", baudrate=9600)
while count < cycles:
with _thread_lock:
- result = client.read_holding_registers(10, 1, unit=1).getRegister(0)
+ client.read_holding_registers(10, 1, unit=1).registers[0]
count += 1
- except: logger.exception("failed to run test successfully")
+ except:
+ logger.exception("failed to run test successfully")
logger.debug("finished worker: %d" % os.getpid())
#---------------------------------------------------------------------------#
@@ -73,6 +74,10 @@ def single_client_test(host, cycles):
# threads that was specified. We then start all the threads and block on
# them to finish. This may need to switch to another mechanism to signal
# finished as the process/thread start up/shut down may skew the test a bit.
+
+# RTU 32 requests/second @9600
+# TCP 31430 requests/second
+
#---------------------------------------------------------------------------#
args = (host, int(cycles * 1.0 / workers))
procs = [Worker(target=single_client_test, args=args) for _ in range(workers)]
diff --git a/pymodbus/client/sync.py b/pymodbus/client/sync.py
index 7f85116..80946b7 100644
--- a/pymodbus/client/sync.py
+++ b/pymodbus/client/sync.py
@@ -308,7 +308,10 @@ class ModbusSerialClient(BaseModbusClient):
self.timeout = kwargs.get('timeout', Defaults.Timeout)
if self.method == "rtu":
self._last_frame_end = 0.0
- self._silent_interval = 3.5 * (1 + 8 + 2) / self.baudrate
+ if self.baudrate > 19200:
+ self._silent_interval = 1.75/1000 # ms
+ else:
+ self._silent_interval = 3.5 * (1 + 8 + 2) / self.baudrate
@staticmethod
def __implementation(method):
diff --git a/pymodbus/diag_message.py b/pymodbus/diag_message.py
index ead3d36..f6c02cb 100644
--- a/pymodbus/diag_message.py
+++ b/pymodbus/diag_message.py
@@ -74,7 +74,6 @@ class DiagnosticStatusRequest(ModbusRequest):
return 1 + 2 + 2 * len(self.message)
-
class DiagnosticStatusResponse(ModbusResponse):
'''
This is a base class for all of the diagnostic response functions
@@ -118,7 +117,11 @@ class DiagnosticStatusResponse(ModbusResponse):
:param data: The data to decode into the function code
'''
- self.sub_function_code, self.message = struct.unpack('>HH', data)
+ word_len = len(data)//2
+ if len(data) % 2:
+ word_len += 1
+ data = struct.unpack('>' + 'H'*word_len, data)
+ self.sub_function_code, self.message = data[0], data[1:]
class DiagnosticStatusSimpleRequest(DiagnosticStatusRequest):
@@ -708,6 +711,23 @@ class GetClearModbusPlusRequest(DiagnosticStatusSimpleRequest):
'''
sub_function_code = 0x0015
+ def __init__(self, **kwargs):
+ super(GetClearModbusPlusRequest, self).__init__(**kwargs)
+
+ def get_response_pdu_size(self):
+ """
+ Returns a series of 54 16-bit words (108 bytes) in the data field of the response
+ (this function differs from the usual two-byte length of the data field). The data
+ contains the statistics for the Modbus Plus peer processor in the slave device.
+ Func_code (1 byte) + Sub function code (2 byte) + Operation (2 byte) + Data (108 bytes)
+ :return:
+ """
+ if self.message == ModbusPlusOperation.GetStatistics:
+ data = 2 + 108 # byte count(2) + data (54*2)
+ else:
+ data = 0
+ return 1 + 2 + 2 + 2+ data
+
def execute(self, *args):
''' Execute the diagnostic request on the given device
@@ -716,9 +736,23 @@ class GetClearModbusPlusRequest(DiagnosticStatusSimpleRequest):
message = None # the clear operation does not return info
if self.message == ModbusPlusOperation.ClearStatistics:
_MCB.Plus.reset()
- else: message = _MCB.Plus.encode()
+ message = self.message
+ else:
+ message = [self.message]
+ message += _MCB.Plus.encode()
return GetClearModbusPlusResponse(message)
+ def encode(self):
+ '''
+ Base encoder for a diagnostic response
+ we encode the data set in self.message
+
+ :returns: The encoded packet
+ '''
+ packet = struct.pack('>H', self.sub_function_code)
+ packet += struct.pack('>H', self.message)
+ return packet
+
class GetClearModbusPlusResponse(DiagnosticStatusSimpleResponse):
'''
diff --git a/pymodbus/exceptions.py b/pymodbus/exceptions.py
index c143569..a2ad482 100644
--- a/pymodbus/exceptions.py
+++ b/pymodbus/exceptions.py
@@ -78,19 +78,19 @@ class ConnectionException(ModbusException):
ModbusException.__init__(self, message)
-class InvalidResponseRecievedException(ModbusException):
+class InvalidMessageRecievedException(ModbusException):
"""
Error resulting from invalid response received or decoded
"""
+ def __init__(self, string=""):
+ ''' Initialize the exception
-def __init__(self, string=""):
- ''' Initialize the exception
+ :param string: The message to append to the error
+ '''
+ message = "[Invalid Message] %s" % string
+ ModbusException.__init__(self, message)
- :param string: The message to append to the error
- '''
- message = "[Invalid Response] %s" % string
- ModbusException.__init__(self, message)
#---------------------------------------------------------------------------#
# Exported symbols
diff --git a/pymodbus/other_message.py b/pymodbus/other_message.py
index 93c6cfb..b30ea75 100644
--- a/pymodbus/other_message.py
+++ b/pymodbus/other_message.py
@@ -403,7 +403,7 @@ class ReportSlaveIdResponse(ModbusResponse):
status = ModbusStatus.SlaveOn
else:
status = ModbusStatus.SlaveOff
- length = len(self.identifier) + 2
+ length = len(self.identifier) + 1
packet = int2byte(length)
packet += self.identifier # we assume it is already encoded
packet += int2byte(status)
diff --git a/pymodbus/payload.py b/pymodbus/payload.py
index a34a10f..cbc7221 100644
--- a/pymodbus/payload.py
+++ b/pymodbus/payload.py
@@ -349,6 +349,14 @@ class BinaryPayloadDecoder(object):
self._pointer += size
return self._payload[self._pointer - size:self._pointer]
+ def skip_bytes(self, nbytes):
+ ''' Skip n bytes in the buffer
+
+ :param nbytes: The number of bytes to skip
+ '''
+ self._pointer += nbytes
+ return None
+
#---------------------------------------------------------------------------#
# Exported Identifiers
#---------------------------------------------------------------------------#
diff --git a/pymodbus/server/sync.py b/pymodbus/server/sync.py
index 55d3180..4559513 100644
--- a/pymodbus/server/sync.py
+++ b/pymodbus/server/sync.py
@@ -28,6 +28,7 @@ _logger = logging.getLogger(__name__)
#---------------------------------------------------------------------------#
# Protocol Handlers
#---------------------------------------------------------------------------#
+
class ModbusBaseRequestHandler(socketserver.BaseRequestHandler):
''' Implements the modbus server protocol
@@ -85,34 +86,6 @@ class ModbusBaseRequestHandler(socketserver.BaseRequestHandler):
raise NotImplementedException("Method not implemented by derived class")
-# class ModbusSingleRequestHandler(ModbusBaseRequestHandler):
-# ''' Implements the modbus server protocol
-#
-# This uses the socketserver.BaseRequestHandler to implement
-# the client handler for a single client(serial clients)
-# '''
-#
-# def handle(self):
-# ''' Callback when we receive any data
-# '''
-# while self.running:
-# try:
-# data = self.request.recv(1024)
-# if data:
-# if _logger.isEnabledFor(logging.DEBUG):
-# _logger.debug(' '.join([hex(byte2int(x)) for x in data]))
-# self.framer.processIncomingPacket(data, self.execute)
-# except Exception as msg:
-# # since we only have a single socket, we cannot exit
-# _logger.error("Socket error occurred %s" % msg)
-#
-# def send(self, message):
-# ''' Send a request (string) to the network
-#
-# :param message: The unencoded modbus response
-# '''
-
-
class ModbusSingleRequestHandler(ModbusBaseRequestHandler):
''' Implements the modbus server protocol
@@ -132,6 +105,8 @@ class ModbusSingleRequestHandler(ModbusBaseRequestHandler):
self.framer.processIncomingPacket(data, self.execute)
except Exception as msg:
# since we only have a single socket, we cannot exit
+ # Clear frame buffer
+ self.framer.resetFrame()
_logger.error("Socket error occurred %s" % msg)
def send(self, message):
@@ -147,6 +122,16 @@ class ModbusSingleRequestHandler(ModbusBaseRequestHandler):
return self.request.send(pdu)
+class CustomSingleRequestHandler(ModbusSingleRequestHandler):
+
+ def __init__(self, request, client_address, server):
+ self.request = request
+ self.client_address = client_address
+ self.server = server
+ self.running = True
+ self.setup()
+
+
class ModbusConnectedRequestHandler(ModbusBaseRequestHandler):
''' Implements the modbus server protocol
@@ -167,6 +152,7 @@ class ModbusConnectedRequestHandler(ModbusBaseRequestHandler):
to supply the alternative request handler class.
'''
+ reset_frame = False
while self.running:
try:
data = self.request.recv(1024)
@@ -178,13 +164,18 @@ class ModbusConnectedRequestHandler(ModbusBaseRequestHandler):
except socket.timeout as msg:
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug("Socket timeout occurred %s", msg)
- pass
+ reset_frame = True
except socket.error as msg:
_logger.error("Socket error occurred %s" % msg)
self.running = False
except:
_logger.error("Socket exception occurred %s" % traceback.format_exc() )
self.running = False
+ reset_frame = True
+ finally:
+ if reset_frame:
+ self.framer.resetFrame()
+ reset_frame = False
def send(self, message):
''' Send a request (string) to the network
@@ -211,10 +202,12 @@ class ModbusDisconnectedRequestHandler(ModbusBaseRequestHandler):
def handle(self):
''' Callback when we receive any data
'''
+ reset_frame = False
while self.running:
try:
data, self.request = self.request
- if not data: self.running = False
+ if not data:
+ self.running = False
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug(' '.join([hex(byte2int(x)) for x in data]))
# if not self.server.control.ListenOnly:
@@ -223,7 +216,15 @@ class ModbusDisconnectedRequestHandler(ModbusBaseRequestHandler):
except socket.error as msg:
_logger.error("Socket error occurred %s" % msg)
self.running = False
- except: self.running = False
+ reset_frame = True
+ except Exception as msg:
+ _logger.error(msg)
+ self.running = False
+ reset_frame = True
+ finally:
+ if reset_frame:
+ self.framer.resetFrame()
+ reset_frame = False
def send(self, message):
''' Send a request (string) to the network
@@ -368,6 +369,8 @@ class ModbusSerialServer(object):
server context instance.
'''
+ handler = None
+
def __init__(self, context, framer=None, identity=None, **kwargs):
''' Overloaded initializer for the socket server
@@ -402,8 +405,9 @@ class ModbusSerialServer(object):
self.timeout = kwargs.get('timeout', Defaults.Timeout)
self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves)
self.socket = None
- self._connect()
- self.is_running = True
+ if self._connect():
+ self.is_running = True
+ self._build_handler()
def _connect(self):
''' Connect to the serial server
@@ -425,12 +429,13 @@ class ModbusSerialServer(object):
:returns: A patched handler
'''
+
request = self.socket
request.send = request.write
request.recv = request.read
- handler = ModbusSingleRequestHandler(request,
- (self.device, self.device), self)
- return handler
+ self.handler = CustomSingleRequestHandler(request,
+ (self.device, self.device),
+ self)
def serve_forever(self):
''' Callback for connecting a new client thread
@@ -438,16 +443,23 @@ class ModbusSerialServer(object):
:param request: The request to handle
:param client: The address of the client
'''
- _logger.debug("Started thread to serve client")
- handler = self._build_handler()
- while self.is_running:
- handler.handle()
+ if self._connect():
+ _logger.debug("Started thread to serve client")
+ if not self.handler:
+ self._build_handler()
+ while self.is_running:
+ self.handler.handle()
+ else:
+ _logger.error("Error opening serial port , Unable to start server!!")
def server_close(self):
''' Callback for stopping the running server
'''
_logger.debug("Modbus server stopped")
self.is_running = False
+ self.handler.finish()
+ self.handler.running = False
+ self.handler = None
self.socket.close()
diff --git a/pymodbus/transaction.py b/pymodbus/transaction.py
index 9b54697..a70c268 100644
--- a/pymodbus/transaction.py
+++ b/pymodbus/transaction.py
@@ -7,7 +7,7 @@ import socket
from binascii import b2a_hex, a2b_hex
from pymodbus.exceptions import ModbusIOException, NotImplementedException
-from pymodbus.exceptions import InvalidResponseRecievedException
+from pymodbus.exceptions import InvalidMessageRecievedException
from pymodbus.constants import Defaults
from pymodbus.interfaces import IModbusFramer
from pymodbus.utilities import checkCRC, computeCRC
@@ -61,7 +61,7 @@ class ModbusTransactionManager(object):
elif isinstance(self.client.framer, ModbusRtuFramer):
self.base_adu_size = 3 # address(1), CRC(2)
elif isinstance(self.client.framer, ModbusAsciiFramer):
- self.base_adu_size = 4 # Address(2), LRC(2)
+ self.base_adu_size = 7 # start(1)+ Address(2), LRC(2) + end(2)
elif isinstance(self.client.framer, ModbusBinaryFramer):
self.base_adu_size = 3 #, Address(1), CRC(2)
else:
@@ -95,7 +95,8 @@ class ModbusTransactionManager(object):
elif isinstance(self.client.framer, ModbusAsciiFramer):
if len(response) >= 5 and int(response[3:5], 16) > 128:
return False
- elif isinstance(self.client.framer, (ModbusRtuFramer, ModbusBinaryFramer)):
+ elif isinstance(self.client.framer, (ModbusRtuFramer,
+ ModbusBinaryFramer)):
if len(response) >= 2 and byte2int(response[1]) > 128:
return False
@@ -108,9 +109,12 @@ class ModbusTransactionManager(object):
retries = self.retries
request.transaction_id = self.getNextTID()
_logger.debug("Running transaction %d" % request.transaction_id)
+ self.client.framer.resetFrame()
expected_response_length = None
if hasattr(request, "get_response_pdu_size"):
response_pdu_size = request.get_response_pdu_size()
+ if isinstance(self.client.framer, ModbusAsciiFramer):
+ response_pdu_size = response_pdu_size * 2
if response_pdu_size:
expected_response_length = self._calculate_response_length(response_pdu_size)
@@ -121,15 +125,9 @@ class ModbusTransactionManager(object):
packet = self.client.framer.buildPacket(request)
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet]))
- self.client._send(packet)
- exception = False
- result = self.client._recv(expected_response_length or 1024)
- while result and expected_response_length and len(result) < expected_response_length:
- if not exception and not self._check_response(result):
- exception = True
- expected_response_length = self._calculate_exception_length()
- continue
- result += self.client._recv(expected_response_length - len(result))
+ self._send(packet)
+ # exception = False
+ result = self._recv(expected_response_length or 1024)
if not result and self.retry_on_empty:
retries -= 1
@@ -139,19 +137,49 @@ class ModbusTransactionManager(object):
_logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in result]))
self.client.framer.processIncomingPacket(result, self.addTransaction)
break
- except (socket.error, ModbusIOException, InvalidResponseRecievedException) as msg:
+ except (socket.error, ModbusIOException, InvalidMessageRecievedException) as msg:
self.client.close()
_logger.debug("Transaction failed. (%s) " % msg)
retries -= 1
last_exception = msg
response = self.getTransaction(request.transaction_id)
if not response:
- last_exception = last_exception or ("No Response "
- "received from the remote unit")
- response = ModbusIOException(last_exception)
+ if len(self.transactions):
+ response = self.getTransaction(tid=0)
+ else:
+ last_exception = last_exception or ("No Response "
+ "received from the remote unit")
+ response = ModbusIOException(last_exception)
return response
+ def _send(self, packet):
+ return self.client._send(packet)
+
+ def _recv(self, expected_response_length):
+ retries = self.retries
+ exception = False
+ while retries:
+ result = self.client._recv(expected_response_length or 1024)
+ while result and expected_response_length and len(
+ result) < expected_response_length:
+ if not exception and not self._check_response(result):
+ exception = True
+ expected_response_length = self._calculate_exception_length()
+ continue
+ if isinstance(self.client.framer, ModbusSocketFramer):
+ break
+ r = self.client._recv(expected_response_length - len(result))
+ if not r:
+ # If no response being recived there is no point in conitnuing
+ break
+ result += r
+ if result:
+ break
+ retries -= 1
+ return result
+
+
def addTransaction(self, request, tid=None):
''' Adds a transaction to the handler
@@ -439,7 +467,7 @@ class ModbusSocketFramer(IModbusFramer):
if result is None:
raise ModbusIOException("Unable to decode request")
elif error and result.function_code < 0x80:
- raise InvalidResponseRecievedException(result)
+ raise InvalidMessageRecievedException(result)
else:
self.populateResult(result)
self.advanceFrame()
@@ -453,7 +481,7 @@ class ModbusSocketFramer(IModbusFramer):
end of the message (python just doesn't have the resolution to
check for millisecond delays).
'''
- self.__buffer = ''
+ self.__buffer = b''
self.__header = {}
def getRawFrame(self):
@@ -680,7 +708,7 @@ class ModbusRtuFramer(IModbusFramer):
if result is None:
raise ModbusIOException("Unable to decode request")
elif error and result.function_code < 0x80:
- raise InvalidResponseRecievedException(result)
+ raise InvalidMessageRecievedException(result)
else:
self.populateResult(result)
self.advanceFrame()
@@ -786,6 +814,17 @@ class ModbusAsciiFramer(IModbusFramer):
if end > 0: return a2b_hex(buffer)
return b''
+ def resetFrame(self):
+ ''' Reset the entire message frame.
+ This allows us to skip ovver errors that may be in the stream.
+ It is hard to know if we are simply out of sync or if there is
+ an error in the stream as we have no way to check the start or
+ end of the message (python just doesn't have the resolution to
+ check for millisecond delays).
+ '''
+ self.__buffer = b''
+ self.__header = {'lrc':'0000', 'len':0, 'uid':0x00}
+
def populateResult(self, result):
''' Populates the modbus result header
@@ -1018,6 +1057,17 @@ class ModbusBinaryFramer(IModbusFramer):
array.append(d)
return bytes(array)
+ def resetFrame(self):
+ ''' Reset the entire message frame.
+ This allows us to skip ovver errors that may be in the stream.
+ It is hard to know if we are simply out of sync or if there is
+ an error in the stream as we have no way to check the start or
+ end of the message (python just doesn't have the resolution to
+ check for millisecond delays).
+ '''
+ self.__buffer = b''
+ self.__header = {'crc': 0x0000, 'len': 0, 'uid': 0x00}
+
#---------------------------------------------------------------------------#
# Exported symbols
#---------------------------------------------------------------------------#
diff --git a/pymodbus/version.py b/pymodbus/version.py
index 2fc9d29..f95e10a 100644
--- a/pymodbus/version.py
+++ b/pymodbus/version.py
@@ -41,7 +41,8 @@ class Version(object):
return '[%s, version %s]' % (self.package, self.short())
-version = Version('pymodbus', 1, 3, 1)
+version = Version('pymodbus', 1, 3, 2)
+
version.__name__ = 'pymodbus' # fix epydoc error
diff --git a/test/test_client_sync.py b/test/test_client_sync.py
index c88d682..516b442 100644
--- a/test/test_client_sync.py
+++ b/test/test_client_sync.py
@@ -202,6 +202,13 @@ class SynchronousClientTest(unittest.TestCase):
self.assertTrue(isinstance(ModbusSerialClient(method='binary').framer, ModbusBinaryFramer))
self.assertRaises(ParameterException, lambda: ModbusSerialClient(method='something'))
+ def testSyncSerialRTUClientTimeouts(self):
+ client = ModbusSerialClient(method="rtu", baudrate=9600)
+ assert client._silent_interval == (3.5 * 11/9600)
+ client = ModbusSerialClient(method="rtu", baudrate=38400)
+ assert client._silent_interval == (1.75/1000)
+
+
@patch("serial.Serial")
def testBasicSyncSerialClient(self, mock_serial):
''' Test the basic methods for the serial sync client'''
diff --git a/test/test_diag_messages.py b/test/test_diag_messages.py
index e62b8ec..ecb0939 100644
--- a/test/test_diag_messages.py
+++ b/test/test_diag_messages.py
@@ -33,7 +33,7 @@ class SimpleDataStoreTest(unittest.TestCase):
(ReturnSlaveBusCharacterOverrunCountRequest, b'\x00\x12\x00\x00', b'\x00\x12\x00\x00'),
(ReturnIopOverrunCountRequest, b'\x00\x13\x00\x00', b'\x00\x13\x00\x00'),
(ClearOverrunCountRequest, b'\x00\x14\x00\x00', b'\x00\x14\x00\x00'),
- (GetClearModbusPlusRequest, b'\x00\x15\x00\x00', b'\x00\x15' + b'\x00\x00' * 55),
+ (GetClearModbusPlusRequest, b'\x00\x15\x00\x00', b'\x00\x15\x00\x00' + b'\x00\x00' * 55),
]
self.responses = [
@@ -56,7 +56,7 @@ class SimpleDataStoreTest(unittest.TestCase):
(ReturnSlaveBusCharacterOverrunCountResponse, b'\x00\x12\x00\x00'),
(ReturnIopOverrunCountResponse, b'\x00\x13\x00\x00'),
(ClearOverrunCountResponse, b'\x00\x14\x00\x00'),
- (GetClearModbusPlusResponse, b'\x00\x15' + b'\x00\x00' * 55),
+ (GetClearModbusPlusResponse, b'\x00\x15\x00\x04' + b'\x00\x00' * 55),
]
def tearDown(self):
@@ -100,7 +100,8 @@ class SimpleDataStoreTest(unittest.TestCase):
def testDiagnosticExecute(self):
''' Testing diagnostic message execution '''
for message, encoded, executed in self.requests:
- self.assertEqual(message().execute().encode(), executed)
+ encoded = message().execute().encode()
+ self.assertEqual(encoded, executed)
def testReturnQueryDataRequest(self):
''' Testing diagnostic message execution '''
@@ -130,13 +131,14 @@ class SimpleDataStoreTest(unittest.TestCase):
def testGetClearModbusPlusRequestExecute(self):
''' Testing diagnostic message execution '''
- request = GetClearModbusPlusRequest(ModbusPlusOperation.ClearStatistics);
+ request = GetClearModbusPlusRequest(data=ModbusPlusOperation.ClearStatistics);
response = request.execute()
- self.assertEqual(response.message, None)
+ self.assertEqual(response.message, ModbusPlusOperation.ClearStatistics)
- request = GetClearModbusPlusRequest(ModbusPlusOperation.GetStatistics);
+ request = GetClearModbusPlusRequest(data=ModbusPlusOperation.GetStatistics);
response = request.execute()
- self.assertEqual(response.message, [0x00] * 55)
+ resp = [ModbusPlusOperation.GetStatistics]
+ self.assertEqual(response.message, resp+[0x00] * 55)
#---------------------------------------------------------------------------#
# Main
diff --git a/test/test_other_messages.py b/test/test_other_messages.py
index 1e9437a..08b24a2 100644
--- a/test/test_other_messages.py
+++ b/test/test_other_messages.py
@@ -92,13 +92,14 @@ class ModbusOtherMessageTest(unittest.TestCase):
self.assertEqual(request.execute().function_code, 0x11)
response = ReportSlaveIdResponse(request.execute().identifier, True)
- self.assertEqual(response.encode(), b'\x0aPymodbus\xff')
+
+ self.assertEqual(response.encode(), b'\tPymodbus\xff')
response.decode(b'\x03\x12\x00')
self.assertEqual(response.status, False)
self.assertEqual(response.identifier, b'\x12\x00')
response.status = False
- self.assertEqual(response.encode(), b'\x04\x12\x00\x00')
+ self.assertEqual(response.encode(), b'\x03\x12\x00\x00')
#---------------------------------------------------------------------------#
# Main
diff --git a/test/test_payload.py b/test/test_payload.py
index 157f584..2375c95 100644
--- a/test/test_payload.py
+++ b/test/test_payload.py
@@ -31,13 +31,15 @@ class ModbusPayloadUtilityTests(unittest.TestCase):
b'\x01\x02\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00' \
b'\x00\x00\x00\xff\xfe\xff\xfd\xff\xff\xff\xfc\xff' \
b'\xff\xff\xff\xff\xff\xff\x00\x00\xa0\x3f\x00\x00' \
- b'\x00\x00\x00\x00\x19\x40\x74\x65\x73\x74\x11'
+ b'\x00\x00\x00\x00\x19\x40\x01\x00\x74\x65\x73\x74' \
+ b'\x11'
self.big_endian_payload = \
b'\x01\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00' \
b'\x00\x00\x04\xff\xff\xfe\xff\xff\xff\xfd\xff\xff' \
b'\xff\xff\xff\xff\xff\xfc\x3f\xa0\x00\x00\x40\x19' \
- b'\x00\x00\x00\x00\x00\x00\x74\x65\x73\x74\x11'
+ b'\x00\x00\x00\x00\x00\x00\x00\x01\x74\x65\x73\x74' \
+ b'\x11'
self.bitstring = [True, False, False, False, True, False, False, False]
@@ -62,6 +64,7 @@ class ModbusPayloadUtilityTests(unittest.TestCase):
builder.add_64bit_int(-4)
builder.add_32bit_float(1.25)
builder.add_64bit_float(6.25)
+ builder.add_16bit_uint(1) # placeholder
builder.add_string(b'test')
builder.add_bits(self.bitstring)
self.assertEqual(self.little_endian_payload, builder.to_string())
@@ -79,6 +82,7 @@ class ModbusPayloadUtilityTests(unittest.TestCase):
builder.add_64bit_int(-4)
builder.add_32bit_float(1.25)
builder.add_64bit_float(6.25)
+ builder.add_16bit_uint(1) # placeholder
builder.add_string('test')
builder.add_bits(self.bitstring)
self.assertEqual(self.big_endian_payload, builder.to_string())
@@ -125,6 +129,7 @@ class ModbusPayloadUtilityTests(unittest.TestCase):
self.assertEqual(-4, decoder.decode_64bit_int())
self.assertEqual(1.25, decoder.decode_32bit_float())
self.assertEqual(6.25, decoder.decode_64bit_float())
+ self.assertEqual(None, decoder.skip_bytes(2))
self.assertEqual('test', decoder.decode_string(4).decode())
self.assertEqual(self.bitstring, decoder.decode_bits())
@@ -141,6 +146,7 @@ class ModbusPayloadUtilityTests(unittest.TestCase):
self.assertEqual(-4, decoder.decode_64bit_int())
self.assertEqual(1.25, decoder.decode_32bit_float())
self.assertEqual(6.25, decoder.decode_64bit_float())
+ self.assertEqual(None, decoder.skip_bytes(2))
self.assertEqual(b'test', decoder.decode_string(4))
self.assertEqual(self.bitstring, decoder.decode_bits())
diff --git a/test/test_server_sync.py b/test/test_server_sync.py
index e6a9743..348f2ff 100644
--- a/test/test_server_sync.py
+++ b/test/test_server_sync.py
@@ -33,6 +33,7 @@ class MockServer(object):
self.threads = []
self.context = {}
+
#---------------------------------------------------------------------------#
# Fixture
#---------------------------------------------------------------------------#
@@ -280,24 +281,30 @@ class SynchronousServerTest(unittest.TestCase):
#-----------------------------------------------------------------------#
def testSerialServerConnect(self):
with patch.object(serial, 'Serial') as mock_serial:
- mock_serial.return_value = "socket"
- identity = ModbusDeviceIdentification(info={0x00: 'VendorName'})
- server = ModbusSerialServer(context=None, identity=identity)
- self.assertEqual(server.socket, "socket")
- self.assertEqual(server.control.Identity.VendorName, 'VendorName')
-
- server._connect()
- self.assertEqual(server.socket, "socket")
+ # mock_serial.return_value = "socket"
+ mock_serial.write = lambda x: len(x)
+ mock_serial.read = lambda size: '\x00' * size
+ identity = ModbusDeviceIdentification(info={0x00: 'VendorName'})
+ server = ModbusSerialServer(context=None, identity=identity, port="dummy")
+ # # mock_serial.return_value = "socket"
+ # self.assertEqual(server.socket.port, "dummy")
+ self.assertEquals(server.handler.__class__.__name__, "CustomSingleRequestHandler")
+ self.assertEqual(server.control.Identity.VendorName, 'VendorName')
+
+ server._connect()
+ # self.assertEqual(server.socket, "socket")
with patch.object(serial, 'Serial') as mock_serial:
+ mock_serial.write = lambda x: len(x)
+ mock_serial.read = lambda size: '\x00' * size
mock_serial.side_effect = serial.SerialException()
- server = ModbusSerialServer(None)
+ server = ModbusSerialServer(None, port="dummy")
self.assertEqual(server.socket, None)
def testSerialServerServeForever(self):
''' test that the synchronous serial server closes correctly '''
with patch.object(serial, 'Serial') as mock_serial:
- with patch('pymodbus.server.sync.ModbusSingleRequestHandler') as mock_handler:
+ with patch('pymodbus.server.sync.CustomSingleRequestHandler') as mock_handler:
server = ModbusSerialServer(None)
instance = mock_handler.return_value
instance.handle.side_effect = server.server_close
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/pymodbus.git
More information about the Python-modules-commits
mailing list