Skip to content

Commit 8f3fc71

Browse files
authored
pymodbus 1.5.0 (#294)
* 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
1 parent 4ed1f1a commit 8f3fc71

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2704
-1685
lines changed

CHANGELOG.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
Version 1.5.0
2+
------------------------------------------------------------
3+
* 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
4+
5+
`client = Client(..., retry_on_empty=True)`
6+
7+
* Fix tcp servers (sync/async) not processing requests with transaction id > 255
8+
* Introduce new api to check if the received response is an error or not (response.isError())
9+
* Move timing logic to framers so that irrespective of client, correct timing logics are followed.
10+
* Move framers from transaction.py to respective modules
11+
* Fix modbus payload builder and decoder
12+
* 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)
14+
* Add expected response lengths for WriteMultipleCoilRequest and WriteMultipleRegisterRequest
15+
* Fix _rtu_byte_count_pos for GetCommEventLogResponse
16+
* Add support for repeated MEI device information Object IDs
17+
* Fix struct errors while decoding stray response
18+
* Modbus read retries works only when empty/no message is received
19+
* Change test runner from nosetest to pytest
20+
* Fix Misc examples
21+
122
Version 1.4.0
223
------------------------------------------------------------
324
* Bug fix Modbus TCP client reading incomplete data

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ check: install
3939

4040
test: install
4141
@pip install --quiet --requirement=requirements-tests.txt
42-
@nosetests --with-coverage --cover-html
42+
@py.test
4343
@coverage report --fail-under=90
4444

4545
tox: install

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ For those of you that just want to get started fast, here you go::
7575
client = ModbusTcpClient('127.0.0.1')
7676
client.write_coil(1, True)
7777
result = client.read_coils(1,1)
78-
print result.bits[0]
78+
print(result.bits[0])
7979
client.close()
8080

8181
For more advanced examples, check out the examples included in the
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
pymodbus\.framer package
2+
========================
3+
4+
Submodules
5+
----------
6+
7+
pymodbus\.framer\.ascii_framer module
8+
-------------------------------------
9+
10+
.. automodule:: pymodbus.framer.ascii_framer
11+
:members:
12+
:undoc-members:
13+
:show-inheritance:
14+
15+
pymodbus\.framer\.binary_framer module
16+
--------------------------------------
17+
18+
.. automodule:: pymodbus.framer.binary_framer
19+
:members:
20+
:undoc-members:
21+
:show-inheritance:
22+
23+
pymodbus\.framer\.rtu_framer module
24+
-----------------------------------
25+
26+
.. automodule:: pymodbus.framer.rtu_framer
27+
:members:
28+
:undoc-members:
29+
:show-inheritance:
30+
31+
pymodbus\.framer\.socket_framer module
32+
--------------------------------------
33+
34+
.. automodule:: pymodbus.framer.socket_framer
35+
:members:
36+
:undoc-members:
37+
:show-inheritance:
38+
39+
40+
Module contents
41+
---------------
42+
43+
.. automodule:: pymodbus.framer
44+
:members:
45+
:undoc-members:
46+
:show-inheritance:
47+

doc/source/library/pymodbus.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ Subpackages
88

99
pymodbus.client
1010
pymodbus.datastore
11+
pymodbus.framer
1112
pymodbus.internal
1213
pymodbus.server
1314

15+
1416
Submodules
1517
----------
1618

examples/common/asynchronous_client.py

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,53 +6,67 @@
66
The following is an example of how to use the asynchronous modbus
77
client implementation from pymodbus.
88
"""
9-
# --------------------------------------------------------------------------- #
9+
# --------------------------------------------------------------------------- #
1010
# import needed libraries
11-
# --------------------------------------------------------------------------- #
11+
# --------------------------------------------------------------------------- #
1212
from twisted.internet import reactor, protocol
1313
from pymodbus.constants import Defaults
1414

15-
# --------------------------------------------------------------------------- #
15+
# --------------------------------------------------------------------------- #
1616
# choose the requested modbus protocol
17-
# --------------------------------------------------------------------------- #
17+
# --------------------------------------------------------------------------- #
1818
from pymodbus.client.async import ModbusClientProtocol
19-
#from pymodbus.client.async import ModbusUdpClientProtocol
19+
from pymodbus.client.async import ModbusUdpClientProtocol
20+
from pymodbus.framer.rtu_framer import ModbusRtuFramer
2021

21-
# --------------------------------------------------------------------------- #
22+
# --------------------------------------------------------------------------- #
2223
# configure the client logging
23-
# --------------------------------------------------------------------------- #
24+
# --------------------------------------------------------------------------- #
2425
import logging
25-
logging.basicConfig()
26+
FORMAT = ('%(asctime)-15s %(threadName)-15s'
27+
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
28+
logging.basicConfig(format=FORMAT)
2629
log = logging.getLogger()
2730
log.setLevel(logging.DEBUG)
2831

29-
# --------------------------------------------------------------------------- #
32+
# --------------------------------------------------------------------------- #
3033
# helper method to test deferred callbacks
3134
# --------------------------------------------------------------------------- #
3235

3336

3437
def dassert(deferred, callback):
3538
def _assertor(value):
3639
assert value
37-
3840
deferred.addCallback(lambda r: _assertor(callback(r)))
3941
deferred.addErrback(lambda _: _assertor(False))
4042

41-
# --------------------------------------------------------------------------- #
43+
# --------------------------------------------------------------------------- #
4244
# specify slave to query
43-
# --------------------------------------------------------------------------- #
45+
# --------------------------------------------------------------------------- #
4446
# The slave to query is specified in an optional parameter for each
4547
# individual request. This can be done by specifying the `unit` parameter
4648
# which defaults to `0x00`
4749
# --------------------------------------------------------------------------- #
4850

4951

52+
def processResponse(result):
53+
log.debug(result)
54+
55+
5056
def exampleRequests(client):
5157
rr = client.read_coils(1, 1, unit=0x02)
58+
rr.addCallback(processResponse)
59+
rr = client.read_holding_registers(1, 1, unit=0x02)
60+
rr.addCallback(processResponse)
61+
rr = client.read_discrete_inputs(1, 1, unit=0x02)
62+
rr.addCallback(processResponse)
63+
rr = client.read_input_registers(1, 1, unit=0x02)
64+
rr.addCallback(processResponse)
65+
stopAsynchronousTest(client)
5266

53-
# --------------------------------------------------------------------------- #
67+
# --------------------------------------------------------------------------- #
5468
# example requests
55-
# --------------------------------------------------------------------------- #
69+
# --------------------------------------------------------------------------- #
5670
# simply call the methods that you would like to use. An example session
5771
# is displayed below along with some assert checks. Note that unlike the
5872
# synchronous version of the client, the asynchronous version returns
@@ -61,54 +75,59 @@ def exampleRequests(client):
6175
# deferred assert helper(dassert).
6276
# --------------------------------------------------------------------------- #
6377

64-
UNIT = 0x01
78+
79+
UNIT = 0x00
80+
81+
82+
def stopAsynchronousTest(client):
83+
# ----------------------------------------------------------------------- #
84+
# close the client at some time later
85+
# ----------------------------------------------------------------------- #
86+
reactor.callLater(1, client.transport.loseConnection)
87+
reactor.callLater(2, reactor.stop)
6588

6689
def beginAsynchronousTest(client):
6790
rq = client.write_coil(1, True, unit=UNIT)
6891
rr = client.read_coils(1, 1, unit=UNIT)
69-
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
92+
dassert(rq, lambda r: not r.isError()) # test for no error
7093
dassert(rr, lambda r: r.bits[0] == True) # test the expected value
71-
94+
7295
rq = client.write_coils(1, [True]*8, unit=UNIT)
7396
rr = client.read_coils(1, 8, unit=UNIT)
74-
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
97+
dassert(rq, lambda r: not r.isError()) # test for no error
7598
dassert(rr, lambda r: r.bits == [True]*8) # test the expected value
76-
99+
77100
rq = client.write_coils(1, [False]*8, unit=UNIT)
78101
rr = client.read_discrete_inputs(1, 8, unit=UNIT)
79-
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
102+
dassert(rq, lambda r: not r.isError()) # test for no error
80103
dassert(rr, lambda r: r.bits == [True]*8) # test the expected value
81-
104+
82105
rq = client.write_register(1, 10, unit=UNIT)
83106
rr = client.read_holding_registers(1, 1, unit=UNIT)
84-
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
107+
dassert(rq, lambda r: not r.isError()) # test for no error
85108
dassert(rr, lambda r: r.registers[0] == 10) # test the expected value
86-
109+
87110
rq = client.write_registers(1, [10]*8, unit=UNIT)
88111
rr = client.read_input_registers(1, 8, unit=UNIT)
89-
dassert(rq, lambda r: r.function_code < 0x80) # test for no error
112+
dassert(rq, lambda r: not r.isError()) # test for no error
90113
dassert(rr, lambda r: r.registers == [17]*8) # test the expected value
91-
114+
92115
arguments = {
93116
'read_address': 1,
94117
'read_count': 8,
95118
'write_address': 1,
96119
'write_registers': [20]*8,
97120
}
98-
rq = client.readwrite_registers(**arguments, unit=UNIT)
121+
rq = client.readwrite_registers(arguments, unit=UNIT)
99122
rr = client.read_input_registers(1, 8, unit=UNIT)
100123
dassert(rq, lambda r: r.registers == [20]*8) # test the expected value
101124
dassert(rr, lambda r: r.registers == [17]*8) # test the expected value
125+
stopAsynchronousTest(client)
102126

103-
# ----------------------------------------------------------------------- #
104-
# close the client at some time later
105-
# ----------------------------------------------------------------------- #
106-
reactor.callLater(1, client.transport.loseConnection)
107-
reactor.callLater(2, reactor.stop)
108127

109-
# --------------------------------------------------------------------------- #
128+
# --------------------------------------------------------------------------- #
110129
# extra requests
111-
# --------------------------------------------------------------------------- #
130+
# --------------------------------------------------------------------------- #
112131
# If you are performing a request that is not available in the client
113132
# mixin, you have to perform the request like this instead::
114133
#
@@ -120,11 +139,11 @@ def beginAsynchronousTest(client):
120139
# if isinstance(response, ClearCountersResponse):
121140
# ... do something with the response
122141
#
123-
# --------------------------------------------------------------------------- #
142+
# --------------------------------------------------------------------------- #
124143

125-
# --------------------------------------------------------------------------- #
144+
# --------------------------------------------------------------------------- #
126145
# choose the client you want
127-
# --------------------------------------------------------------------------- #
146+
# --------------------------------------------------------------------------- #
128147
# make sure to start an implementation to hit against. For this
129148
# you can use an existing device, the reference implementation in the tools
130149
# directory, or start a pymodbus server.
@@ -134,5 +153,11 @@ def beginAsynchronousTest(client):
134153
if __name__ == "__main__":
135154
defer = protocol.ClientCreator(
136155
reactor, ModbusClientProtocol).connectTCP("localhost", 5020)
156+
157+
# TCP server with a different framer
158+
159+
# defer = protocol.ClientCreator(
160+
# reactor, ModbusClientProtocol, framer=ModbusRtuFramer).connectTCP(
161+
# "localhost", 5020)
137162
defer.addCallback(beginAsynchronousTest)
138163
reactor.run()

examples/common/asynchronous_processor.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@
2626
# configure the client logging
2727
# --------------------------------------------------------------------------- #
2828
import logging
29-
logging.basicConfig()
30-
log = logging.getLogger("pymodbus")
29+
FORMAT = ('%(asctime)-15s %(threadName)-15s'
30+
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
31+
logging.basicConfig(format=FORMAT)
32+
log = logging.getLogger()
3133
log.setLevel(logging.DEBUG)
3234

3335
# --------------------------------------------------------------------------- #
3436
# state a few constants
3537
# --------------------------------------------------------------------------- #
36-
SERIAL_PORT = "/dev/ttyp0"
38+
SERIAL_PORT = "/dev/ptyp0"
3739
STATUS_REGS = (1, 2)
3840
STATUS_COILS = (1, 3)
3941
CLIENT_DELAY = 1
@@ -173,7 +175,7 @@ def write(self, response):
173175

174176
def main():
175177
log.debug("Initializing the client")
176-
framer = ModbusFramer(ClientDecoder())
178+
framer = ModbusFramer(ClientDecoder(), client=None)
177179
reader = LoggingLineReader()
178180
factory = ExampleFactory(framer, reader)
179181
SerialModbusClient(factory, SERIAL_PORT, reactor)

examples/common/asynchronous_server.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717
from pymodbus.device import ModbusDeviceIdentification
1818
from pymodbus.datastore import ModbusSequentialDataBlock
1919
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
20-
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
20+
from pymodbus.transaction import (ModbusRtuFramer,
21+
ModbusAsciiFramer,
22+
ModbusBinaryFramer)
2123

2224
# --------------------------------------------------------------------------- #
2325
# configure the service logging
2426
# --------------------------------------------------------------------------- #
2527
import logging
26-
logging.basicConfig()
28+
FORMAT = ('%(asctime)-15s %(threadName)-15s'
29+
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
30+
logging.basicConfig(format=FORMAT)
2731
log = logging.getLogger()
2832
log.setLevel(logging.DEBUG)
2933

@@ -101,18 +105,41 @@ def run_async_server():
101105
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
102106
identity.ProductName = 'Pymodbus Server'
103107
identity.ModelName = 'Pymodbus Server'
104-
identity.MajorMinorRevision = '1.0'
108+
identity.MajorMinorRevision = '1.5'
105109

106110
# ----------------------------------------------------------------------- #
107111
# run the server you want
108112
# ----------------------------------------------------------------------- #
109-
113+
114+
# TCP Server
115+
110116
StartTcpServer(context, identity=identity, address=("localhost", 5020))
111-
# StartUdpServer(context, identity=identity, address=("localhost", 502))
112-
# StartSerialServer(context, identity=identity,
113-
# port='/dev/pts/3', framer=ModbusRtuFramer)
114-
# StartSerialServer(context, identity=identity,
115-
# port='/dev/pts/3', framer=ModbusAsciiFramer)
117+
118+
# TCP Server with deferred reactor run
119+
120+
# from twisted.internet import reactor
121+
# StartTcpServer(context, identity=identity, address=("localhost", 5020),
122+
# defer_reactor_run=True)
123+
# reactor.run()
124+
125+
# Server with RTU framer
126+
# StartTcpServer(context, identity=identity, address=("localhost", 5020),
127+
# framer=ModbusRtuFramer)
128+
129+
# UDP Server
130+
# StartUdpServer(context, identity=identity, address=("127.0.0.1", 5020))
131+
132+
# RTU Server
133+
# StartSerialServer(context, identity=identity,
134+
# port='/dev/ttyp0', framer=ModbusRtuFramer)
135+
136+
# ASCII Server
137+
# StartSerialServer(context, identity=identity,
138+
# port='/dev/ttyp0', framer=ModbusAsciiFramer)
139+
140+
# Binary Server
141+
# StartSerialServer(context, identity=identity,
142+
# port='/dev/ttyp0', framer=ModbusBinaryFramer)
116143

117144

118145
if __name__ == "__main__":

0 commit comments

Comments
 (0)