Skip to content

Commit

Permalink
pymodbus 1.5.0 (#294)
Browse files Browse the repository at this point in the history
* Improve transaction speeds for sync clients (RTU/ASCII), now retry on empty happens only when retry_on_empty kwarg is passed to client during intialization

`client = Client(..., retry_on_empty=True)`

* Fix tcp servers (sync/async) not processing requests with transaction id > 255
* Introduce new api to check if the received response is an error or not (response.isError())
* Move timing logic to framers so that irrespective of client, correct timing logics are followed.
* Move framers from transaction.py to respective modules
* Fix modbus payload builder and decoder
* Async servers can now have an option to defer `reactor.run()` when using `Start<Tcp/Serial/Udo>Server(...,defer_reactor_run=True)`
* Fix UDP client issue while handling MEI messages (ReadDeviceInformationRequest)
* Add expected response lengths for WriteMultipleCoilRequest and WriteMultipleRegisterRequest
* Fix _rtu_byte_count_pos for GetCommEventLogResponse
* Add support for repeated MEI device information Object IDs
* Fix struct errors while decoding stray response
* Modbus read retries works only when empty/no message is received
* Change test runner from nosetest to pytest
* Fix Misc examples
  • Loading branch information
dhoomakethu committed Apr 27, 2018
1 parent 4ed1f1a commit 8f3fc71
Show file tree
Hide file tree
Showing 52 changed files with 2,704 additions and 1,685 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
Version 1.5.0
------------------------------------------------------------
* Improve transaction speeds for sync clients (RTU/ASCII), now retry on empty happens only when retry_on_empty kwarg is passed to client during intialization

`client = Client(..., retry_on_empty=True)`

* Fix tcp servers (sync/async) not processing requests with transaction id > 255
* Introduce new api to check if the received response is an error or not (response.isError())
* Move timing logic to framers so that irrespective of client, correct timing logics are followed.
* Move framers from transaction.py to respective modules
* Fix modbus payload builder and decoder
* Async servers can now have an option to defer `reactor.run()` when using `Start<Tcp/Serial/Udo>Server(...,defer_reactor_run=True)`
* Fix UDP client issue while handling MEI messages (ReadDeviceInformationRequest)
* Add expected response lengths for WriteMultipleCoilRequest and WriteMultipleRegisterRequest
* Fix _rtu_byte_count_pos for GetCommEventLogResponse
* Add support for repeated MEI device information Object IDs
* Fix struct errors while decoding stray response
* Modbus read retries works only when empty/no message is received
* Change test runner from nosetest to pytest
* Fix Misc examples

Version 1.4.0
------------------------------------------------------------
* Bug fix Modbus TCP client reading incomplete data
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ check: install

test: install
@pip install --quiet --requirement=requirements-tests.txt
@nosetests --with-coverage --cover-html
@py.test
@coverage report --fail-under=90

tox: install
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ For those of you that just want to get started fast, here you go::
client = ModbusTcpClient('127.0.0.1')
client.write_coil(1, True)
result = client.read_coils(1,1)
print result.bits[0]
print(result.bits[0])
client.close()

For more advanced examples, check out the examples included in the
Expand Down
47 changes: 47 additions & 0 deletions doc/source/library/pymodbus.framer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pymodbus\.framer package
========================

Submodules
----------

pymodbus\.framer\.ascii_framer module
-------------------------------------

.. automodule:: pymodbus.framer.ascii_framer
:members:
:undoc-members:
:show-inheritance:

pymodbus\.framer\.binary_framer module
--------------------------------------

.. automodule:: pymodbus.framer.binary_framer
:members:
:undoc-members:
:show-inheritance:

pymodbus\.framer\.rtu_framer module
-----------------------------------

.. automodule:: pymodbus.framer.rtu_framer
:members:
:undoc-members:
:show-inheritance:

pymodbus\.framer\.socket_framer module
--------------------------------------

.. automodule:: pymodbus.framer.socket_framer
:members:
:undoc-members:
:show-inheritance:


Module contents
---------------

.. automodule:: pymodbus.framer
:members:
:undoc-members:
:show-inheritance:

2 changes: 2 additions & 0 deletions doc/source/library/pymodbus.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ Subpackages

pymodbus.client
pymodbus.datastore
pymodbus.framer
pymodbus.internal
pymodbus.server


Submodules
----------

Expand Down
97 changes: 61 additions & 36 deletions examples/common/asynchronous_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,67 @@
The following is an example of how to use the asynchronous modbus
client implementation from pymodbus.
"""
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# import needed libraries
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
from twisted.internet import reactor, protocol
from pymodbus.constants import Defaults

# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# choose the requested modbus protocol
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
from pymodbus.client.async import ModbusClientProtocol
#from pymodbus.client.async import ModbusUdpClientProtocol
from pymodbus.client.async import ModbusUdpClientProtocol
from pymodbus.framer.rtu_framer import ModbusRtuFramer

# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# configure the client logging
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
import logging
logging.basicConfig()
FORMAT = ('%(asctime)-15s %(threadName)-15s'
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# helper method to test deferred callbacks
# --------------------------------------------------------------------------- #


def dassert(deferred, callback):
def _assertor(value):
assert value

deferred.addCallback(lambda r: _assertor(callback(r)))
deferred.addErrback(lambda _: _assertor(False))

# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# specify slave to query
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# The slave to query is specified in an optional parameter for each
# individual request. This can be done by specifying the `unit` parameter
# which defaults to `0x00`
# --------------------------------------------------------------------------- #


def processResponse(result):
log.debug(result)


def exampleRequests(client):
rr = client.read_coils(1, 1, unit=0x02)
rr.addCallback(processResponse)
rr = client.read_holding_registers(1, 1, unit=0x02)
rr.addCallback(processResponse)
rr = client.read_discrete_inputs(1, 1, unit=0x02)
rr.addCallback(processResponse)
rr = client.read_input_registers(1, 1, unit=0x02)
rr.addCallback(processResponse)
stopAsynchronousTest(client)

# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# example requests
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# simply call the methods that you would like to use. An example session
# is displayed below along with some assert checks. Note that unlike the
# synchronous version of the client, the asynchronous version returns
Expand All @@ -61,54 +75,59 @@ def exampleRequests(client):
# deferred assert helper(dassert).
# --------------------------------------------------------------------------- #

UNIT = 0x01

UNIT = 0x00


def stopAsynchronousTest(client):
# ----------------------------------------------------------------------- #
# close the client at some time later
# ----------------------------------------------------------------------- #
reactor.callLater(1, client.transport.loseConnection)
reactor.callLater(2, reactor.stop)

def beginAsynchronousTest(client):
rq = client.write_coil(1, True, unit=UNIT)
rr = client.read_coils(1, 1, unit=UNIT)
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
dassert(rq, lambda r: not r.isError()) # test for no error
dassert(rr, lambda r: r.bits[0] == True) # test the expected value

rq = client.write_coils(1, [True]*8, unit=UNIT)
rr = client.read_coils(1, 8, unit=UNIT)
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
dassert(rq, lambda r: not r.isError()) # test for no error
dassert(rr, lambda r: r.bits == [True]*8) # test the expected value

rq = client.write_coils(1, [False]*8, unit=UNIT)
rr = client.read_discrete_inputs(1, 8, unit=UNIT)
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
dassert(rq, lambda r: not r.isError()) # test for no error
dassert(rr, lambda r: r.bits == [True]*8) # test the expected value

rq = client.write_register(1, 10, unit=UNIT)
rr = client.read_holding_registers(1, 1, unit=UNIT)
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
dassert(rq, lambda r: not r.isError()) # test for no error
dassert(rr, lambda r: r.registers[0] == 10) # test the expected value

rq = client.write_registers(1, [10]*8, unit=UNIT)
rr = client.read_input_registers(1, 8, unit=UNIT)
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
dassert(rq, lambda r: not r.isError()) # test for no error
dassert(rr, lambda r: r.registers == [17]*8) # test the expected value

arguments = {
'read_address': 1,
'read_count': 8,
'write_address': 1,
'write_registers': [20]*8,
}
rq = client.readwrite_registers(**arguments, unit=UNIT)
rq = client.readwrite_registers(arguments, unit=UNIT)
rr = client.read_input_registers(1, 8, unit=UNIT)
dassert(rq, lambda r: r.registers == [20]*8) # test the expected value
dassert(rr, lambda r: r.registers == [17]*8) # test the expected value
stopAsynchronousTest(client)

# ----------------------------------------------------------------------- #
# close the client at some time later
# ----------------------------------------------------------------------- #
reactor.callLater(1, client.transport.loseConnection)
reactor.callLater(2, reactor.stop)

# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# extra requests
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# If you are performing a request that is not available in the client
# mixin, you have to perform the request like this instead::
#
Expand All @@ -120,11 +139,11 @@ def beginAsynchronousTest(client):
# if isinstance(response, ClearCountersResponse):
# ... do something with the response
#
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #

# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# choose the client you want
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# make sure to start an implementation to hit against. For this
# you can use an existing device, the reference implementation in the tools
# directory, or start a pymodbus server.
Expand All @@ -134,5 +153,11 @@ def beginAsynchronousTest(client):
if __name__ == "__main__":
defer = protocol.ClientCreator(
reactor, ModbusClientProtocol).connectTCP("localhost", 5020)

# TCP server with a different framer

# defer = protocol.ClientCreator(
# reactor, ModbusClientProtocol, framer=ModbusRtuFramer).connectTCP(
# "localhost", 5020)
defer.addCallback(beginAsynchronousTest)
reactor.run()
10 changes: 6 additions & 4 deletions examples/common/asynchronous_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@
# configure the client logging
# --------------------------------------------------------------------------- #
import logging
logging.basicConfig()
log = logging.getLogger("pymodbus")
FORMAT = ('%(asctime)-15s %(threadName)-15s'
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

# --------------------------------------------------------------------------- #
# state a few constants
# --------------------------------------------------------------------------- #
SERIAL_PORT = "/dev/ttyp0"
SERIAL_PORT = "/dev/ptyp0"
STATUS_REGS = (1, 2)
STATUS_COILS = (1, 3)
CLIENT_DELAY = 1
Expand Down Expand Up @@ -173,7 +175,7 @@ def write(self, response):

def main():
log.debug("Initializing the client")
framer = ModbusFramer(ClientDecoder())
framer = ModbusFramer(ClientDecoder(), client=None)
reader = LoggingLineReader()
factory = ExampleFactory(framer, reader)
SerialModbusClient(factory, SERIAL_PORT, reactor)
Expand Down
45 changes: 36 additions & 9 deletions examples/common/asynchronous_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
from pymodbus.transaction import (ModbusRtuFramer,
ModbusAsciiFramer,
ModbusBinaryFramer)

# --------------------------------------------------------------------------- #
# configure the service logging
# --------------------------------------------------------------------------- #
import logging
logging.basicConfig()
FORMAT = ('%(asctime)-15s %(threadName)-15s'
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

Expand Down Expand Up @@ -101,18 +105,41 @@ def run_async_server():
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'Pymodbus Server'
identity.ModelName = 'Pymodbus Server'
identity.MajorMinorRevision = '1.0'
identity.MajorMinorRevision = '1.5'

# ----------------------------------------------------------------------- #
# run the server you want
# ----------------------------------------------------------------------- #


# TCP Server

StartTcpServer(context, identity=identity, address=("localhost", 5020))
# StartUdpServer(context, identity=identity, address=("localhost", 502))
# StartSerialServer(context, identity=identity,
# port='/dev/pts/3', framer=ModbusRtuFramer)
# StartSerialServer(context, identity=identity,
# port='/dev/pts/3', framer=ModbusAsciiFramer)

# TCP Server with deferred reactor run

# from twisted.internet import reactor
# StartTcpServer(context, identity=identity, address=("localhost", 5020),
# defer_reactor_run=True)
# reactor.run()

# Server with RTU framer
# StartTcpServer(context, identity=identity, address=("localhost", 5020),
# framer=ModbusRtuFramer)

# UDP Server
# StartUdpServer(context, identity=identity, address=("127.0.0.1", 5020))

# RTU Server
# StartSerialServer(context, identity=identity,
# port='/dev/ttyp0', framer=ModbusRtuFramer)

# ASCII Server
# StartSerialServer(context, identity=identity,
# port='/dev/ttyp0', framer=ModbusAsciiFramer)

# Binary Server
# StartSerialServer(context, identity=identity,
# port='/dev/ttyp0', framer=ModbusBinaryFramer)


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit 8f3fc71

Please sign in to comment.