I have to control a modbus device with a serial interface. I've got not experience with modbus. But my short research revealed several modbus libraries
What are the advantages/disadvantages, are there even better alternatives?
A Modbus library for Linux, Mac OS X, FreeBSD, QNX and Win32 A free software library to send/receive data according to the Modbus protocol. This library is written in C and supports RTU (serial) and TCP (Ethernet) communications.
Modbus-RTU (Remote Terminal Unit) means that the Modbus protocol is used on top of a serial line with an RS-232, RS-485 or similar physical interface. Numerous automation systems have Modbus-RTU interfaces for communication.
MinimalModbus is an easy-to-use Python module for talking to instruments (slaves) from a computer (master) using the Modbus protocol, and is intended to be running on the master.
About the same time I faced the same problem - which library to choose for python modbus master implementation but in my case for serial communication (modbus RTU) so my observations are only valid for modbus RTU.
In my examination I didn't pay too much attention to documentation but examples for serial RTU master were easiest to find for modbus-tk however still in source not on a wiki etc.
distinctive feature: relies on serial stream (post by the author) and serial timeout must be dynamically set otherwise performance will be low (serial timeout must be adjusted for the longest possible response)
distinctive feature: probes serial buffer for data, assembles and returns response quickly.
For over 6 months I was using pymodbus due to best performance / CPU load ratio but unreliable responses became a serious issue at higher request rates and eventually I moved to faster embedded system and added support for modbus-tk which works best for me.
My goal was to achieve minimum response time.
code:
import time import traceback import serial import modbus_tk.defines as tkCst import modbus_tk.modbus_rtu as tkRtu import minimalmodbus as mmRtu from pymodbus.client.sync import ModbusSerialClient as pyRtu slavesArr = [2] iterSp = 100 regsSp = 10 portNbr = 21 portName = 'com22' baudrate = 153600 timeoutSp=0.018 + regsSp*0 print "timeout: %s [s]" % timeoutSp mmc=mmRtu.Instrument(portName, 2) # port name, slave address mmc.serial.baudrate=baudrate mmc.serial.timeout=timeoutSp tb = None errCnt = 0 startTs = time.time() for i in range(iterSp): for slaveId in slavesArr: mmc.address = slaveId try: mmc.read_registers(0,regsSp) except: tb = traceback.format_exc() errCnt += 1 stopTs = time.time() timeDiff = stopTs - startTs mmc.serial.close() print mmc.serial print "mimalmodbus:\ttime to read %s x %s (x %s regs): %.3f [s] / %.3f [s/req]" % (len(slavesArr),iterSp, regsSp, timeDiff, timeDiff/iterSp) if errCnt >0: print " !mimalmodbus:\terrCnt: %s; last tb: %s" % (errCnt, tb) pymc = pyRtu(method='rtu', port=portNbr, baudrate=baudrate, timeout=timeoutSp) errCnt = 0 startTs = time.time() for i in range(iterSp): for slaveId in slavesArr: try: pymc.read_holding_registers(0,regsSp,unit=slaveId) except: errCnt += 1 tb = traceback.format_exc() stopTs = time.time() timeDiff = stopTs - startTs print "pymodbus:\ttime to read %s x %s (x %s regs): %.3f [s] / %.3f [s/req]" % (len(slavesArr),iterSp, regsSp, timeDiff, timeDiff/iterSp) if errCnt >0: print " !pymodbus:\terrCnt: %s; last tb: %s" % (errCnt, tb) pymc.close() tkmc = tkRtu.RtuMaster(serial.Serial(port=portNbr, baudrate=baudrate)) tkmc.set_timeout(timeoutSp) errCnt = 0 startTs = time.time() for i in range(iterSp): for slaveId in slavesArr: try: tkmc.execute(slaveId, tkCst.READ_HOLDING_REGISTERS, 0,regsSp) except: errCnt += 1 tb = traceback.format_exc() stopTs = time.time() timeDiff = stopTs - startTs print "modbus-tk:\ttime to read %s x %s (x %s regs): %.3f [s] / %.3f [s/req]" % (len(slavesArr),iterSp, regsSp, timeDiff, timeDiff/iterSp) if errCnt >0: print " !modbus-tk:\terrCnt: %s; last tb: %s" % (errCnt, tb) tkmc.close()
results:
platform: P8700 @2.53GHz WinXP sp3 32bit Python 2.7.1 FTDI FT232R series 1220-0 FTDI driver 2.08.26 (watch out for possible issues with 2.08.30 version on Windows) pymodbus version 1.2.0 MinimalModbus version 0.4 modbus-tk version 0.4.2
reading 100 x 64 registers:
no power saving
timeout: 0.05 [s] Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 64 regs): 9.135 [s] / 0.091 [s/req] pymodbus: time to read 1 x 100 (x 64 regs): 6.151 [s] / 0.062 [s/req] modbus-tk: time to read 1 x 100 (x 64 regs): 2.280 [s] / 0.023 [s/req] timeout: 0.03 [s] Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 64 regs): 7.292 [s] / 0.073 [s/req] pymodbus: time to read 1 x 100 (x 64 regs): 3.170 [s] / 0.032 [s/req] modbus-tk: time to read 1 x 100 (x 64 regs): 2.342 [s] / 0.023 [s/req] timeout: 0.018 [s] Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 64 regs): 4.481 - 7.198 [s] / 0.045 - 0.072 [s/req] pymodbus: time to read 1 x 100 (x 64 regs): 3.045 [s] / 0.030 [s/req] modbus-tk: time to read 1 x 100 (x 64 regs): 2.342 [s] / 0.023 [s/req]
maximum power saving
timeout: 0.05 [s] Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 64 regs): 10.289 [s] / 0.103 [s/req] pymodbus: time to read 1 x 100 (x 64 regs): 6.074 [s] / 0.061 [s/req] modbus-tk: time to read 1 x 100 (x 64 regs): 2.358 [s] / 0.024 [s/req] timeout: 0.03 [s] Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 64 regs): 8.166 [s] / 0.082 [s/req] pymodbus: time to read 1 x 100 (x 64 regs): 4.138 [s] / 0.041 [s/req] modbus-tk: time to read 1 x 100 (x 64 regs): 2.327 [s] / 0.023 [s/req] timeout: 0.018 [s] Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 64 regs): 7.776 [s] / 0.078 [s/req] pymodbus: time to read 1 x 100 (x 64 regs): 3.169 [s] / 0.032 [s/req] modbus-tk: time to read 1 x 100 (x 64 regs): 2.342 [s] / 0.023 [s/req]
reading 100 x 10 registers:
no power saving
timeout: 0.05 [s] Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 10 regs): 6.246 [s] / 0.062 [s/req] pymodbus: time to read 1 x 100 (x 10 regs): 6.199 [s] / 0.062 [s/req] modbus-tk: time to read 1 x 100 (x 10 regs): 1.577 [s] / 0.016 [s/req] timeout: 0.03 [s] Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 10 regs): 3.088 [s] / 0.031 [s/req] pymodbus: time to read 1 x 100 (x 10 regs): 3.143 [s] / 0.031 [s/req] modbus-tk: time to read 1 x 100 (x 10 regs): 1.533 [s] / 0.015 [s/req] timeout: 0.018 [s] Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 10 regs): 3.066 [s] / 0.031 [s/req] pymodbus: time to read 1 x 100 (x 10 regs): 3.006 [s] / 0.030 [s/req] modbus-tk: time to read 1 x 100 (x 10 regs): 1.533 [s] / 0.015 [s/req]
maximum power saving
timeout: 0.05 [s] Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 10 regs): 6.386 [s] / 0.064 [s/req] pymodbus: time to read 1 x 100 (x 10 regs): 5.934 [s] / 0.059 [s/req] modbus-tk: time to read 1 x 100 (x 10 regs): 1.499 [s] / 0.015 [s/req] timeout: 0.03 [s] Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 10 regs): 3.139 [s] / 0.031 [s/req] pymodbus: time to read 1 x 100 (x 10 regs): 3.170 [s] / 0.032 [s/req] modbus-tk: time to read 1 x 100 (x 10 regs): 1.562 [s] / 0.016 [s/req] timeout: 0.018 [s] Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False) mimalmodbus: time to read 1 x 100 (x 10 regs): 3.123 [s] / 0.031 [s/req] pymodbus: time to read 1 x 100 (x 10 regs): 3.060 [s] / 0.031 [s/req] modbus-tk: time to read 1 x 100 (x 10 regs): 1.561 [s] / 0.016 [s/req]
Load example for modbus-rpc bridge (~3% is caused by RPC server part)
5 x 64 registers synchronous reads per second and simultaneous
asynchronous access with serial port timeout set to 0.018 s
modbus-tk
pymodbus:
EDIT: the modbus-tk library can be easily improved to reduce the CPU usage. In the original version after request is sent and T3.5 sleep passed master assembles response one byte at a time. Profiling proved most od the time is spent on serial port access. This can be improved by trying to read the expected length of data from the serial buffer. According to pySerial documentation it should be safe (no hang up when response is missing or too short) if timeout is set:
read(size=1) Parameters: size – Number of bytes to read. Returns: Bytes read from the port. Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read.
after modifying the `modbus_rtu.py' in the following way:
def _recv(self, expected_length=-1): """Receive the response from the slave""" response = "" read_bytes = "dummy" iterCnt = 0 while read_bytes: if iterCnt == 0: read_bytes = self._serial.read(expected_length) # reduces CPU load for longer frames; serial port timeout is used anyway else: read_bytes = self._serial.read(1) response += read_bytes if len(response) >= expected_length >= 0: #if the expected number of byte is received consider that the response is done #improve performance by avoiding end-of-response detection by timeout break iterCnt += 1
After modbus-tk modification the CPU load in the real-life application dropped considerably without significant performance penalty (still better than pymodbus):
Updated load example for modbus-rpc bridge (~3% is caused by RPC server part)
5 x 64 registers synchronous reads per second and simultaneous
asynchronous access with serial port timeout set to 0.018 s
modbus-tk
pymodbus:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With