Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

сохранение длинного рассказа:

MinimalModbus:

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

pymodbus:

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

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

Modbus-ки:

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

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

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

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

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

Настройка:

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

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

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

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

  • MinimalModbus: после первоначальных тестов упал
  • pymodbus: ~ 30 мс для чтения 64 регистров; эффективно до 30 запросов/сек
    • но ответы ненадежны (в случае синхронного доступа из нескольких потоков)
    • возможно, существует потоковая вилка на github, но она находится за мастером, и я не пробовал (https://github.com/xvart/pymodbus/network)
  • modbus-tk: ~ 16 мс для чтения 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 с

    • modbus-tk

      • 10 regs: {'currentCpuUsage': 20.6, 'requestPerSec': 73.2}// может быть улучшено; см. раздел EDIT ниже
      • 64 regs: {'currentCpuUsage': 31.2, 'requestPerSec': 41.91}// можно улучшить; см. раздел EDIT ниже
    • pymodbus:

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

EDIT: библиотека 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 с

    • modbus-tk

      • 10 regs: {'currentCpuUsage': 7.8, 'requestPerSec': 66.81}
      • 64 regs: {'currentCpuUsage': 8.1, 'requestPerSec': 37.61}
    • pymodbus:

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

Ответ 2

Это действительно зависит от того, какое приложение вы используете, и чего вы пытаетесь достичь.

pymodbus - очень надежная библиотека. Он работает, и он дает вам множество инструментов для работы. Но это может оказаться немного пугающим, когда вы пытаетесь его использовать. Мне было трудно работать с лично. Он предлагает вам возможность использовать как RTU, так и TCP/IP, что отлично!

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

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

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