Bibliothèque modbus de Python
je dois contrôler un périphérique modbus avec une interface série. J'ai pas d'expérience avec modbus. Mais ma courte recherche a révélé plusieurs bibliothèques modbus
- pymodbus
- MinimalModbus
- Modbus-tk
Quels sont les avantages/désavantages, y a-t-il des alternatives encore meilleures?
3 réponses
a peu près au même moment j'ai fait face au même problème - quelle bibliothèque choisir pour l'implémentation maître de modbus de python mais dans mon cas pour la communication série (modbus RTU) donc mes observations ne sont valables que pour modbus RTU.
dans mon examen je n'ai pas accordé trop d'attention à la documentation mais les exemples pour serial RTU master étaient les plus faciles à trouver pour modbus-tk cependant toujours en source pas sur un wiki etc.
garder une longue histoire bref:
MinimalModbus:
- pros:
- module léger
- performance peut être acceptable pour des applications de lecture de ~10 registres
- cons:
- inacceptable (pour mon application) lent lors de la lecture de ~64 registres
- charge CPU relativement élevée
pymodbus:
caractéristique distinctive: repose sur le flux de série (post par l'auteur) et le temps d'arrêt en série doit être réglé dynamiquement sinon les performances seront basses (le temps d'arrêt en série doit être réglé pour la réponse la plus longue possible)
- pros:
- faible charge CPU
- performance acceptable
- cons:
- même lorsque le timeout est réglé dynamiquement, la performance est 2 fois plus faible que celle du modbus-tk; si le timeout est laissé à une valeur constante, la performance est beaucoup plus élevée que celle du modbus-tk. le pire (mais la requête est constant)
- sensible au matériel (en raison de la dépendance sur le flux de traitement à partir du tampon de série je pense) ou il peut y avoir un problème interne avec les transactions: vous pouvez obtenir des réponses mélangées si différentes lisent ou lisent/écrivent sont effectuées ~20 fois par seconde ou plus. Des délais plus longs aident, mais ne rendent pas toujours l'implémentation de pymodbus RTU sur une ligne Série pas assez robuste pour une utilisation en production.
- ajout d'un support pour la série dynamique le réglage du port timeout nécessite une programmation supplémentaire: hériter de la classe client sync de base et implémenter les méthodes de modification du délai d'attente de socket
- validation des réponses pas aussi détaillée qu'en modbus-tk. Par exemple, dans le cas d'une carie de bus, seule l'exception est lancée, tandis que modbus-tk retourne dans la même situation une fausse adresse d'esclave ou une erreur CRC qui aide à identifier la cause profonde du problème (qui peut être un délai trop court, une mauvaise terminaison / absence de bus ou un terrain flottant). etc.)
modbus-tk:
caractéristique distinctive: sonde le tampon de série pour les données, assemble et renvoie la réponse rapidement.
- pros
- meilleure performance; ~2 x fois plus rapide que pymodbus avec un temps d'arrêt dynamique
- cons:
- env. 4 x charge CPU plus élevée par rapport à pymodbus// peut être grandement améliorée en rendant ce point invalide; voir la section éditer à la fin
- augmentation de la charge CPU pour les requêtes plus importantes// peut être grandement amélioré en rendant ce point invalide; voir la section éditer à la fin
- code pas aussi élégant que pymodbus
pendant plus de 6 mois, j'ai utilisé pymodbus en raison de la meilleure performance / rapport de charge CPU, mais des réponses peu fiables est devenu un problème grave à des taux de demande plus élevés et finalement je suis passé à un système intégré plus rapide et un soutien supplémentaire pour modbus-tk qui fonctionne le mieux pour moi.
Pour ceux qui sont intéressés dans les détails
mon but était d'obtenir un temps de réponse minimum.
installation:
- débit en bauds: 153600
- en synchronisation avec 16MHz horloge du microcontrôleur mise en œuvre de modbus esclave)
- mon bus rs-485 a seulement 50m
- FTDI FT232R convertisseur et également de série sur TCP pont (à l'aide de com4com comme un pont dans RFC2217 mode)
- dans le cas D'un convertisseur USB-série les plus bas temps d'attente et les tailles de tampon configurées pour le port série (pour réduire la latence)
- auto-TX rs-485 adaptateur (le bus a un État dominant)
scénario de cas d'Utilisation:
- bureaux de 5, 8 ou 10 fois par seconde avec le soutien pour l'accès asynchrone entre
- demandes de lecture / écriture 10 à 70 registres
moyenne à long terme (semaines) performance:
- MinimalModbus: abandonné après les premiers tests
- pymodbus: ~30ms pour lire 64 registres; effectivement jusqu'à 30 demandes / sec
- mais les réponses ne sont pas fiables (en cas d'accès synchronisé à partir de plusieurs threads)
- il n'est peut-être un des threads fork sur github, mais c'est derrière le maître et je n'ai pas essayé (https://github.com/xvart/pymodbus/network)
- modbus-tk: ~16ms à lire 64 registres; effectivement jusqu'à 70 - 80 requêtes / s pour les requêtes
test
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()
résultats:
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
lecture 100 x 64 registres:
pas d'économie d'énergie
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]
économie maximale de puissance
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]
lecture de 100 x 10 registres:
pas d'économie d'énergie
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]
puissance maximale d'économie d'
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]
la vie réelle demande:
exemple de charge pour le pont modbus-rpc (~3% est causé par une partie du serveur RPC)
5 x 64 enregistre des lectures synchrones par seconde et simultanées
accès asynchrone avec temporisation du port série réglée à 0.018 s
modbus-tk
- 10 regs: {'currentCpuUsage': 20.6, 'requestsPerSec': 73.2}//peut être amélioré; voir éditer la section ci-dessous
- 64 regs: {'currentCpuUsage': 31.2, 'requestsPerSec': 41.91}/ peut être amélioré; voir modifier la section ci-dessous
pymodbus:
- 10 regs: {'currentCpuUsage': 5.0, 'requestsPerSec': 36.88}
- 64 regs: {'currentCpuUsage': 5.0, 'requestsPerSec': 34.29}
EDIT: la la bibliothèque modbus-tk peut être facilement améliorée pour réduire l'utilisation du CPU. Dans la version originale après la demande est envoyée et T3.5 sommeil passé maître assemble réponse un octet à la fois. Le profilage a prouvé la plupart du temps od le temps est passé sur l'accès au port série. Ceci peut être amélioré en essayant de lire la longueur attendue des données à partir du tampon série. Selon pySerial documentation il doit être sûr (pas de raccrocher quand la réponse est manquante ou trop courte) si le délai est réglé:
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.
après modification du `modbus_rtu.py" de la manière suivante:
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
après modification modbus-tk, la charge CPU de L'application réelle a chuté considérablement sans pénalité significative de performance (toujours meilleure que pymodbus):
Updated load example for modbus-rpc bridge (~3% est causé par une partie du serveur RPC)
5 x 64 enregistre des lectures synchrones par seconde et simultanée
accès asynchrone avec temporisation du port série réglée à 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}
cela dépend vraiment de l'application que vous utilisez, et de ce que vous essayez d'atteindre.
pymodbus est une bibliothèque très robuste. Il fonctionne, et il vous donne beaucoup d'outils pour travailler avec. Mais il peut se révéler être un peu intimidant quand vous essayez de l'utiliser. Je l'ai trouvé difficile à travailler personnellement. Il vous offre la possibilité d'utiliser à la fois RTU et TCP/IP, ce qui est génial!
MinimalModbus est une bibliothèque très simple. J'ai fini par utiliser ceci pour mon application parce qu'il a fait exactement ce dont j'avais besoin qu'il fasse. Il ne fait que les communications RTU, et il le fait aussi bien que je le sais. Je n'ai jamais eu aucun problème avec elle.
Je n'ai jamais cherché dans Modbus-tk, donc je ne sais pas où il se trouve.
en fin de compte cependant, cela dépend de ce que votre application est. Finalement, j'ai découvert que python n'était pas le meilleur choix pour moi.
je viens de découvrir uModbus, et pour un déploiement dans quelque chose comme un Raspberry PI (ou autre petite SBC), c'est un rêve. C'est un simple paquet capable qui n'apporte pas de dépendances 10+ comme le fait pymodbus.