Python modbus библиотека

Трябва да управлявам modbus устройство със сериен интерфейс. Нямам опит с modbus. Но краткото ми проучване разкри няколко modbus библиотеки

Какви са предимствата/недостатъците, има ли още по-добри алтернативи?


person P3trus    schedule 13.06.2013    source източник


Отговори (3)


Горе-долу по същото време се сблъсках със същия проблем - коя библиотека да избера за реализация на python modbus master, но в моя случай за серийна комуникация (modbus RTU), така че моите наблюдения са валидни само за modbus RTU.

При проверката си не обърнах много внимание на документацията, но примерите за сериен RTU master бяха най-лесни за намиране за modbus-tk, но все още в източника, а не в wiki и т.н.

запазвайки дългата история кратка:

MinimalModbus:

  • pros:
    • lightweight module
    • производителността може да е приемлива за приложения, четещи ~10 регистъра
  • cons:
    • unacceptably (for my application) slow when reading ~64 registers
    • относително високо натоварване на процесора

pymodbus:

отличителна черта: разчита на сериен поток (публикация от автора) и серийното изчакване трябва да бъде динамично зададено, в противен случай производителността ще бъде ниска (серийното изчакване трябва да се коригира за възможно най-дълъг отговор)

  • pros:
    • low CPU load
    • приемливо представяне
  • 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)
    • чувствителен към хардуер (мисля, че в резултат на зависимост от обработващ поток от сериен буфер) или може да има вътрешен проблем с транзакциите: можете да получите смесени отговори, ако различни четения или четения/записи се извършват ~20 пъти в секунда или повече . По-дългите изчаквания помагат, но не винаги правят изпълнението на pymodbus RTU през серийна линия недостатъчно стабилно за използване в производството.
    • добавянето на поддръжка за динамична настройка за таймаут на сериен порт изисква допълнително програмиране: наследяване на базов клиентски клас за синхронизиране и прилагане на методи за модифициране на таймаут на сокет
    • валидирането на отговорите не е толкова подробно, колкото в modbus-tk. Например в случай на затихване на шината се хвърля само изключение, докато modbus-tk връща в същата ситуация грешен подчинен адрес или CRC грешка, което помага да се идентифицира основната причина за проблема (което може да е твърде кратко време за изчакване, грешно прекъсване на шината / липса на такова или плаваща земя и др.)

modbus-tk:

отличителна черта: сондира сериен буфер за данни, сглобява и връща отговор бързо.

  • 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
    • Натоварването на процесора се увеличава за по-големи заявки // може да бъде значително подобрено, което прави тази точка невалидна; вижте раздела РЕДАКТИРАНЕ в края
    • код не е толкова елегантен като pymodbus

Повече от 6 месеца използвах pymodbus поради най-доброто съотношение производителност/натоварване на процесора, но ненадеждните отговори се превърнаха в сериозен проблем при по-високи честоти на заявки и в крайна сметка преминах към по-бърза вградена система и добавих поддръжка за modbus-tk, която работи най-добре за мен.

За интересуващите се от подробности

Целта ми беше да постигна минимално време за реакция.

настройвам:

  • baudrate: 153600
    • in sync with 16MHz clock of the microcontroller implementing modbus slave)
    • моят автобус rs-485 има само 50 метра
  • FTDI FT232R конвертор и също сериен през TCP мост (използвайки com4com като мост в режим RFC2217)
  • в случай на преобразувател от USB към сериен най-ниски изчаквания и размери на буфера, конфигурирани за сериен порт (за намаляване на латентността)
  • auto-tx rs-485 адаптер (шината има доминиращо състояние)

Сценарий на използване:

  • Проучване 5, 8 или 10 пъти в секунда с поддръжка за асинхронен достъп между тях
  • Заявки за четене/запис от 10 до 70 регистъра

Типична дългосрочна (седмици) ефективност:

  • MinimalModbus: отпадна след първоначални тестове
  • pymodbus: ~30ms to read 64 registers; effectively up to 30 requests / sec
    • but responses unreliable (in case of synchronized access from multiple threads)
    • вероятно има безопасен за нишки разклонение в github, но той е зад главния и не съм го пробвал (https://github.com/xvart/pymodbus/network)
  • modbus-tk: ~16ms за четене на 64 регистъра; ефективно до 70 - 80 заявки/сек за по-малки заявки

бенчмарк

код:

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

Резултати:

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

четене на 100 x 64 регистъра:

без пестене на енергия

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]

максимална икономия на енергия

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]

четене на 100 x 10 регистри:

без пестене на енергия

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]

максимална икономия на енергия

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]

реално приложение:

Пример за зареждане за modbus-rpc мост (~3% е причинено от RPC сървърна част)

  • 5 x 64 регистрират синхронни четения за секунда и едновременно

  • асинхронен достъп с таймаут на сериен порт, зададен на 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} // може да се подобри; вижте раздела РЕДАКТИРАНЕ по-долу
    • pymodbus:

      • 10 regs: {'currentCpuUsage': 5.0, 'requestsPerSec': 36.88}
      • 64 регламента: {'currentCpuUsage': 5.0, 'requestsPerSec': 34.29}

РЕДАКТИРАНЕ: библиотеката modbus-tk може лесно да бъде подобрена, за да се намали използването на процесора. В оригиналната версия след изпращане на заявка и преминаване на T3.5 заспиване главният асемблира отговор един байт наведнъж. Профилирането доказа, че по-голямата част от времето се изразходва за достъп до сериен порт. Това може да се подобри, като се опитате да прочетете очакваната дължина на данните от серийния буфер. Според документацията на pySerial трябва да е безопасно (без увисване нагоре, когато отговорът липсва или е твърде кратък), ако е зададено време за изчакване:

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. 

след модифициране на `modbus_rtu.py' по следния начин:

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

След модификация на modbus-tk, натоварването на процесора в приложението в реалния живот спадна значително без значително увреждане на производителността (все още по-добро от pymodbus):

Актуализиран пример за натоварване за modbus-rpc мост (~3% е причинено от RPC сървърна част)

  • 5 x 64 регистрират синхронни четения за секунда и едновременно

  • асинхронен достъп с таймаут на сериен порт, зададен на 0,018 s

    • modbus-tk

      • 10 regs: {'currentCpuUsage': 7.8, 'requestsPerSec': 66.81}
      • 64 регламента: {'currentCpuUsage': 8.1, 'requestsPerSec': 37.61}
    • pymodbus:

      • 10 regs: {'currentCpuUsage': 5.0, 'requestsPerSec': 36.88}
      • 64 регламента: {'currentCpuUsage': 5.0, 'requestsPerSec': 34.29}
person Mr. Girgitt    schedule 30.01.2014
comment
Така че използвам stackoverflow повече от 5 години и това може би е най-добрият отговор, който някога съм срещал за него. Добре написано и обяснява много. Опитвах се да разбера защо получавам бавна пропускателна способност на данни чрез modbus, когато можех да получа 37k+ четене/запис на минута през прав сериен при 115220 бода. Благодаря и за кода за сравнителен анализ, не знаех за modbus-tk. Използвах pymodbus за известно време чрез TCP и работих страхотно и бързо, но тествах някои серийни неща и работех доста бавно, но това отговори на доста въпроси за мен, така че благодаря. - person xamox; 21.08.2014
comment
Благодаря за думите на признателност. Радвам се, че усилията ми са полезни и на някой друг. Между другото споменатата производителност (37k четене/запис/мин) изглежда нереалистична за стандарта RTU, който за baud › 19000 определя T3.5 при 1,75 ms, което е почти равно на времето, необходимо за целия комуникационен цикъл (37k четене/запис/мин ~= 617 цикъла в секунда ~= 1,62ms / цикъл). Използвахте ли Modbys binary, за да постигнете такъв резултат? - person Mr. Girgitt; 23.08.2014
comment
Съжалявам, трябваше да съм по-конкретен. Използвах pyserial, което имах предвид под прав сериен. Просто пишех времеви печат на една машина и четях от друга. Реших, че ще бъде по-бързо, но не толкова бързо, колкото беше. Тествах modbus както в ASCII, така и в RTU режим, но не видях особено подобрение. Също така тествах чрез pyserial, за да видя дали това може да е тясното място, тъй като споменах TCP в pymodbus, мога да работя много по-бързо. Тествах също синхронизиране срещу асинхронен сървър и забелязах само леко подобрение на асинхронния. - person xamox; 26.08.2014
comment
Във вашия tk пример tkmc.execute(slaveId, tkCst.READ_HOLDING_REGISTERS, 0,regsSp), как да прочетем конкретен холдинг регистър или няколко регистъра? И как да отпечатате отговора? - person mrid; 21.05.2018
comment
@mrid Последните два аргумента (... 0, regSp) указват диапазона на регистрите за четене: индекс на първия регистър за четене (0) и брой регистри за четене, започвайки от първия регистър (regSp - който е настроен на 10, така че повикването чете регистри 0-10). За подробности как да обработите отговора, вижте тестовете на модула: github.com/mushorg/modbus-tk/blob/master/tests/ - както можете да видите, методът execute връща кортеж, който може да бъде повторен и т.н., за да отпечата стойностите му. - person Mr. Girgitt; 23.05.2018
comment
Само за информация, предложеното подобрение е внедрено в modbus-tk. Благодаря за това предложение - person luc; 18.04.2019

Току-що открих uModbus и за внедряване в нещо като Raspberry PI (или друг малък SBC), това е мечта. Това е прост пакет с единична възможност, който не включва 10+ зависимости, както прави pymodbus.

person Travis Griggs    schedule 03.07.2018
comment
uModbus изглежда добре и чисто. Той чете серийния буфер за получаване ефективно (очакван брой байтове) и трябва да работи надеждно, ако си спомните да конфигурирате времето за изчакване и сами да управлявате наличността на сериен порт (напр. повторно свързване на USB-сериен адаптер по време на работа). Той също така декодира Modbus кодове за изключения. Това е всичко, което мога да кажа без дългосрочни тестове. - person Mr. Girgitt; 08.01.2021

Наистина зависи какво приложение използвате и какво се опитвате да постигнете.

pymodbus е много стабилна библиотека. Работи и ви дава много инструменти, с които да работите. Но може да се окаже малко смущаващо, когато се опитате да го използвате. Трудно ми беше да работя лично. Той ви предлага възможността да използвате както RTU, така и TCP/IP, което е страхотно!

MinimalModbus е много проста библиотека. В крайна сметка използвах това за моето приложение, защото направи точно това, което трябваше да направя. Прави само RTU комуникации и го прави добре, доколкото знам. Никога не съм имал проблеми с него.

Никога не съм разглеждал Modbus-tk, така че не знам къде стои.

В крайна сметка обаче зависи от това какво е вашето приложение. В крайна сметка открих, че Python не е най-добрият избор за мен.

person Windsplunts    schedule 13.06.2013