Biblioteca de Modbus Python
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
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)
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}
-
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.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.