Skip to content

Commit 195d79a

Browse files
committed
client.
1 parent 0529c8d commit 195d79a

File tree

7 files changed

+413
-106
lines changed

7 files changed

+413
-106
lines changed

README.rst

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
PyModbus - A Python Modbus Stack
22
================================
3-
43
.. image:: https://github.com/pymodbus-dev/pymodbus/actions/workflows/ci.yml/badge.svg?branch=dev
54
:target: https://github.com/pymodbus-dev/pymodbus/actions/workflows/ci.yml
65
.. image:: https://readthedocs.org/projects/pymodbus/badge/?version=latest
@@ -25,7 +24,6 @@ Source code on `github <https://pymodbus.readthedocs.io/en/latest/source/authors
2524

2625
Pymodbus in a nutshell
2726
----------------------
28-
2927
Pymodbus consist of 5 parts:
3028

3129
- **client**, connect to your favorite device(s)
@@ -36,7 +34,6 @@ Pymodbus consist of 5 parts:
3634

3735
Common features
3836
^^^^^^^^^^^^^^^
39-
4037
* Full :download:`modbus standard protocol <_static/Modbus_Application_Protocol_V1_1b3.pdf>` implementation
4138
* Support for custom function codes
4239
* support serial (rs-485), tcp, tls and udp communication
@@ -51,7 +48,6 @@ Common features
5148

5249
Client Features
5350
^^^^^^^^^^^^^^^
54-
5551
* asynchronous API and synchronous API for applications
5652
* very simple setup and call sequence (just 6 lines of code)
5753
* utilities to convert int/float to/from multiple registers
@@ -62,7 +58,6 @@ Client Features
6258

6359
Server Features
6460
^^^^^^^^^^^^^^^
65-
6661
* asynchronous implementation for high performance
6762
* synchronous API classes for convenience
6863
* simulate real life devices
@@ -76,7 +71,6 @@ Server Features
7671

7772
REPL Features
7873
^^^^^^^^^^^^^
79-
8074
- server/client commandline emulator
8175
- easy test of real device (client)
8276
- easy test of client app (server)
@@ -88,7 +82,6 @@ REPL Features
8882

8983
Simulator Features
9084
^^^^^^^^^^^^^^^^^^
91-
9285
- server simulator with WEB interface
9386
- configure the structure of a real device
9487
- monitor traffic online
@@ -124,7 +117,6 @@ https://readthedocs.org/docs/pymodbus/en/latest/index.html
124117

125118
Install
126119
-------
127-
128120
The library is available on pypi.org and github.com to install with
129121

130122
- :code:`pip` for those who just want to use the library
@@ -147,7 +139,6 @@ You need to have python3 installed, preferable 3.11.
147139

148140
Install with pip
149141
^^^^^^^^^^^^^^^^
150-
151142
You can install using pip by issuing the following
152143
commands in a terminal window::
153144

@@ -162,15 +153,10 @@ This will install pymodbus with the pyserial dependency.
162153
Pymodbus offers a number of extra options:
163154

164155
- **repl**, needed by pymodbus.repl
165-
166156
- **serial**, needed for serial communication
167-
168157
- **simulator**, needed by pymodbus.simulator
169-
170158
- **documentation**, needed to generate documentation
171-
172159
- **development**, needed for development
173-
174160
- **all**, installs all of the above
175161

176162
which can be installed as::
@@ -184,7 +170,6 @@ It is possible to install old releases if needed::
184170

185171
Install with github
186172
^^^^^^^^^^^^^^^^^^^
187-
188173
On github, fork https://github.com/pymodbus-dev/pymodbus.git
189174

190175
Clone the source, and make a virtual environment::

doc/source/client.rst

Lines changed: 199 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,122 @@
11
Client
22
======
3+
Pymodbus offers both a :mod:`synchronous client` and a :mod:`asynchronous client`.
4+
Both clients offer simple calls for each type of request, as well as a unified response, removing
5+
a lot of the complexities in the modbus protocol.
36

4-
Pymodbus offers clients with transport different protocols:
7+
In addition to the "pure" client, pymodbus offers a set of utilities converting to/from registers to/from "normal" python values.
58

6-
- *Serial* (RS-485) typically using a dongle
7-
- *TCP*
8-
- *TLS*
9-
- *UDP*
9+
The client is NOT thread safe, meaning the application must ensure that calls are serialized.
10+
This is only a problem for synchronous applications that use multiple threads or for asynchronous applications
11+
that use multiple :mod:`asyncio.create_task`.
1012

11-
The application can use either a :mod:`synchronous client` or a :mod:`asynchronous client`.
13+
It is allowed to have multiple client objects that e.g. each communicate with a TCP based device.
1214

15+
16+
Client performance
17+
------------------
18+
There are currently a big performance gab between the 2 clients
19+
(try it on your computer `performance test <https://github.com/pymodbus-dev/pymodbus/blob/dev/examples/client_performance.py>`_).
20+
This is due to a rather old implementation of the synchronous client, we are currently working to update the client code.
21+
Our aim is to achieve a similar data rate with both clients and at least double the data rate while keeping the stability.
22+
Table below is a test with 1000 calls each reading 10 registers.
23+
24+
.. list-table::
25+
:header-rows: 1
26+
27+
* - **client**
28+
- **asynchronous**
29+
- **synchronous**
30+
* - total time
31+
- 0,33 sec
32+
- 114,10 sec
33+
* - ms/call
34+
- 0,33 ms
35+
- 114,10 ms
36+
* - ms/register
37+
- 0,03 ms
38+
- 11,41 ms
39+
* - calls/sec
40+
- 3.030
41+
- 8
42+
* - registers/sec
43+
- 30.300
44+
- 87
45+
46+
47+
Client protocols/framers
48+
------------------------
49+
Pymodbus offers clients with transport different protocols and different framers
50+
51+
.. list-table::
52+
:header-rows: 1
53+
54+
* - **protocol**
55+
- ASCII
56+
- RTU
57+
- RTU_OVER_TCP
58+
- Socket
59+
- TLS
60+
* - Serial (RS-485)
61+
- Yes
62+
- Yes
63+
- No
64+
- No
65+
- No
66+
* - TCP
67+
- Yes
68+
- No
69+
- Yes
70+
- Yes
71+
- No
72+
* - TLS
73+
- No
74+
- No
75+
- No
76+
- No
77+
- Yes
78+
* - UDP
79+
- Yes
80+
- No
81+
- Yes
82+
- Yes
83+
- No
84+
85+
86+
Serial (RS-485)
87+
^^^^^^^^^^^^^^^
88+
Pymodbus do not connect to the device (server) but connects to a comm port or usb port on the local computer.
89+
90+
RS-485 is a half duplex protocol, meaning the servers do nothing until the client sends a request then the server
91+
being addressed responds. The client controls the traffic and as a consequence one RS-485 line can only have 1 client
92+
but upto 254 servers (physical devices).
93+
94+
RS-485 is a simple 2 wire cabling with a pullup resistor. It is important to note that many USB converters do not have a
95+
builtin resistor, this must be added manually. When experiencing many faulty packets and retries this is often the problem.
96+
97+
98+
TCP
99+
^^^
100+
Pymodbus connects directly to the device using a standard socket and have a one-to-one connection with the device.
101+
In case of multiple TCP devices the application must instantiate multiple client objects one for each connection.
102+
103+
.. tip:: a TCP device often represent multiple physical devices (e.g Ethernet-RS485 converter), each of these devices
104+
can be addressed normally
105+
106+
107+
TLS
108+
^^^
109+
A variant of **TCP** that uses encryption and certificates. **TLS** is mostly used when the devices are connected to the internet.
110+
111+
112+
UDP
113+
^^^
114+
A broadcast variant of **TCP**. **UDP** allows addressing of many devices with a single request, however there are no control
115+
that a device have received the packet.
116+
117+
118+
Client usage
119+
------------
13120
Using pymodbus client to set/get information from a device (server)
14121
is done in a few simple steps, like the following synchronous example::
15122

@@ -18,7 +125,7 @@ is done in a few simple steps, like the following synchronous example::
18125
client = ModbusTcpClient('MyDevice.lan') # Create client object
19126
client.connect() # connect to device, reconnect automatically
20127
client.write_coil(1, True, slave=1) # set information in device
21-
result = client.read_coils(1, 1, slave=1) # get information from device
128+
result = client.read_coils(2, 3, slave=1) # get information from device
22129
print(result.bits[0]) # use information
23130
client.close() # Disconnect device
24131

@@ -30,10 +137,22 @@ and a asynchronous example::
30137
client = ModbusAsyncTcpClient('MyDevice.lan') # Create client object
31138
await client.connect() # connect to device, reconnect automatically
32139
await client.write_coil(1, True, slave=1) # set information in device
33-
result = await client.read_coils(1, 1, slave=1) # get information from device
140+
result = await client.read_coils(2, 3, slave=1) # get information from device
34141
print(result.bits[0]) # use information
35142
client.close() # Disconnect device
36143

144+
The line :mod:`client = ModbusAsyncTcpClient('MyDevice.lan')` only creates the object it does not activate
145+
anything.
146+
147+
The line :mod:`await client.connect()` connects to the device (or comm port), if this cannot connect successfully within
148+
the timeout it throws an exception. If connected successfully reconnecting later is handled automatically
149+
150+
The line :mod:`await client.write_coil(1, True, slave=1)` is an example of a write request, set address 1 to True on device 1 (slave).
151+
152+
The line :mod:`result = await client.read_coils(2, 1, slave=1)` is an example of a read request, get the value of address 2, 3 and 4 (count = 3) from device 1 (slave).
153+
154+
The last line :mod:`client.close()` closes the connection and render the object inactive.
155+
37156
Large parts of the implementation are shared between the different classes,
38157
to ensure high stability and efficient maintenance.
39158

@@ -46,14 +165,74 @@ The asynchronous client only runs in the thread where the asyncio loop is create
46165
it does not provide mechanisms to prevent (semi)parallel calls,
47166
that must be prevented at application level.
48167

49-
Client classes
50-
--------------
51168

52-
.. autoclass:: pymodbus.client.ModbusBaseClient
53-
:members:
54-
:member-order: bysource
55-
:show-inheritance:
169+
Client device addressing
170+
------------------------
171+
172+
With **TCP**, **TLS** and **UDP**, the tcp/ip address of the physical device is defined when creating the object.
173+
The logical devices represented by the device is addressed with the :mod:`slave=` parameter.
174+
175+
With **Serial**, the comm port is defined when creating the object.
176+
The physical devices are addressed with the :mod:`slave=` parameter.
177+
178+
:mod:`slave=0` is used as broadcast in order to address all devices.
179+
However experience shows that modern devices do not allow broadcast, mostly because it is
180+
inheriently dangerous. With :mod:`slave=0` the application can get upto 254 responses on a single request!
181+
182+
The simple request calls (mixin) do NOT support broadcast, if an application wants to use broadcast
183+
it must call :mod:`client.execute` and deal with the responses.
184+
185+
186+
187+
Client response handling
188+
------------------------
189+
190+
All simple request calls (mixin) return a unified result independent whether it´s a read, write or diagnostic call.
191+
192+
The application should evaluate the result generically::
193+
194+
try:
195+
rr = await client.read_coils(1, 1, slave=1)
196+
except ModbusException as exc:
197+
_logger.error(f"ERROR: exception in pymodbus {exc}")
198+
raise exc
199+
if rr.isError():
200+
_logger.error("ERROR: pymodbus returned an error!")
201+
raise ModbusException(txt)
202+
203+
:mod:`except ModbusException as exc:` happens generally when pymodbus experiences an internal error.
204+
There are a few situation where a unexpected response from a device can cause an exception.
205+
206+
:mod:`rr.isError()` is set whenever the device reports a problem.
207+
208+
And in case of read retrieve the data depending on type of request
209+
210+
- :mod:`rr.bits` is set for coils / input_register requests
211+
- :mod:`rr.registers` is set for other requests
212+
213+
214+
Client interface classes
215+
------------------------
216+
217+
There are a client class for each type of communication and for asynchronous/synchronous
218+
219+
.. list-table::
220+
221+
* - **Serial**
222+
- :mod:`AsyncModbusSerialClient`
223+
- :mod:`ModbusSerialClient`
224+
* - **TCP**
225+
- :mod:`AsyncModbusTcpClient`
226+
- :mod:`ModbusTcpClient`
227+
* - **TLS**
228+
- :mod:`AsyncModbusTlsClient`
229+
- :mod:`ModbusTlsClient`
230+
* - **UDP**
231+
- :mod:`AsyncModbusUdpClient`
232+
- :mod:`ModbusUdpClient`
56233

234+
Client serial
235+
^^^^^^^^^^^^^
57236
.. autoclass:: pymodbus.client.AsyncModbusSerialClient
58237
:members:
59238
:member-order: bysource
@@ -64,6 +243,8 @@ Client classes
64243
:member-order: bysource
65244
:show-inheritance:
66245

246+
Client TCP
247+
^^^^^^^^^^
67248
.. autoclass:: pymodbus.client.AsyncModbusTcpClient
68249
:members:
69250
:member-order: bysource
@@ -74,6 +255,8 @@ Client classes
74255
:member-order: bysource
75256
:show-inheritance:
76257

258+
Client TLS
259+
^^^^^^^^^^
77260
.. autoclass:: pymodbus.client.AsyncModbusTlsClient
78261
:members:
79262
:member-order: bysource
@@ -84,6 +267,8 @@ Client classes
84267
:member-order: bysource
85268
:show-inheritance:
86269

270+
Client UDP
271+
^^^^^^^^^^
87272
.. autoclass:: pymodbus.client.AsyncModbusUdpClient
88273
:members:
89274
:member-order: bysource

pymodbus/client/base.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ class ModbusBaseClient(ModbusClientMixin, ModbusProtocol):
3535
:param no_resend_on_retry: (optional) Do not resend request when retrying due to missing response.
3636
:param kwargs: (optional) Experimental parameters.
3737
38-
.. tip::
39-
Common parameters and all external methods for all clients are documented here,
40-
and not repeated with each client.
41-
4238
.. tip::
4339
**reconnect_delay** doubles automatically with each unsuccessful connect, from
4440
**reconnect_delay** to **reconnect_delay_max**.
@@ -132,9 +128,9 @@ def __init__( # pylint: disable=too-many-arguments
132128
# Client external interface
133129
# ----------------------------------------------------------------------- #
134130
@property
135-
def connected(self):
136-
"""Connect internal."""
137-
return True
131+
def connected(self) -> bool:
132+
"""Return state of connection."""
133+
return self.is_active()
138134

139135
def register(self, custom_response_class: ModbusResponse) -> None:
140136
"""Register a custom response class with the decoder (call **sync**).
@@ -147,7 +143,7 @@ def register(self, custom_response_class: ModbusResponse) -> None:
147143
"""
148144
self.framer.decoder.register(custom_response_class)
149145

150-
def close(self, reconnect=False) -> None:
146+
def close(self, reconnect: bool = False) -> None:
151147
"""Close connection."""
152148
if reconnect:
153149
self.connection_lost(asyncio.TimeoutError("Server not responding"))

0 commit comments

Comments
 (0)