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

Я должен управлять устройством modbus с последовательным интерфейсом. У меня нет опыта работы с modbus. Но мое короткое исследование показало несколько библиотек modbus

  • pymodbus
  • MinimalModbus
  • Modbus-tk

каковы преимущества / недостатки, есть ли еще лучшие альтернативы?

3 ответов


примерно в то же время я столкнулся с той же проблемой - какую библиотеку выбрать для реализации Python modbus master, но в моем случае для последовательной связи (modbus RTU), поэтому мои наблюдения действительны только для modbus RTU.

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

держать длинную историю коротко:

MinimalModbus:

  • плюсы:
    • легкий модуль
    • представление может быть приемлемо для применений читая ~ 10 регистров
  • минусы:
    • неприемлемо (для моего приложения) медленно при чтении ~64 регистров
    • относительно высокая загрузка ЦП

pymodbus:

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

  • плюсы:
    • низкая загрузка процессора
    • приемлемой производительности
  • минусы:
    • даже когда тайм-аут динамически установлен, производительность на 2 x ниже по сравнению с modbus-tk; если тайм-аут остается при постоянном значении производительности хуже (но время запроса постоянно)
    • чувствительный к оборудованию (в результате зависимости от потока обработки от последовательного буфера я думаю) или может быть внутренняя проблема с транзакциями: вы можете получить ответы, смешанные, если выполняются разные чтения или чтения/записи ~20 раз в секунду или более. Более длительные тайм-ауты помогают, но не всегда делают реализацию PYMODBUS RTU по последовательной линии недостаточно надежной для использования в производстве.
    • добавление поддержки динамического последовательного настройка тайм-аута порта требует дополнительного программирования: наследование базового класса клиента синхронизации и реализация методов изменения тайм-аута сокета
    • проверка ответов не так подробно, как в modbus-tk. Например, в случае распада шины выдается только исключение, тогда как modbus-tk возвращает в той же ситуации неправильный ведомый адрес или ошибку CRC, которая помогает идентифицировать первопричину проблемы (которая может быть слишком коротким таймаутом, неправильным завершением шины / его отсутствием или плавающей землей так далее.)

modbus-tk:

отличительная черта: буфер зондов серийный для данных, собирает и возвращает ответ быстро.

  • плюсы
    • лучшая производительность; ~2 раза быстрее, чем pymodbus с динамическим таймаутом
  • минусы:
    • ок. 4 x более высокая загрузка процессора по сравнению с pymodbus // можно значительно улучшить, сделав этот пункт недействительным; см. раздел редактирования в конце
    • загрузка процессора увеличивается для больших запросов //можно значительно улучшить, сделав этот пункт недействительным; см. раздел редактирования в конце
    • код не такой элегантный, как pymodbus

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

для тех, кто интересуется деталями

моей целью было достичь минимального времени отклика.

настройка:

  • baudrate: 153600
    • в синхронизации с часами 16 МГц микроконтроллера, реализующего Modbus slave)
    • мой автобус rs-485 имеет только 50m
  • FTDI ft232r конвертер, а также последовательный через TCP-мост (используя com4com в качестве моста в RFC2217 mode)
  • в случае USB-последовательного преобразователя самые низкие таймауты и размеры буфера, настроенные для последовательного порта (для снижения латентности)
  • адаптер auto-tx rs-485 (шина имеет доминирующее состояние)

сценарий использования:

  • опрос 5, 8 или 10 раз в секунду с поддержкой асинхронного доступа в между
  • запросы на чтение / запись от 10 до 70 регистров

Типичный долгосрочный (недели) производительность:

  • MinimalModbus: упал после начальных испытаний
  • pymodbus: ~30ms для чтения 64 регистров; эффективно до 30 запросов / сек
    • но ответы ненадежны (в случае синхронизированного доступа из нескольких потоков)
    • есть, возможно, threadsafe вилка на github, но она находится за мастером, и я не пробовал (https://github.com/xvart/pymodbus/network)
  • modbus-tk: ~16ms для чтения 64 регистров; эффективно до 70 - 80 запросов / сек для небольших запросов

benchmark

код:

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}//можно улучшить; см. раздел редактирования ниже
      • 64 regs: {'currentCpuUsage': 31.2, 'requestsPerSec': 41.91}//можно улучшить; см. раздел редактирования ниже
    • pymodbus:

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

EDIT: библиотеку modbus-tk можно легко улучшить для уменьшения использования CPU. В оригинальной версии после запроса отправляется и T3.5 sleep passed master собирает ответ по одному байту за раз. Профилирование доказано, большинство времени тратится на доступ к последовательному порту. Это можно улучшить, попытавшись прочитать ожидаемую длину данных из последовательный буфер. Согласно документация 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 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-очень надежная библиотека. Это работает, и это дает вам много инструментов для работы. Но он может оказаться немного пугающим, когда вы пытаетесь использовать его. Мне было трудно работать с ним лично. Он предлагает вам возможность использовать как RTU, так и TCP / IP, что здорово!

MinimalModbus-очень простая библиотека. Я закончил тем, что использовал это для своего приложения, потому что это именно то, что мне нужно. Он только делает RTU communications, и он делает это хорошо, насколько я знаю. У меня никогда не было с этим проблем.

Я никогда не смотрел в Modbus-tk, поэтому я не знаю, где он стоит.

в конечном счете, это зависит от того, что ваше приложение. В конце концов я обнаружил, что python не был лучшим выбором для меня.


Я только что обнаружил uModbus, и для развертывания в чем-то вроде Raspberry PI (или другого небольшого SBC), это мечта. Это простой одиночный способный пакет, который не приносит зависимостей 10+, как pymodbus.