Python modbus库

P3t*_*rus 45 python modbus

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

有哪些优点/缺点,还有更好的选择吗?

Mr.*_*itt 111

大约在同一时间我遇到了同样的问题 - 哪个库选择用于python modbus主站实现,但在我的情况下用于串行通信(modbus RTU),因此我的观察仅对modbus RTU有效.

在我的考试中,我并没有过多关注文档,但是串行RTU大师的例子最容易找到modbus-tk但是仍然在源代码中没有在维基等.

长话短说:

MinimalModbus:

  • 优点:
    • 轻量级模块
    • 读取~10个寄存器的应用程序可以接受性能
  • 缺点:
    • 阅读~64个寄存器时,(对于我的应用程序)来说,这是不可接受的
    • CPU负载相对较高

pymodbus:

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

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

MODBUS-TK:

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

  • 利弊
    • 最棒的表演; 比动态超时的pymodbus快〜2倍
  • 缺点:
    • 约.与pymodbus相比,可以大大提高 4倍的CPU负载// 这一点无效; 最后看EDIT部分
    • 更大的请求的CPU负载增加// 可以大大改善,使这一点无效; 最后看EDIT部分
    • 代码不如pymodbus那么优雅

超过6个月我使用pymodbus是因为最佳的性能/ CPU负载率,但不可靠的响应在更高的请求率下成为一个严重的问题,最终我转向更快的嵌入式系统并增加了对我最好的modbus-tk的支持.

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

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

建立:

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

用例场景:

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

典型的长期(周)表现:

  • MinimalModbus:初始测试后下降
  • pymodbus:读取64个寄存器〜30ms; 有效地高达30个请求/秒
  • modbus-tk:读取64个寄存器〜16ms; 对于较小的请求,有效地高达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()
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

读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]
Run Code Online (Sandbox Code Playgroud)

最大功率节省

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]
Run Code Online (Sandbox Code Playgroud)

读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]
Run Code Online (Sandbox Code Playgroud)

最大功率节省

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]
Run Code Online (Sandbox Code Playgroud)

现实生活中的应用:

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

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

  • 异步访问,串口超时设置为0.018秒

    • MODBUS-TK

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

      • 10 regs:{'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. 
Run Code Online (Sandbox Code Playgroud)

在以下列方式修改`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
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 异步访问,串口超时设置为0.018秒

    • MODBUS-TK

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

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

  • 所以我现在已经使用stackoverflow超过5年了,这可能是我遇到过的最好的回应.写得很好并解释了很多.我试图找出为什么我通过modbus获得慢速数据吞吐量,因为我可以通过115220波特的直接序列获得每分钟37k +读/写.感谢基准测试代码,我没有意识到modbus-tk.我已经通过TCP使用了pymodbus一段时间并且工作得非常快,但测试了一些连续的东西并且运行速度很慢,但这对我来说回答了很多问题,所以谢谢. (11认同)
  • 仅供参考,提议的改进已在 modbus-tk 中实现。谢谢这个建议 (3认同)

Tra*_*ggs 7

我刚刚发现了uModbus,并且要在Raspberry PI(或其他小型SBC)之类的系统中进行部署,这是一个梦想。这是一个简单的单功能软件包,不会像pymodbus那样引入10多个依赖项。


小智 5

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

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

MinimalModbus是一个非常简单的库。我最终在应用程序中使用了它,因为它确实完成了我需要做的事情。据我所知,它仅进行RTU通信。我从来没有遇到任何麻烦。

我从未研究过Modbus-tk,所以我不知道它的位置。

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