Skip to content

Commit 900db55

Browse files
committed
1. #284 Async servers - Option to start reactor outside Start<server>Server function
2. #283 Fix BinaryPayloadDecoder/Builder issues when using with pymodbus server 3. #278 Fix issue with sync/async servers failing to handle requests with transaction id > 255 4. #221 Move timing and transcational logic to framers for sync clients 5. #221 More debug logs for sync clients 6. Misc updates with examples and minor enhancements
1 parent 689708a commit 900db55

32 files changed

+1694
-1107
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ Version 1.5.0
66

77
* Fix tcp servers (sync/async) not processing requests with transaction id > 255
88
* 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)`
913
* Fix Misc examples
1014

1115
Version 1.4.0
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: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
# choose the requested modbus protocol
1717
# --------------------------------------------------------------------------- #
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

2122
# --------------------------------------------------------------------------- #
2223
# configure the client logging
2324
# --------------------------------------------------------------------------- #
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

@@ -34,7 +37,6 @@
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

@@ -47,8 +49,20 @@ def _assertor(value):
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

5367
# --------------------------------------------------------------------------- #
5468
# example requests
@@ -61,7 +75,16 @@ 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)
@@ -99,12 +122,8 @@ def beginAsynchronousTest(client):
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

109128
# --------------------------------------------------------------------------- #
110129
# extra requests
@@ -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__":

examples/common/modbus_payload.py

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,25 @@
1010
from pymodbus.payload import BinaryPayloadBuilder
1111
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
1212
from pymodbus.compat import iteritems
13+
from collections import OrderedDict
1314

1415
# --------------------------------------------------------------------------- #
1516
# configure the client logging
1617
# --------------------------------------------------------------------------- #
18+
1719
import logging
18-
logging.basicConfig()
20+
FORMAT = ('%(asctime)-15s %(threadName)-15s'
21+
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
22+
logging.basicConfig(format=FORMAT)
1923
log = logging.getLogger()
20-
log.setLevel(logging.INFO)
24+
log.setLevel(logging.DEBUG)
2125

2226

2327
def run_binary_payload_ex():
2428
# ----------------------------------------------------------------------- #
2529
# We are going to use a simple client to send our requests
2630
# ----------------------------------------------------------------------- #
27-
client = ModbusClient('127.0.0.1', port=5440)
31+
client = ModbusClient('127.0.0.1', port=5020)
2832
client.connect()
2933

3034
# ----------------------------------------------------------------------- #
@@ -67,19 +71,36 @@ def run_binary_payload_ex():
6771
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #
6872

6973
# ----------------------------------------------------------------------- #
70-
builder = BinaryPayloadBuilder(byteorder=Endian.Little,
71-
wordorder=Endian.Big)
74+
builder = BinaryPayloadBuilder(byteorder=Endian.Big,
75+
wordorder=Endian.Little)
7276
builder.add_string('abcdefgh')
73-
builder.add_32bit_float(22.34)
74-
builder.add_16bit_uint(0x1234)
75-
builder.add_16bit_uint(0x5678)
76-
builder.add_8bit_int(0x12)
7777
builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0])
78-
builder.add_32bit_uint(0x12345678)
78+
builder.add_8bit_int(-0x12)
79+
builder.add_8bit_uint(0x12)
80+
builder.add_16bit_int(-0x5678)
81+
builder.add_16bit_uint(0x1234)
7982
builder.add_32bit_int(-0x1234)
80-
builder.add_64bit_int(0x1234567890ABCDEF)
83+
builder.add_32bit_uint(0x12345678)
84+
builder.add_32bit_float(22.34)
85+
builder.add_32bit_float(-22.34)
86+
builder.add_64bit_int(-0xDEADBEEF)
87+
builder.add_64bit_uint(0x12345678DEADBEEF)
88+
builder.add_64bit_uint(0x12345678DEADBEEF)
89+
builder.add_64bit_float(123.45)
90+
builder.add_64bit_float(-123.45)
91+
payload = builder.to_registers()
92+
print("-" * 60)
93+
print("Writing Registers")
94+
print("-" * 60)
95+
print(payload)
96+
print("\n")
8197
payload = builder.build()
8298
address = 0
99+
# Can write registers
100+
# registers = builder.to_registers()
101+
# client.write_registers(address, registers, unit=1)
102+
103+
# Or can write encoded binary string
83104
client.write_registers(address, payload, skip_encode=True, unit=1)
84105
# ----------------------------------------------------------------------- #
85106
# If you need to decode a collection of registers in a weird layout, the
@@ -95,7 +116,7 @@ def run_binary_payload_ex():
95116
# - an 8 bit int 0x12
96117
# - an 8 bit bitstring [0,1,0,1,1,0,1,0]
97118
# ----------------------------------------------------------------------- #
98-
address = 0x00
119+
address = 0x0
99120
count = len(payload)
100121
result = client.read_holding_registers(address, count, unit=1)
101122
print("-" * 60)
@@ -105,19 +126,26 @@ def run_binary_payload_ex():
105126
print("\n")
106127
decoder = BinaryPayloadDecoder.fromRegisters(result.registers,
107128
byteorder=Endian.Little,
108-
wordorder=Endian.Big)
109-
decoded = {
110-
'string': decoder.decode_string(8),
111-
'float': decoder.decode_32bit_float(),
112-
'16uint': decoder.decode_16bit_uint(),
113-
'ignored': decoder.skip_bytes(2),
114-
'8int': decoder.decode_8bit_int(),
115-
'bits': decoder.decode_bits(),
116-
"32uints": decoder.decode_32bit_uint(),
117-
"32ints": decoder.decode_32bit_int(),
118-
"64ints": decoder.decode_64bit_int(),
119-
}
120-
129+
wordorder=Endian.Little)
130+
131+
decoded = OrderedDict([
132+
('string', decoder.decode_string(8)),
133+
('bits', decoder.decode_bits()),
134+
('8int', decoder.decode_8bit_int()),
135+
('8uint', decoder.decode_8bit_uint()),
136+
('16int', decoder.decode_16bit_int()),
137+
('16uint', decoder.decode_16bit_uint()),
138+
('32int', decoder.decode_32bit_int()),
139+
('32uint', decoder.decode_32bit_uint()),
140+
('32float', decoder.decode_32bit_float()),
141+
('32float2', decoder.decode_32bit_float()),
142+
('64int', decoder.decode_64bit_int()),
143+
('64uint', decoder.decode_64bit_uint()),
144+
('ignore', decoder.skip_bytes(8)),
145+
('64float', decoder.decode_64bit_float()),
146+
('64float2', decoder.decode_64bit_float()),
147+
])
148+
121149
print("-" * 60)
122150
print("Decoded Data")
123151
print("-" * 60)

0 commit comments

Comments
 (0)