Python modbus library

后端 未结 3 1332
予麋鹿
予麋鹿 2020-12-04 06:54

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

  • pymodb
相关标签:
3条回答
  • 2020-12-04 07:08

    I just discovered uModbus, and for deployment in something like a Raspberry PI (or other small SBC), it's a dream. It's a simple single capable package that doesn't bring in 10+ dependencies like pymodbus does.

    0 讨论(0)
  • 2020-12-04 07:12

    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.

    keeping long story short:

    MinimalModbus:

    • pros:
      • lightweight module
      • performance may be acceptable for applications reading ~10 registers
    • cons:
      • unacceptably (for my application) slow when reading ~64 registers
      • relatively high CPU load

    pymodbus:

    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)

    • pros:
      • low CPU load
      • acceptable performance
    • cons:
      • even when timeout is dynamically set performance is 2 x lower compared to modbus-tk; if timeout is left at a constant value performance is much worse (but query time is constant)
      • sensitive to hardware (as a result of dependency on processing stream from serial buffer I think) or there may be internal problem with transactions: you can get responses mixed-up if different reads or reads/writes are performed ~20 times per second or more. Longer timeouts help but not always making pymodbus RTU implementation over a serial line not enough robust for use in production.
      • adding support for dynamic serial port timeout setting requires additional programming: inheriting base sync client class and implementing socket timeout modification methods
      • responses validation not as detailed as in modbus-tk. For example in case of a bus decay only exception is thrown whereas modbus-tk returns in the same situation wrong slave address or CRC error which helps identifying root cause of the problem (which may be too short timeout, wrong bus termination / lack thereof or floating ground etc.)

    modbus-tk:

    distinctive feature: probes serial buffer for data, assembles and returns response quickly.

    • pros
      • best performance; ~2 x times faster than pymodbus with dynamic timeout
    • cons:
      • approx. 4 x higher CPU load compared to pymodbus // can be greately improved making this point invalid; see EDIT section at the end
      • CPU load increases for larger requests // can be greately improved making this point invalid; see EDIT section at the end
      • code not as elegant as pymodbus

    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.

    For those interested in details

    My goal was to achieve minimum response time.

    setup:

    • baudrate: 153600
      • in sync with 16MHz clock of the microcontroller implementing modbus slave)
      • my rs-485 bus has only 50m
    • FTDI FT232R converter and also serial over TCP bridge (using com4com as a bridge in RFC2217 mode)
    • in case of USB to serial converter lowest timeouts and buffer sizes configured for serial port (to lower latency)
    • auto-tx rs-485 adapter (bus has a dominant state)

    Use case scenario:

    • Polling 5, 8 or 10 times a second with support for asynchronous access in between
    • Requests for reading/writing 10 to 70 registers

    Typical long-term (weeks) performance:

    • MinimalModbus: dropped after initial tests
    • pymodbus: ~30ms to read 64 registers; effectively up to 30 requests / sec
      • but responses unreliable (in case of synchronized access from multiple threads)
      • there is possibly a threadsafe fork on github but it's behind the master and I haven't tried it (https://github.com/xvart/pymodbus/network)
    • modbus-tk: ~16ms to read 64 registers; effectively up to 70 - 80 requests / sec for smaller requests

    benchmark

    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]
    

    real-life application:

    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

        • 10 regs: {'currentCpuUsage': 20.6, 'requestsPerSec': 73.2} // can be improved; see EDIT section below
        • 64 regs: {'currentCpuUsage': 31.2, 'requestsPerSec': 41.91} // can be improved; see EDIT section below
      • pymodbus:

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

    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

        • 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}
    0 讨论(0)
  • 2020-12-04 07:24

    It really depends on what application you're using, and what you're trying to achieve.

    pymodbus is a very robust library. It works, and it gives you a lot of tools to work with. But it can prove to be a little intimidating when you try to use it. I found it hard to work with personally. It offers you the ability to use both RTU and TCP/IP, which is great!

    MinimalModbus is a very simple library. I ended up using this for my application because it did exactly what I needed it to do. It only does RTU communications, and it does it well as far as I know. I've never had any trouble with it.

    I've never looked into Modbus-tk, so I don't know where it stands.

    Ultimately though, it does depend on what your application is. In the end I found that python wasn't the best choice for me.

    0 讨论(0)
提交回复
热议问题