Skip to content

Commit 88ff6b0

Browse files
committed
Merge branch 'dev' into 3.0.0
2 parents 9967ff0 + d2d1974 commit 88ff6b0

File tree

10 files changed

+124
-88
lines changed

10 files changed

+124
-88
lines changed

CHANGELOG.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
version 3.0.0dev2
2+
----------------------------------------------------------
3+
14
version 3.0.0dev1
25
----------------------------------------------------------
36
* Support python3.10
@@ -13,6 +16,14 @@ version 3.0.0dev0
1316
* Support python3.7 and above
1417
* Support creating asyncio clients from with in coroutines.
1518

19+
version 2.5.3
20+
----------------------------------------------------------
21+
* Fix retries on tcp client failing randomly.
22+
* Fix Asyncio client timeout arg not being used.
23+
* Treat exception codes as valid responses
24+
* Fix examples (modbus_payload)
25+
* Add missing identity argument to async ModbusSerialServer
26+
1627
version 2.5.2
1728
----------------------------------------------------------
1829
* Add kwarg `reset_socket` to control closing of the socket on read failures (set to `True` by default).

examples/common/modbus_payload.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,31 @@ def run_binary_payload_ex():
3535
# ----------------------------------------------------------------------- #
3636
client = ModbusClient('127.0.0.1', port=5020)
3737
client.connect()
38-
38+
3939
# ----------------------------------------------------------------------- #
4040
# If you need to build a complex message to send, you can use the payload
4141
# builder to simplify the packing logic.
4242
#
4343
# Here we demonstrate packing a random payload layout, unpacked it looks
4444
# like the following:
4545
#
46-
# - a 8 byte string 'abcdefgh'
47-
# - a 32 bit float 22.34
48-
# - a 16 bit unsigned int 0x1234
49-
# - another 16 bit unsigned int 0x5678
50-
# - an 8 bit int 0x12
46+
# - an 8 byte string "abcdefgh"
5147
# - an 8 bit bitstring [0,1,0,1,1,0,1,0]
52-
# - an 32 bit uint 0x12345678
53-
# - an 32 bit signed int -0x1234
54-
# - an 64 bit signed int 0x12345678
48+
# - an 8 bit int -0x12
49+
# - an 8 bit unsigned int 0x12
50+
# - a 16 bit int -0x5678
51+
# - a 16 bit unsigned int 0x1234
52+
# - a 32 bit int -0x1234
53+
# - a 32 bit unsigned int 0x12345678
54+
# - a 16 bit float 12.34
55+
# - a 16 bit float -12.34
56+
# - a 32 bit float 22.34
57+
# - a 32 bit float -22.34
58+
# - a 64 bit int -0xDEADBEEF
59+
# - a 64 bit unsigned int 0x12345678DEADBEEF
60+
# - another 64 bit unsigned int 0x12345678DEADBEEF
61+
# - a 64 bit float 123.45
62+
# - a 64 bit float -123.45
5563

5664
# The packing can also be applied to the word (wordorder) and bytes in each
5765
# word (byteorder)
@@ -123,12 +131,23 @@ def run_binary_payload_ex():
123131
# Here we demonstrate decoding a random register layout, unpacked it looks
124132
# like the following:
125133
#
126-
# - a 8 byte string 'abcdefgh'
127-
# - a 32 bit float 22.34
128-
# - a 16 bit unsigned int 0x1234
129-
# - another 16 bit unsigned int which we will ignore
130-
# - an 8 bit int 0x12
134+
# - an 8 byte string "abcdefgh"
131135
# - an 8 bit bitstring [0,1,0,1,1,0,1,0]
136+
# - an 8 bit int -0x12
137+
# - an 8 bit unsigned int 0x12
138+
# - a 16 bit int -0x5678
139+
# - a 16 bit unsigned int 0x1234
140+
# - a 32 bit int -0x1234
141+
# - a 32 bit unsigned int 0x12345678
142+
# - a 16 bit float 12.34
143+
# - a 16 bit float -12.34
144+
# - a 32 bit float 22.34
145+
# - a 32 bit float -22.34
146+
# - a 64 bit int -0xDEADBEEF
147+
# - a 64 bit unsigned int 0x12345678DEADBEEF
148+
# - another 64 bit unsigned int which we will ignore
149+
# - a 64 bit float 123.45
150+
# - a 64 bit float -123.45
132151
# ----------------------------------------------------------------------- #
133152
address = 0x0
134153
count = len(payload)
@@ -174,7 +193,7 @@ def run_binary_payload_ex():
174193
print("-" * 60)
175194
for name, value in iteritems(decoded):
176195
print("%s\t" % name, hex(value) if isinstance(value, int) else value)
177-
196+
178197
# ----------------------------------------------------------------------- #
179198
# close the client
180199
# ----------------------------------------------------------------------- #

pymodbus/client/asynchronous/async_io/__init__.py

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ class ReconnectingAsyncioModbusTcpClient(object):
240240
#: Maximum delay in milli seconds before reconnect is attempted.
241241
DELAY_MAX_MS = 1000 * 60 * 5
242242

243-
def __init__(self, protocol_class=None, loop=None):
243+
def __init__(self, protocol_class=None, loop=None, **kwargs):
244244
"""
245245
Initialize ReconnectingAsyncioModbusTcpClient
246246
:param protocol_class: Protocol used to talk to modbus device.
@@ -257,6 +257,7 @@ def __init__(self, protocol_class=None, loop=None):
257257
self.connected = False
258258
#: Reconnect delay in milli seconds.
259259
self.delay_ms = self.DELAY_MIN_MS
260+
self._proto_args = kwargs
260261

261262
def reset_delay(self):
262263
"""
@@ -296,7 +297,7 @@ def _create_protocol(self):
296297
"""
297298
Factory function to create initialized protocol instance.
298299
"""
299-
protocol = self.protocol_class()
300+
protocol = self.protocol_class(**self._proto_args)
300301
protocol.factory = self
301302
return protocol
302303

@@ -353,7 +354,7 @@ async def _reconnect(self):
353354
class AsyncioModbusTcpClient(object):
354355
"""Client to connect to modbus device over TCP/IP."""
355356

356-
def __init__(self, host=None, port=502, protocol_class=None, loop=None):
357+
def __init__(self, host=None, port=502, protocol_class=None, loop=None, **kwargs):
357358
"""
358359
Initializes Asyncio Modbus Tcp Client
359360
:param host: Host IP address
@@ -372,6 +373,7 @@ def __init__(self, host=None, port=502, protocol_class=None, loop=None):
372373
self.port = port
373374

374375
self.connected = False
376+
self._proto_args = kwargs
375377

376378
def stop(self):
377379
"""
@@ -387,7 +389,7 @@ def _create_protocol(self):
387389
"""
388390
Factory function to create initialized protocol instance.
389391
"""
390-
protocol = self.protocol_class()
392+
protocol = self.protocol_class(**self._proto_args)
391393
protocol.factory = self
392394
return protocol
393395

@@ -441,27 +443,34 @@ class ReconnectingAsyncioModbusTlsClient(ReconnectingAsyncioModbusTcpClient):
441443
"""
442444
Client to connect to modbus device repeatedly over TLS."
443445
"""
444-
def __init__(self, protocol_class=None, loop=None, framer=None):
446+
def __init__(self, protocol_class=None, loop=None, framer=None, **kwargs):
445447
"""
446448
Initialize ReconnectingAsyncioModbusTcpClient
447449
:param protocol_class: Protocol used to talk to modbus device.
448450
:param loop: Event loop to use
449451
"""
450452
self.framer = framer
451-
ReconnectingAsyncioModbusTcpClient.__init__(self, protocol_class, loop)
453+
ReconnectingAsyncioModbusTcpClient.__init__(self, protocol_class, loop, **kwargs)
452454

453-
async def start(self, host='localhost', port=802, sslctx=None,
454-
certfile=None, keyfile=None, password=None, **kwargs):
455+
async def start(self, host, port=802, sslctx=None, server_hostname=None):
455456
"""
456457
Initiates connection to start client
457-
:param host: The host to connect to (default localhost)
458-
:param port: Port to connect
459-
:param sslctx:The SSLContext to use for TLS (default None and auto create)
460-
:param certfile: The optional client's cert file path for TLS server request
461-
:param keyfile: The optional client's key file path for TLS server request
462-
:param password: The password for for decrypting client's private key file
458+
:param host:
459+
:param port:
460+
:param sslctx:
461+
:param server_hostname:
462+
:return:
463463
"""
464-
self.sslctx = sslctx_provider(sslctx, certfile, keyfile, password)
464+
self.sslctx = sslctx
465+
if self.sslctx is None:
466+
self.sslctx = ssl.create_default_context()
467+
# According to MODBUS/TCP Security Protocol Specification, it is
468+
# TLSv2 at least
469+
self.sslctx.options |= ssl.OP_NO_TLSv1_1
470+
self.sslctx.options |= ssl.OP_NO_TLSv1
471+
self.sslctx.options |= ssl.OP_NO_SSLv3
472+
self.sslctx.options |= ssl.OP_NO_SSLv2
473+
self.server_hostname = server_hostname
465474
return await ReconnectingAsyncioModbusTcpClient.start(self, host, port)
466475

467476
async def _connect(self):
@@ -484,7 +493,7 @@ def _create_protocol(self):
484493
"""
485494
Factory function to create initialized protocol instance.
486495
"""
487-
protocol = self.protocol_class(framer=self.framer)
496+
protocol = self.protocol_class(framer=self.framer, **self._proto_args)
488497
protocol.transaction = FifoTransactionManager(self)
489498
protocol.factory = self
490499
return protocol
@@ -500,7 +509,7 @@ class ReconnectingAsyncioModbusUdpClient(object):
500509
#: Maximum delay in milli seconds before reconnect is attempted.
501510
DELAY_MAX_MS = 1000 * 60 * 5
502511

503-
def __init__(self, protocol_class=None, loop=None):
512+
def __init__(self, protocol_class=None, loop=None, **kwargs):
504513
"""
505514
Initializes ReconnectingAsyncioModbusUdpClient
506515
:param protocol_class: Protocol used to talk to modbus device.
@@ -517,6 +526,7 @@ def __init__(self, protocol_class=None, loop=None):
517526
self.port = 0
518527

519528
self.connected = False
529+
self._proto_args = kwargs
520530
self.reset_delay()
521531

522532
def reset_delay(self):
@@ -565,7 +575,7 @@ def _create_protocol(self, host=None, port=0):
565575
"""
566576
Factory function to create initialized protocol instance.
567577
"""
568-
protocol = self.protocol_class()
578+
protocol = self.protocol_class(**self._proto_args)
569579
protocol.host = host
570580
protocol.port = port
571581
protocol.factory = self
@@ -629,7 +639,7 @@ class AsyncioModbusUdpClient(object):
629639
Client to connect to modbus device over UDP.
630640
"""
631641

632-
def __init__(self, host=None, port=502, protocol_class=None, loop=None):
642+
def __init__(self, host=None, port=502, protocol_class=None, loop=None, **kwargs):
633643
"""
634644
Initializes Asyncio Modbus UDP Client
635645
:param host: Host IP address
@@ -648,6 +658,7 @@ def __init__(self, host=None, port=502, protocol_class=None, loop=None):
648658
self.port = port
649659

650660
self.connected = False
661+
self._proto_args = kwargs
651662

652663
def stop(self):
653664
"""
@@ -666,7 +677,7 @@ def _create_protocol(self, host=None, port=0):
666677
"""
667678
Factory function to create initialized protocol instance.
668679
"""
669-
protocol = self.protocol_class()
680+
protocol = self.protocol_class(**self._proto_args)
670681
protocol.host = host
671682
protocol.port = port
672683
protocol.factory = self
@@ -832,31 +843,30 @@ async def init_tcp_client(proto_cls, loop, host, port, **kwargs):
832843
:return:
833844
"""
834845
client = ReconnectingAsyncioModbusTcpClient(protocol_class=proto_cls,
835-
loop=loop)
846+
loop=loop, **kwargs)
836847
await client.start(host, port)
837848
return client
838849

839850

840-
async def init_tls_client(proto_cls, loop, host, port, sslctx=None,
841-
certfile=None, keyfile=None, password=None,
842-
framer=None, **kwargs):
851+
@asyncio.coroutine
852+
def init_tls_client(proto_cls, loop, host, port, sslctx=None,
853+
server_hostname=None, framer=None, **kwargs):
843854
"""
844855
Helper function to initialize tcp client
845856
:param proto_cls:
846857
:param loop:
847858
:param host:
848859
:param port:
849860
:param sslctx:
850-
:param certfile: The optional client's cert file path for TLS server request
851-
:param keyfile: The optional client's key file path for TLS server request
852-
:param password: The password for for decrypting client's private key file
861+
:param server_hostname:
853862
:param framer:
854863
:param kwargs:
855864
:return:
856865
"""
857866
client = ReconnectingAsyncioModbusTlsClient(protocol_class=proto_cls,
858-
loop=loop, framer=framer)
859-
await client.start(host, port, sslctx, certfile, keyfile, password)
867+
loop=loop, framer=framer,
868+
**kwargs)
869+
await client.start(host, port, sslctx, server_hostname)
860870
return client
861871

862872

@@ -871,6 +881,6 @@ async def init_udp_client(proto_cls, loop, host, port, **kwargs):
871881
:return:
872882
"""
873883
client = ReconnectingAsyncioModbusUdpClient(protocol_class=proto_cls,
874-
loop=loop)
884+
loop=loop, **kwargs)
875885
await client.start(host, port)
876886
return client

pymodbus/client/asynchronous/factory/tcp.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,7 @@ def io_loop_factory(host="127.0.0.1", port=Defaults.Port, framer=None,
7777
return protocol, future
7878

7979

80-
def async_io_factory(host="127.0.0.1", port=Defaults.Port, framer=None,
81-
source_address=None, timeout=None, **kwargs):
80+
def async_io_factory(host="127.0.0.1", port=Defaults.Port, **kwargs):
8281
"""
8382
Factory to create asyncio based asynchronous tcp clients
8483
:param host: Host IP address
@@ -93,28 +92,27 @@ def async_io_factory(host="127.0.0.1", port=Defaults.Port, framer=None,
9392
from pymodbus.client.asynchronous.async_io import init_tcp_client
9493

9594
try:
96-
loop = kwargs.get("loop") or asyncio.get_running_loop()
95+
loop = kwargs.pop("loop") or asyncio.get_running_loop()
9796
except RuntimeError:
9897
loop = asyncio.new_event_loop()
9998

100-
proto_cls = kwargs.get("proto_cls")
99+
proto_cls = kwargs.pop("proto_cls", None)
101100

102101
if not loop.is_running():
103102
asyncio.set_event_loop(loop)
104-
cor = init_tcp_client(proto_cls, loop, host, port)
103+
cor = init_tcp_client(proto_cls, loop, host, port, **kwargs)
105104
client = loop.run_until_complete(asyncio.gather(cor))[0]
105+
106106
elif loop is asyncio.get_event_loop():
107-
cor = init_tcp_client(proto_cls, loop, host, port)
107+
cor = init_tcp_client(proto_cls, loop, host, port, **kwargs)
108108
client = asyncio.create_task(cor)
109109
# future = asyncio.run_coroutine_threadsafe(cor, loop=loop)
110110
# client = future.result()
111111
else:
112-
if loop is asyncio.get_event_loop():
113-
return init_tcp_client(proto_cls, loop, host, port)
114-
else:
115-
cor = init_tcp_client(proto_cls, loop, host, port)
116-
future = asyncio.run_coroutine_threadsafe(cor, loop=loop)
117-
client = future.result()
112+
cor = init_tcp_client(proto_cls, loop, host, port, **kwargs)
113+
future = asyncio.run_coroutine_threadsafe(cor, loop=loop)
114+
client = future.result()
115+
118116
return loop, client
119117

120118

0 commit comments

Comments
 (0)