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

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

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

4 ответа

Решение

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

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

Короче говоря:

MinimalModbus:

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

pymodbus:

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

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

тк-Modbus:

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

  • профи
    • лучшее представление; ~ В 2 раза быстрее, чем pymodbus с динамическим тайм-аутом
  • минусы:
    • ок. 4-х кратная загрузка процессора по сравнению с pymodbus // может быть значительно улучшена, сделав эту точку недействительной; см. раздел РЕДАКТИРОВАТЬ в конце
    • Нагрузка на ЦП для больших запросов // может быть значительно улучшена, что делает эту точку недействительной; см. раздел РЕДАКТИРОВАТЬ в конце
    • код не такой элегантный, как pymodbus

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

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

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

настроить:

  • скорость передачи: 153600
    • синхронно с тактовой частотой 16 МГц микроконтроллера, реализующего ведомое устройство Modbus)
    • мой автобус RS-485 имеет только 50 м
  • Конвертер FTDI FT232R, а также последовательный мост через TCP (используя com4com в качестве моста в режиме RFC2217)
  • в случае преобразования 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 х 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 х 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 х 64 регистров синхронного чтения в секунду и одновременного

  • асинхронный доступ с тайм-аутом последовательного порта, установленным на 0,018 с

    • Modbus-тк

      • 10 regs: {'currentCpuUsage': 20.6, 'quesPerSec': 73.2} // могут быть улучшены; см раздел РЕДАКТИРОВАТЬ ниже
      • 64 regs: {'currentCpuUsage': 31.2, 'playsPerSec': 41.91} // могут быть улучшены; см раздел РЕДАКТИРОВАТЬ ниже
    • pymodbus:

      • 10 правил: {'currentCpuUsage': 5.0, 'playsPerSec': 36.88}
      • 64 regs: {'currentCpuUsage': 5.0, 'playsPerSec': 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 х 64 регистров синхронного чтения в секунду и одновременного

  • асинхронный доступ с тайм-аутом последовательного порта, установленным на 0,018 с

    • Modbus-тк

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

      • 10 правил: {'currentCpuUsage': 5.0, 'playsPerSec': 36.88}
      • 64 regs: {'currentCpuUsage': 5.0, 'playsPerSec': 34.29}

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

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

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

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

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

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

Я провел несколько тестов с использованием Python 2.7.18 и Python 3.8.10. Похоже, pymodbus оптимизирован для Python3.

      $ python2 test.py 
timeout: 0.018 [s]
umodbus:        time to read 1 x 1000 (x 16 regs): 53.261 [s] / 0.053 [s/req]
pymodbus:       time to read 1 x 1000 (x 16 regs): 65.648 [s] / 0.066 [s/req]
modbus-tk:      time to read 1 x 1000 (x 16 regs): 60.191 [s] / 0.060 [s/req]

$ python3 test.py 
timeout: 0.018 [s]
umodbus:        time to read 1 x 1000 (x 16 regs): 53.246 [s] / 0.053 [s/req]
pymodbus:       time to read 1 x 1000 (x 16 regs): 32.765 [s] / 0.033 [s/req]
modbus-tk:      time to read 1 x 1000 (x 16 regs): 60.352 [s] / 0.060 [s/req]
Другие вопросы по тегам