web-dev-qa-db-fra.com

Python bibliothèque modbus

Je dois contrôler un appareil Modbus avec une interface série. Je n'ai pas d'expérience avec Modbus. Mais ma courte recherche a révélé plusieurs bibliothèques Modbus

Quels sont les avantages/inconvénients, existe-t-il des alternatives encore meilleures?

43
P3trus

À peu près au même moment, j'ai été confronté au même problème - quelle bibliothèque choisir pour python implémentation du maître Modbus mais dans mon cas pour la communication série (Modbus RTU), donc mes observations ne sont valables que pour Modbus RTU.

Lors de mon examen, je n'ai pas prêté trop d'attention à la documentation, mais les exemples de maître série RTU étaient plus faciles à trouver pour modbus-tk, mais toujours dans la source, pas sur un wiki, etc.

pour faire court:

MinimalModbus:

  • avantages:
    • module léger
    • les performances peuvent être acceptables pour les applications lisant ~ 10 registres
  • les inconvénients:
    • 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 série ( publication de l'auteur ) et le délai d'expiration série doit être défini dynamiquement sinon les performances seront faibles (le délai d'expiration série doit être ajusté pour la réponse la plus longue possible)

  • avantages:
    • faible charge CPU
    • performance acceptable
  • les inconvénients:
    • même lorsque le timeout est réglé dynamiquement, les performances sont 2 fois inférieures à celles de modbus-tk; si le délai est laissé à une valeur constante, les performances sont bien pires (mais le temps de requête est constant)
    • sensible au matériel (en raison de la dépendance du flux de traitement du tampon 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 lectures ou lectures/écritures 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.
    • l'ajout de la prise en charge du paramètre de délai d'expiration du port série dynamique nécessite une programmation supplémentaire: héritage de la classe client de synchronisation de base et implémentation des méthodes de modification du délai d'expiration du socket
    • la validation des réponses n'est pas aussi détaillée que dans modbus-tk. Par exemple, en cas de désintégration de bus, seule une exception est levée alors que modbus-tk renvoie dans la même situation une mauvaise adresse esclave ou une erreur CRC qui aide à identifier la cause racine du problème (qui peut être un délai trop court, une mauvaise terminaison de bus/son absence ou sol flottant, etc.)

modbus-tk:

caractéristique distinctive: sonde le tampon série pour les données, assemble et renvoie rapidement la réponse.

  • avantages
    • meilleure performance; ~ 2 fois plus rapide que pymodbus avec timeout dynamique
  • les inconvénients:
    • environ. 4 x charge CPU plus élevée par rapport à pymodbus // peut être considérablement améliorée rendant ce point invalide; voir la section EDIT à la fin
    • La charge du processeur augmente pour les demandes plus importantes // peut être considérablement améliorée, ce qui rend ce point invalide; voir la section EDIT à la fin
    • code pas aussi élégant que pymodbus

Pendant plus de 6 mois, j'utilisais pymodbus en raison du meilleur rapport performances/charge du processeur, mais les réponses peu fiables sont devenues un problème grave à des taux de demande plus élevés et j'ai finalement opté pour un système embarqué plus rapide et ajouté la prise en charge de modbus-tk qui fonctionne le mieux pour moi.

Pour ceux intéressés par les détails

Mon objectif était d'atteindre un temps de réponse minimum.

installer:

  • vitesse de transmission: 153600
    • en synchronisation avec l'horloge 16 MHz du microcontrôleur implémentant l'esclave Modbus)
    • mon bus rs-485 a seulement 50m
  • Convertisseur FTDI FT232R et également série sur TCP bridge (en utilisant com4com comme pont en mode RFC2217)
  • dans le cas d'un convertisseur USB vers série, les délais d'attente les plus bas et les tailles de mémoire tampon configurés pour le port série (pour réduire la latence)
  • adaptateur auto-tx rs-485 (le bus a un état dominant)

Scénario d'utilisation:

  • Interrogation 5, 8 ou 10 fois par seconde avec prise en charge de l'accès asynchrone entre les deux
  • Demandes de lecture/écriture de 10 à 70 registres

Performance typique à long terme (semaines):

  • MinimalModbus: abandonné après les premiers tests
  • pymodbus: ~ 30 ms pour lire 64 registres; efficacement jusqu'à 30 requêtes/sec
    • mais les réponses ne sont pas fiables (en cas d'accès synchronisé à partir de plusieurs threads)
    • il y a peut-être un fork threadsafe sur github mais il est derrière le maître et je ne l'ai pas essayé ( https://github.com/xvart/pymodbus/network )
  • modbus-tk: ~ 16 ms pour lire 64 registres; efficacement jusqu'à 70 - 80 requêtes/sec pour les requêtes plus petites

référence

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 de 100 x 64 registres:

aucune é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 d'énergie maximale

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:

aucune é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]

économie d'énergie maximale

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]

application réelle:

Exemple de charge pour le pont modbus-rpc (~ 3% est causé par une partie serveur RPC)

  • 5 x 64 registres de lectures synchrones par seconde et simultanées

  • accès asynchrone avec délai d'expiration du port série défini sur 0,018 s

    • modbus-tk

      • 10 regs: {'currentCpuUsage': 20.6, 'requestsPerSec': 73.2} // peut être amélioré; voir la section MODIFIER ci-dessous
      • 64 regs: {'currentCpuUsage': 31.2, 'requestsPerSec': 41.91} // peut être amélioré; voir la section MODIFIER ci-dessous
    • pymodbus:

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

EDIT: la bibliothèque modbus-tk peut être facilement améliorée pour réduire l'utilisation du CPU. Dans la version d'origine après l'envoi de la demande et le passage en veille T3.5, le maître assemble la réponse un octet à la fois. Le profilage s'est avéré le plus od le temps est consacré à l'accès au port série. Cela peut être amélioré en essayant de lire la longueur attendue des données du tampon série. Selon documentation pySerial = il doit être sûr (pas de raccrochage lorsque la réponse est manquante ou trop courte) si le délai est défini:

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 avoir modifié le `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 la modification de Modbus-tk, la charge du processeur dans l'application réelle a considérablement diminué sans pénalité de performance significative (encore meilleure que pymodbus):

Exemple de charge mise à jour pour le pont modbus-rpc (~ 3% est causé par une partie serveur RPC)

  • 5 x 64 registres de lectures synchrones par seconde et simultanées

  • accès asynchrone avec délai d'expiration du port série défini sur 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}
109
Mr. Girgitt

Je viens de découvrir Modbus , et pour le déploiement dans quelque chose comme un Raspberry Pi (ou un autre petit SBC), c'est un rêve. C'est un simple package capable unique qui n'apporte pas plus de 10 dépendances comme le fait pymodbus.

6
Travis Griggs

Cela dépend vraiment de l'application que vous utilisez et de ce que vous essayez de réaliser.

pymodbus est une bibliothèque très robuste. Cela fonctionne et cela vous donne beaucoup d'outils pour travailler. Mais cela peut s'avérer un peu intimidant lorsque vous essayez de l'utiliser. J'ai eu du mal à travailler avec 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 cela pour mon application, car il faisait exactement ce dont j'avais besoin. Il ne fait que des communications RTU, et il le fait bien pour autant que je sache. Je n'ai jamais eu de problème avec ça.

Je n'ai jamais regardé Modbus-tk, donc je ne sais pas où il en est.

En fin de compte, cela dépend de votre application. Au final, j'ai trouvé que python n'était pas le meilleur choix pour moi.

4
Windsplunts