1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
|
Client
======
Pymodbus offers both a :mod:`synchronous client` and a :mod:`asynchronous client`.
Both clients offer simple calls for each type of request, as well as a unified response, removing
a lot of the complexities in the modbus protocol.
In addition to the "pure" client, pymodbus offers a set of utilities converting to/from registers to/from "normal" python values.
The client is NOT thread safe, meaning the application must ensure that calls are serialized.
This is only a problem for synchronous applications that use multiple threads or for asynchronous applications
that use multiple :mod:`asyncio.create_task`.
It is allowed to have multiple client objects that e.g. each communicate with a TCP based device.
Client performance
------------------
There are currently a big performance gap between the 2 clients
(try it on your computer :github:`examples/client_performance.py`).
This is due to a rather old implementation of the synchronous client, we are currently working to update the client code.
Our aim is to achieve a similar data rate with both clients and at least double the data rate while keeping the stability.
Table below is a test with 1000 calls each reading 10 registers.
.. list-table::
:header-rows: 1
* - **client**
- **asynchronous**
- **synchronous**
* - total time
- 0,33 sec
- 114,10 sec
* - ms/call
- 0,33 ms
- 114,10 ms
* - ms/register
- 0,03 ms
- 11,41 ms
* - calls/sec
- 3.030
- 8
* - registers/sec
- 30.300
- 87
Client protocols/framers
------------------------
Pymodbus offers clients with transport different protocols and different framers
.. list-table::
:header-rows: 1
* - **protocol**
- ASCII
- RTU
- RTU_OVER_TCP
- SOCKET
- TLS
* - SERIAL (RS-485)
- Yes
- Yes
- No
- No
- No
* - TCP
- Yes
- No
- Yes
- Yes
- No
* - TLS
- No
- No
- No
- No
- Yes
* - UDP
- Yes
- No
- Yes
- Yes
- No
Serial (RS-485)
^^^^^^^^^^^^^^^
Pymodbus do not connect to the device (server) but connects to a comm port or usb port on the local computer.
RS-485 is a half duplex protocol, meaning the servers do nothing until the client sends a request then the server
being addressed responds. The client controls the traffic and as a consequence one RS-485 line can only have 1 client
but upto 254 servers (physical devices).
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
builtin resistor, this must be added manually. When experiencing many faulty packets and retries this is often the problem.
TCP
^^^
Pymodbus connects directly to the device using a standard socket and have a one-to-one connection with the device.
In case of multiple TCP devices the application must instantiate multiple client objects one for each connection.
.. tip:: a TCP device often represent multiple physical devices (e.g Ethernet-RS485 converter), each of these devices
can be addressed normally
TLS
^^^
A variant of **TCP** that uses encryption and certificates. **TLS** is mostly used when the devices are connected to the internet.
UDP
^^^
A broadcast variant of **TCP**. **UDP** allows addressing of many devices with a single request, however there are no control
that a device have received the packet.
Client usage
------------
Using pymodbus client to set/get information from a device (server)
is done in a few simple steps.
Synchronous example
^^^^^^^^^^^^^^^^^^^
::
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('MyDevice.lan') # Create client object
client.connect() # connect to device
client.write_coil(1, True, slave=1) # set information in device
result = client.read_coils(2, 3, slave=1) # get information from device
print(result.bits[0]) # use information
client.close() # Disconnect device
The line :mod:`client.connect()` connects to the device (or comm port). If this cannot connect successfully within
the timeout it throws an exception. After this initial connection, further
calls to the same client (here, :mod:`client.write_coil(...)` and
:mod:`client.read_coils(...)` ) will check whether the client is still
connected, and automatically reconnect if not.
Asynchronous example
^^^^^^^^^^^^^^^^^^^^
::
from pymodbus.client import AsyncModbusTcpClient
client = AsyncModbusTcpClient('MyDevice.lan') # Create client object
await client.connect() # connect to device, reconnect automatically
await client.write_coil(1, True, slave=1) # set information in device
result = await client.read_coils(2, 3, slave=1) # get information from device
print(result.bits[0]) # use information
client.close() # Disconnect device
The line :mod:`client = AsyncModbusTcpClient('MyDevice.lan')` only creates the object; it does not activate
anything.
The line :mod:`await client.connect()` connects to the device (or comm port), if this cannot connect successfully within
the timeout it throws an exception. If connected successfully reconnecting later is handled automatically
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).
The line :mod:`result = await client.read_coils(2, 3, slave=1)` is an example of a read request, get the value of address 2, 3 and 4 (count = 3) from device 1 (slave).
The last line :mod:`client.close()` closes the connection and render the object inactive.
Retry logic for async clients
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If no response is received to a request (call), it is retried (parameter retries) times, if not successful
an exception response is returned, BUT the connection is not touched.
If 3 consequitve requests (calls) do not receive a response, the connection is terminated.
Development notes
^^^^^^^^^^^^^^^^^
Large parts of the implementation are shared between the different classes,
to ensure high stability and efficient maintenance.
The synchronous clients are not thread safe nor is a single client intended
to be used from multiple threads. Due to the nature of the modbus protocol,
it makes little sense to have client calls split over different threads,
however the application can do it with proper locking implemented.
The asynchronous client only runs in the thread where the asyncio loop is created,
it does not provide mechanisms to prevent (semi)parallel calls,
that must be prevented at application level.
Client device addressing
------------------------
With **TCP**, **TLS** and **UDP**, the tcp/ip address of the physical device is defined when creating the object.
Logical devices represented by the device is addressed with the :mod:`slave=` parameter.
With **Serial**, the comm port is defined when creating the object.
The physical devices are addressed with the :mod:`slave=` parameter.
:mod:`slave=0` is defined as broadcast in the modbus standard, but pymodbus treats it as a normal device.
please note :mod:`slave=0` can only be used to address devices that truly have id=0 ! Using :mod:`slave=0` to
address a single device with id not 0 is against the protocol.
If an application is expecting multiple responses to a broadcast request, it must call :mod:`client.execute` and deal with the responses.
If no response is expected to a request, the :mod:`no_response_expected=True` argument can be used
in the normal API calls, this will cause the call to return immediately with :mod:`ExceptionResponse(0xff)`.
Client response handling
------------------------
All simple request calls (mixin) return a unified result independent whether it´s a read, write or diagnostic call.
The application should evaluate the result generically::
try:
rr = await client.read_coils(1, 1, slave=1)
except ModbusException as exc:
_logger.error(f"ERROR: exception in pymodbus {exc}")
raise exc
if rr.isError():
_logger.error("ERROR: pymodbus returned an error!")
raise ModbusException(txt)
:mod:`except ModbusException as exc:` happens generally when pymodbus experiences an internal error.
There are a few situation where an unexpected response from a device can cause an exception.
:mod:`rr.isError()` is set whenever the device reports a problem.
And in case of read retrieve the data depending on type of request
- :mod:`rr.bits` is set for coils / input_register requests
- :mod:`rr.registers` is set for other requests
Remark if using :mod:`no_response_expected=True` rr will always be None.
Client interface classes
------------------------
There are a client class for each type of communication and for asynchronous/synchronous
.. list-table::
* - **Serial**
- :mod:`AsyncModbusSerialClient`
- :mod:`ModbusSerialClient`
* - **TCP**
- :mod:`AsyncModbusTcpClient`
- :mod:`ModbusTcpClient`
* - **TLS**
- :mod:`AsyncModbusTlsClient`
- :mod:`ModbusTlsClient`
* - **UDP**
- :mod:`AsyncModbusUdpClient`
- :mod:`ModbusUdpClient`
Client common
^^^^^^^^^^^^^
Some methods are common to all clients:
.. autoclass:: pymodbus.client.base.ModbusBaseClient
:members:
:member-order: bysource
:show-inheritance:
.. autoclass:: pymodbus.client.base.ModbusBaseSyncClient
:members:
:member-order: bysource
:show-inheritance:
Client serial
^^^^^^^^^^^^^
.. autoclass:: pymodbus.client.AsyncModbusSerialClient
:members:
:member-order: bysource
:show-inheritance:
.. autoclass:: pymodbus.client.ModbusSerialClient
:members:
:member-order: bysource
:show-inheritance:
Client TCP
^^^^^^^^^^
.. autoclass:: pymodbus.client.AsyncModbusTcpClient
:members:
:member-order: bysource
:show-inheritance:
.. autoclass:: pymodbus.client.ModbusTcpClient
:members:
:member-order: bysource
:show-inheritance:
Client TLS
^^^^^^^^^^
.. autoclass:: pymodbus.client.AsyncModbusTlsClient
:members:
:member-order: bysource
:show-inheritance:
.. autoclass:: pymodbus.client.ModbusTlsClient
:members:
:member-order: bysource
:show-inheritance:
Client UDP
^^^^^^^^^^
.. autoclass:: pymodbus.client.AsyncModbusUdpClient
:members:
:member-order: bysource
:show-inheritance:
.. autoclass:: pymodbus.client.ModbusUdpClient
:members:
:member-order: bysource
:show-inheritance:
Modbus calls
------------
Pymodbus makes all standard modbus requests/responses available as simple calls.
Using Modbus<transport>Client.register() custom messages can be added to pymodbus,
and handled automatically.
.. autoclass:: pymodbus.client.mixin.ModbusClientMixin
:members:
:member-order: bysource
:show-inheritance:
|