Skip to content

Commit eab4dce

Browse files
committed
1. Merge branch 'master' into async (#254 #246)
2. Fix/update unit tests
2 parents e91d5de + 7f788a4 commit eab4dce

Some content is hidden

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

66 files changed

+3713
-1838
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pymodbus.egg-info/
77
.vscode
88
.idea
99
.noseids
10-
10+
*.db
1111
.idea/
1212
.tox/
1313
doc/api/epydoc/html/
@@ -35,3 +35,4 @@ test/__pycache__/
3535
/doc/sphinx/
3636
/doc/html/
3737
/doc/_build/
38+
.pytest_cache/

CHANGELOG.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,54 @@ Version 2.0.0rc1
44

55
* Async client implementation based on Tornado, Twisted and asyncio
66

7+
Version 1.5.1
8+
------------------------------------------------------------
9+
* Fix device information selectors
10+
* Fixed behaviour of the MEI device information command as a server when an invalid object_id is provided by an external client.
11+
* Add support for repeated MEI device information Object IDs (client/server)
12+
* Added support for encoding device information when it requires more than one PDU to pack.
13+
* Added REPR statements for all syncchronous clients
14+
* Added `isError` method to exceptions, Any response received can be tested for success before proceeding.
15+
16+
```
17+
res = client.read_holding_registers(...)
18+
if not res.isError():
19+
# proceed
20+
else:
21+
# handle error or raise
22+
```
23+
* Add examples for MEI read device information request
24+
25+
Version 1.5.0
26+
------------------------------------------------------------
27+
* 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
28+
29+
`client = Client(..., retry_on_empty=True)`
30+
31+
* Fix tcp servers (sync/async) not processing requests with transaction id > 255
32+
* Introduce new api to check if the received response is an error or not (response.isError())
33+
* Move timing logic to framers so that irrespective of client, correct timing logics are followed.
34+
* Move framers from transaction.py to respective modules
35+
* Fix modbus payload builder and decoder
36+
* Async servers can now have an option to defer `reactor.run()` when using `Start<Tcp/Serial/Udo>Server(...,defer_reactor_run=True)`
37+
* Fix UDP client issue while handling MEI messages (ReadDeviceInformationRequest)
38+
* Add expected response lengths for WriteMultipleCoilRequest and WriteMultipleRegisterRequest
39+
* Fix _rtu_byte_count_pos for GetCommEventLogResponse
40+
* Add support for repeated MEI device information Object IDs
41+
* Fix struct errors while decoding stray response
42+
* Modbus read retries works only when empty/no message is received
43+
* Change test runner from nosetest to pytest
44+
* Fix Misc examples
45+
>>>>>>> master
46+
747
Version 1.4.0
848
------------------------------------------------------------
949
* Bug fix Modbus TCP client reading incomplete data
1050
* Check for slave unit id before processing the request for serial clients
1151
* Bug fix serial servers with Modbus Binary Framer
1252
* Bug fix header size for ModbusBinaryFramer
53+
* Bug fix payload decoder with endian Little
54+
* Payload builder and decoder can now deal with the wordorder as well of 32/64 bit data.
1355
* Support Database slave contexts (SqlStore and RedisStore)
1456
* Custom handlers could be passed to Modbus TCP servers
1557
* Asynchronous Server could now be stopped when running on a seperate thread (StopServer)

Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ check: install
3939

4040
test: install
4141
@pip install --quiet --requirement=requirements-tests.txt
42-
@py.test
43-
@coverage report --fail-under=85 -i
42+
@py.test --cov=pymodbus/ --cov-report term-missing
43+
@coverage report --fail-under=90
4444

4545
tox: install
4646
@pip install --quiet tox && tox
@@ -58,8 +58,8 @@ publish: install
5858
$(MAKE) clean
5959

6060
clean:
61-
@rm -Rf *.egg .cache .coverage .tox build dist docs/build htmlcov
62-
@find -depth -type d -name __pycache__ -exec rm -Rf {} \;
63-
@find -type f -name '*.pyc' -delete
61+
@rm -Rf *.egg .eggs *.egg-info *.db .cache .coverage .tox build dist docs/build htmlcov doc/_build test/.Python test/pip-selfcheck.json test/lib/ test/include/ test/bin/
62+
@find . -depth -type d -name __pycache__ -exec rm -Rf {} \;
63+
@find . -type f -name '*.pyc' -delete
6464

6565
.PHONY: default install reset check test tox docs publish clean

README.rst

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

8484
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
@@ -13,9 +13,11 @@ Subpackages
1313

1414
pymodbus.client
1515
pymodbus.datastore
16+
pymodbus.framer
1617
pymodbus.internal
1718
pymodbus.server
1819

20+
1921
Submodules
2022
----------
2123

examples/common/async_twisted_client.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# --------------------------------------------------------------------------- #
1010
# import needed libraries
1111
# --------------------------------------------------------------------------- #
12+
1213
from twisted.internet import reactor
1314

1415
from pymodbus.client.async.tcp import AsyncModbusTCPClient
@@ -21,11 +22,20 @@
2122

2223
#from pymodbus.client.async import ModbusUdpClientProtocol
2324

25+
from twisted.internet import reactor, protocol
26+
from pymodbus.constants import Defaults
27+
28+
# --------------------------------------------------------------------------- #
29+
# choose the requested modbus protocol
30+
# --------------------------------------------------------------------------- #
31+
2432
# --------------------------------------------------------------------------- #
2533
# configure the client logging
2634
# --------------------------------------------------------------------------- #
2735
import logging
28-
logging.basicConfig()
36+
FORMAT = ('%(asctime)-15s %(threadName)-15s'
37+
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
38+
logging.basicConfig(format=FORMAT)
2939
log = logging.getLogger()
3040
log.setLevel(logging.DEBUG)
3141

@@ -40,7 +50,7 @@ def err(*args, **kwargs):
4050

4151
def dassert(deferred, callback):
4252
def _assertor(value):
43-
assert(value)
53+
assert value
4454
deferred.addCallback(lambda r: _assertor(callback(r)))
4555
deferred.addErrback(err)
4656

@@ -55,9 +65,20 @@ def _assertor(value):
5565

5666
UNIT = 0x01
5767

68+
def processResponse(result):
69+
log.debug(result)
70+
5871

5972
def exampleRequests(client):
6073
rr = client.read_coils(1, 1, unit=0x02)
74+
rr.addCallback(processResponse)
75+
rr = client.read_holding_registers(1, 1, unit=0x02)
76+
rr.addCallback(processResponse)
77+
rr = client.read_discrete_inputs(1, 1, unit=0x02)
78+
rr.addCallback(processResponse)
79+
rr = client.read_input_registers(1, 1, unit=0x02)
80+
rr.addCallback(processResponse)
81+
stopAsynchronousTest(client)
6182

6283
# --------------------------------------------------------------------------- #
6384
# example requests
@@ -70,31 +91,38 @@ def exampleRequests(client):
7091
# deferred assert helper(dassert).
7192
# --------------------------------------------------------------------------- #
7293

94+
def stopAsynchronousTest(client):
95+
# ----------------------------------------------------------------------- #
96+
# close the client at some time later
97+
# ----------------------------------------------------------------------- #
98+
reactor.callLater(1, client.transport.loseConnection)
99+
reactor.callLater(2, reactor.stop)
100+
73101

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

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

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

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

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

100128
arguments = {
@@ -103,10 +131,11 @@ def beginAsynchronousTest(client):
103131
'write_address': 1,
104132
'write_registers': [20]*8,
105133
}
106-
rq = client.readwrite_registers(**arguments, unit=UNIT)
134+
rq = client.readwrite_registers(arguments, unit=UNIT)
107135
rr = client.read_input_registers(1, 8, unit=UNIT)
108136
dassert(rq, lambda r: r.registers == [20]*8) # test the expected value
109137
dassert(rr, lambda r: r.registers == [17]*8) # test the expected value
138+
stopAsynchronousTest(client)
110139

111140
# ----------------------------------------------------------------------- #
112141
# close the client at some time later
@@ -140,11 +169,9 @@ def beginAsynchronousTest(client):
140169

141170

142171
if __name__ == "__main__":
143-
144172
protocol, deferred = AsyncModbusTCPClient(schedulers.REACTOR, port=5020)
145173
# protocol, deferred = AsyncModbusUDPClient(schedulers.REACTOR, port=5020)
146174
# callback=beginAsynchronousTest,
147175
# errback=err)
148176
deferred.addCallback(beginAsynchronousTest)
149177
deferred.addErrback(err)
150-

examples/common/asynchronous_processor.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,17 @@
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 = "/tmp/ttyp0"
37-
# --------------------------------------------------------------------------- #
38+
39+
SERIAL_PORT = "/dev/ptyp0"
3840
STATUS_REGS = (1, 2)
3941
STATUS_COILS = (1, 3)
4042
CLIENT_DELAY = 1
@@ -175,7 +177,7 @@ def write(self, response):
175177

176178
def main():
177179
log.debug("Initializing the client")
178-
framer = ModbusFramer(ClientDecoder())
180+
framer = ModbusFramer(ClientDecoder(), client=None)
179181
reader = LoggingLineReader()
180182
factory = ExampleFactory(framer, reader)
181183
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)