Biblioteca de Modbus Python

Tenho de controlar um dispositivo modbus com uma interface serial. Não tenho experiência com modbus. Mas a minha pequena pesquisa revelou várias bibliotecas modbus

Quais são as vantagens/desvantagens, existem ainda melhores alternativas?

Author: P3trus, 2013-06-13

3 answers

Mais ou menos na mesma altura em que enfrentei o mesmo problema - qual a biblioteca a escolher para a implementação mestre do modbus em python, mas no meu caso PARA A comunicação serial (modbus RTU), por isso as minhas observações só são válidas para o modbus RTU.

No meu exame não prestei muita atenção à documentação, mas os exemplos para o mestre RTU serial foram mais fáceis de encontrar para modbus-tk no entanto, ainda na fonte não em um wiki etc.

Manter uma longa história curta:

MinimalModbus:

    Prós:
    • módulo leve
    • o desempenho pode ser aceitável para aplicações que lêem ~10 registos
  • conts:
    • inaceitavelmente lento (para a minha aplicação) ao ler ~64 registos
    • carga CPU relativamente elevada

Pymodbus:

Característica distintiva: depende do stream serial (posto pelo autor ) e o tempo-limite de série deve ser dinamicamente definido caso contrário, o desempenho será baixo (o tempo-limite de série deve ser ajustado para a resposta mais longa possível)

    Prós:
    • baixa carga de CPU
    • desempenho aceitável
  • conts:
    • mesmo quando o tempo-limite é dinamicamente definido, o desempenho é 2 x menor em comparação com o modbus-tk; se o tempo-limite é deixado com um desempenho de valor constante é muito pior (mas o tempo da consulta é constante)
    • sensível ao hardware (devido à dependência do fluxo de processamento do buffer serial, eu acho) ou pode haver problema interno com transações: você pode obter respostas misturadas se diferentes leituras ou leituras/escritas são realizadas ~20 vezes por segundo ou mais. Tempos mais longos ajudam, mas nem sempre fazem implementação pymodbus RTU sobre uma linha série não suficientemente robusta para uso na produção.
    • a adição de suporte para a configuração dinâmica do tempo-limite da porta série requer uma programação adicional: herdar a classe cliente de sincronização de base e implementar o tempo-limite do 'socket' métodos de modificação
    • a validação das respostas não é tão detalhada como no modbus-tk. Por exemplo, no caso de um bus decay apenas exceção é lançada, enquanto modbus-tk retorna na mesma situação endereço escravo errado ou erro CRC que ajuda a identificar a causa raiz do problema (que pode ser muito curto tempo-limite, terminação errada do bus / falta dele ou terra flutuante, etc.)

Modbus-tk:

Característica distintiva: sondas tampão serial para dados, conjuntos e retorna resposta rapidamente.

    Prós
    • melhor desempenho; ~2 x vezes mais rápido que o pymodbus com tempo-limite dinâmico
  • conts:
    • aprox. 4 x maior carga de CPU em comparação com pymodbus / / pode ser muito melhorada, tornando este ponto inválido; veja a secção de edição no final
    • Os aumentos de carga do CPU para pedidos maiores / / podem ser muito melhorados, tornando este ponto inválido; ver a secção de edição no final
    • código não tão elegante como pymodbus

Durante mais de 6 meses eu estava usando pymodbus devido à melhor relação desempenho / carga de CPU, mas respostas não confiáveis tornou-se um problema sério a taxas de pedidos mais elevadas e, eventualmente, eu movi-me para um sistema embutido mais rápido e adicionei suporte para modbus-tk que funciona melhor para mim.

Para os interessados em detalhes

O meu objectivo era conseguir o tempo mínimo de resposta.

Configuração:

  • baudrato: 153600
      Em sincronia com: 16MHz clock of the microcontroller implementing modbus slave)
  • O meu autocarro rs-485 só tem 50m
  • conversor FT232R FTDI e também série sobre a ponte TCP (utilizando a com4com como ponte no modo RFC2217)
  • no caso do conversor USB em série, Os tempos-limite mais baixos e os tamanhos de buffer configurados para a porta série (para menor latência)
  • adaptador auto-tx rs-485 (o barramento tem um estado dominante) {[[21]}

    Cenário de Utilização:

    • sondagens 5, 8 ou 10 vezes a em segundo lugar, com apoio ao acesso assíncrono entre
    • pedidos de leitura/escrita de 10 a 70 registos

    Desempenho típico a longo prazo (semanas):

    • MinimalModbus: largado após os testes iniciais
    • pymodbus: ~30ms para ler 64 registos; efetivamente até 30 pedidos / s
        Mas as respostas não são fiáveis (em caso de acesso sincronizado a partir de múltiplas threads)
    • Há possivelmente um garfo em GitHub, mas está atrás do ... o mestre e eu não tentámos. https://github.com/xvart/pymodbus/network)
  • modbus-tk: ~16ms para ler 64 registos; efectivamente até 70-80 pedidos / s para pedidos mais pequenos
  • Parâmetro de Referência

    Código:

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

    Resultados:

    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
    

    Leitura de 100 x 64 registos:

    Sem poupança de energia

    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]
    

    Poupança máxima de energia

    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]
    

    Leitura de 100 x 10 registos:

    Sem energia a gravar

    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]
    

    Poupança máxima de energia

    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]
    

    Aplicação na vida Real:

    Ler o exemplo para a ponte modbus-rpc (~3% é causado pela parte do servidor RPC)

    • 5 x 64 regista leituras sincronizadas por segundo e simultâneas

    • Acesso assíncrono com tempo-limite da porta série definido para 0, 018 s

      • Modbus-tk

        • 10 regs: {'currentCpuUsage': 20.6, 'requestsPerSec': 73.2} // pode ser melhorado; veja editar secção abaixo
        • 64 regs: {'currentCpuUsage': 31.2, 'requestsPerSec': 41.91} // pode ser melhorado; veja a secção de edição abaixo
      • Pymodbus:

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

    EDIT: a biblioteca modbus-tk pode ser facilmente melhorada para reduzir a utilização do CPU. No original versão após pedido é enviado e T3. 5 sleep passed master reúne resposta um byte de cada vez. O perfil provou que a maior parte do tempo é gasto em acesso a porta série. Isto pode ser melhorado tentando ler o comprimento esperado dos dados do buffer serial. De acordo com a documentação pySerial deve ser segura (sem desligar quando falta resposta ou demasiado curta) se o tempo-limite estiver definido:

    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. 
    

    Depois de modificar o `modbus_rtu.py " da seguinte forma:

    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
    

    Depois Modbus-tk modification the CPU load in the real-life application dropped considerably without significant performance penalty (still better than pymodbus):

    O exemplo de carga actualizado para a ponte modbus-rpc (~3% é causado pela parte do servidor RPC)

    • 5 x 64 regista leituras sincronizadas por segundo e simultâneas

    • Acesso assíncrono com tempo-limite da porta série definido para 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}
     95
    Author: Mr. Girgitt, 2014-02-27 17:53:59
    Isso realmente depende da aplicação que você está usando, e do que você está tentando alcançar.

    Pymodbus é uma biblioteca muito robusta. Funciona, e dá-lhe muitas ferramentas para trabalhar. Mas pode ser um pouco intimidante quando se tenta usá-lo. Achei difícil trabalhar com ele pessoalmente. Ele oferece a você a capacidade de usar tanto a RTU e TCP/IP, o que é ótimo!

    MinimalModbus é uma biblioteca muito simples. Acabei por usar isto para a minha candidatura porque fez exactamente o que eu precisava. Só faz comunicações RTU, e fá-lo tão bem quanto sei. Nunca tive problemas com isso.

    Nunca investiguei o Modbus-tk, por isso não sei qual é a sua posição. No entanto, em última análise, depende de qual é a sua aplicação. No final, descobri que o python não era a melhor escolha para mim.
     4
    Author: Windsplunts, 2013-06-13 18:40:14

    Acabei de descobrir uModbus, e para a implantação em algo como um Raspberry PI (ou outro pequeno SBC), é um sonho. É um simples pacote único capaz que não traz 10+ dependências como o pymodbus faz.

     2
    Author: Travis Griggs, 2018-07-03 16:45:53