Python modbus 库

标签 python modbus

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

  • pymodbus
  • MinimalModbus
  • Modbus-tk
  • uModbus

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

    最佳答案

    大约在同一时间,我遇到了同样的问题 - 为 python modbus master 实现选择哪个库,但在我的情况下是串行通信(modbus RTU),所以我的观察只对 modbus RTU 有效。
    在我的考试中,我并没有过多关注文档,但是对于 modbus-tk 最容易找到串行 RTU 主站的示例,但是仍然在源代码中,而不是在 wiki 等上。
    长话短说:
    最小Modbus:

  • 优点:
  • 轻量模块
  • 对于读取 ~10 个寄存器的应用程序,性能可能是可以接受的

  • 缺点:
  • 读取 ~64 个寄存器时速度慢得令人无法接受(对于我的应用程序)
  • CPU 负载相对较高


  • pymodbus:
    显着特点:依赖于串行流( post by the author ),串行超时必须动态设置,否则性能会很低(必须调整串行超时以获得尽可能长的响应)
  • 优点:
  • CPU 负载低
  • 可接受的性能

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


  • modbus-tk:
    显着特点:探测串行缓冲区数据,快速组装和返回响应。
  • 优点
  • 最棒的表演;比具有动态超时的 pymodbus 快 2 倍

  • 缺点:
  • 大约与 pymodbus 相比,CPU 负载高 4 倍//可以大大改善使这一点无效;见最后的编辑部分
  • CPU 负载会因更大的请求而增加//可以大大改善使这一点无效;见最后的编辑部分
  • 代码不如 pymodbus 优雅


  • 6 个多月以来,由于最佳性能/CPU 负载率,我一直在使用 pymodbus,但在更高的请求率下,不可靠的响应成为一个严重问题,最终我转向了更快的嵌入式系统,并添加了对 modbus-tk 的支持,这对我来说效果最好。
    对于那些对细节感兴趣的人
    我的目标是实现最短的响应时间。
    设置:
  • 波特率:153600
  • 与实现 modbus slave 的微 Controller 的 16MHz 时钟同步)
  • 我的 rs-485 总线只有 50m

  • FTDI FT232R 转换器和串行 TCP 桥接器(在 RFC2217 模式下使用 com4com 作为桥接器)
  • 如果 USB 到串行转换器为串行端口配置了最低超时和缓冲区大小(以降低延迟)
  • auto-tx rs-485 适配器(总线具有显性状态)

  • 用例场景:
  • 每秒轮询 5、8 或 10 次,支持异步访问
  • 请求读/写 10 到 70 个寄存器

  • 典型的长期(数周)表现:
  • MinimalModbus:初始测试后下降
  • pymodbus:~30ms 读取 64 个寄存器;有效高达 30 个请求/秒
  • 但响应不可靠(在多个线程同步访问的情况下)
  • github 上可能有一个线程安全分支,但它在 master 后面,我还没有尝试过(https://github.com/xvart/pymodbus/network)

  • 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 秒的异步访问
  • 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 sleep 后,主机一次一个字节地组装响应。分析证明大部分时间都花在了串行端口访问上。这可以通过尝试从串行缓冲区读取预期长度的数据来改进。根据 pySerial documentation如果设置了超时,它应该是安全的(当响应丢失或太短时不会挂断):
    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 秒的异步访问
  • modbus-tk
  • 10 条:{'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}


  • 关于Python modbus 库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17081442/

    相关文章:

    python - 为什么SimpleITK不使用imageJ显示图像?

    python - 如何通过删除python中的重复项进行压缩?

    python - 如何在 python 中使用 yield 函数

    python - 如何使用 Python 接收邮件?

    python - python 内置的类型是唯一的

    python - 如何使用 modbus-tk RTU 设置简单的从站和主站 (Python)

    java - J2Mod基础主从题

    modbus - 为什么我们需要 Modbus 中的输入寄存器、线圈位和输入位

    c# - 与多个从站通信(基于 Modbus 协议(protocol))

    c++ - MODBUS TCP 应答转换为浮点 C++