62

我必须控制带有串行接口的 modbus 设备。我没有使用modbus的经验。但我的简短研究揭示了几个 modbus 库

有什么优点/缺点,还有更好的选择吗?

4

3 回答 3

143

大约在同一时间,我遇到了同样的问题——为 python modbus master 实现选择哪个库,但在我的情况下用于串行通信(modbus RTU),所以我的观察只对 modbus RTU 有效。

在我的考试中,我并没有过多关注文档,但串行 RTU 主机的示例最容易在 modbus-tk 中找到,但仍然在源代码中而不是在 wiki 等上。

长话短说:

最小Modbus:

  • 优点:
    • 轻量级模块
    • 对于读取约 10 个寄存器的应用程序,性能可能是可以接受的
  • 缺点:
    • 读取〜64个寄存器时(对于我的应用程序)慢得令人无法接受
    • CPU负载相对较高

pymodbus:

显着特点:依赖串行流(作者发表),必须动态设置串行超时,否则性能会降低(必须调整串行超时以获得最长的响应)

  • 优点:
    • 低CPU负载
    • 可接受的性能
  • 缺点:
    • 即使超时是动态设置的,性能也比 modbus-tk 低 2 倍;如果超时保持在一个常数值,性能会差很多(但查询时间是常数)
    • 对硬件敏感(我认为是由于依赖于来自串行缓冲区的处理流)或者事务可能存在内部问题:如果每秒执行大约 20 次或更多不同的读取或读取/写入,您可能会得到混合的响应. 更长的超时时间会有所帮助,但并不总是使 pymodbus RTU 在串行线路上的实现不够健壮,无法在生产中使用。
    • 添加对动态串口超时设置的支持需要额外的编程:继承基同步客户端类并实现套接字超时修改方法
    • 响应验证不如 modbus-tk 详细。例如,在总线衰减的情况下,仅抛出异常,而 modbus-tk 在相同情况下返回错误的从地址或 CRC 错误,这有助于识别问题的根本原因(可能是超时太短、错误的总线终止/缺少或浮地等)

modbus-tk:

显着特点:探测串行缓冲区的数据,快速组装和返回响应。

  • 优点
    • 最棒的表演; 比动态超时的 pymodbus 快约 2 倍
  • 缺点:
    • 大约 与 pymodbus 相比,CPU 负载高 4 倍 //可以大大提高,使这一点无效;见最后的编辑部分
    • 较大的请求会增加 CPU 负载 //可以大大改善这一点,使其无效;见最后的编辑部分
    • 代码不如 pymodbus 优雅

6 个月以来,我一直在使用 pymodbus,因为它具有最佳性能/CPU 负载比,但在更高的请求率下,不可靠的响应成为一个严重的问题,最终我转向了更快的嵌入式系统,并增加了对 modbus-tk 的支持,这对我来说最有效。

对于那些对细节感兴趣的人

我的目标是实现最短的响应时间。

设置:

  • 波特率:153600
    • 与实现 modbus 从机的微控制器的 16MHz 时钟同步)
    • 我的 rs-485 巴士只有 50m
  • FTDI FT232R 转换器和串行 TCP 桥接器(在 RFC2217 模式下使用 com4com 作为桥接器)
  • 在 USB 到串行转换器的情况下,为串行端口配置的最低超时和缓冲区大小(以降低延迟)
  • auto-tx rs-485 适配器(总线具有显性状态)

用例场景:

  • 每秒轮询 5、8 或 10 次,支持中间的异步访问
  • 读取/写入 10 到 70 个寄存器的请求

典型的长期(数周)表现:

  • MinimalModbus:初始测试后丢弃
  • pymodbus:~30ms 读取 64 个寄存器;有效高达 30 个请求/秒
  • modbus-tk:~16ms 读取 64 个寄存器;对于较小的请求,有效高达 70 - 80 个请求/秒

基准

代码:

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()

结果:

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

读取 100 x 64 寄存器:

不省电

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]

最大节电

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]

读取 100 x 10 个寄存器:

不省电

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]

最大节电

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]

实际应用:

modbus-rpc 桥的加载示例(~​​3% 是由 RPC 服务器部分引起的)

  • 5 x 64 寄存器同步读取每秒和同时

  • 串行端口超时设置为 0.018 s 的异步访问

    • modbus-tk

      • 10 regs: {'currentCpuUsage': 20.6, 'requestsPerSec': 73.2} //可以改进;请参阅下面的编辑部分
      • 64 regs: {'currentCpuUsage': 31.2, 'requestsPerSec': 41.91} //可以改进;请参阅下面的编辑部分
    • pymodbus:

      • 10 个注册:{'currentCpuUsage':5.0,'requestsPerSec':36.88}
      • 64 regs: {'currentCpuUsage': 5.0, 'requestsPerSec': 34.29}

编辑: modbus-tk 库可以很容易地改进以减少 CPU 使用率。在发送请求并通过 T3.5 睡眠后的原始版本中,主机一次组装一个字节的响应。分析证明大部分时间都花在了串行端口访问上。这可以通过尝试从串行缓冲区读取预期长度的数据来改进。根据pySerial 文档,如果设置了超时,它应该是安全的(当响应丢失或太短时不会挂断):

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. 

通过以下方式修改“modbus_rtu.py”后:

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

修改 modbus-tk 后,实际应用程序中的 CPU 负载显着下降,而没有显着的性能损失(仍然优于 pymodbus):

更新了 modbus-rpc 桥的负载示例(~​​3% 是由 RPC 服务器部分引起的)

  • 5 x 64 寄存器同步读取每秒和同时

  • 串行端口超时设置为 0.018 s 的异步访问

    • modbus-tk

      • 10 regs:{'currentCpuUsage':7.8,'requestsPerSec':66.81}
      • 64 regs:{'currentCpuUsage':8.1,'requestsPerSec':37.61}
    • pymodbus:

      • 10 个注册:{'currentCpuUsage':5.0,'requestsPerSec':36.88}
      • 64 regs: {'currentCpuUsage': 5.0, 'requestsPerSec': 34.29}
于 2014-01-30T14:30:17.073 回答
11

我刚刚发现了uModbus,对于部署在 Raspberry PI(或其他小型 SBC)中,这是一个梦想。这是一个简单的单一功能包,不会像 pymodbus 那样引入 10 多个依赖项。

于 2018-07-03T16:45:53.410 回答
7

这实际上取决于您使用的应用程序以及您想要实现的目标。

pymodbus 是一个非常强大的库。它有效,它为您提供了很多工具。但是当您尝试使用它时,它可能会有点吓人。我发现很难与个人合作。它使您能够同时使用 RTU 和 TCP/IP,这太棒了!

MinimalModbus 是一个非常简单的库。我最终将它用于我的应用程序,因为它完全符合我的需要。它只进行 RTU 通信,据我所知,它做得很好。我从来没有遇到过任何麻烦。

我从来没有研究过 Modbus-tk,所以我不知道它在哪里。

但最终,它确实取决于您的应用程序是什么。最后我发现python不是我的最佳选择。

于 2013-06-13T18:40:14.447 回答