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

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

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


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


Ответы (3)


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

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

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

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: ~ 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 x 64 регистра синхронных чтений в секунду и одновременных

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

    • 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 с

    • 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, когда я мог получить 37 тыс. Операций чтения / записи в минуту по прямому последовательному каналу со скоростью 115220 бод. Спасибо за тестовый код, я не знал о modbus-tk. Некоторое время я использовал pymodbus через TCP, и он работал отлично и быстро, но тестировал некоторые последовательные вещи и работал довольно медленно, но это ответило мне на довольно много вопросов, так что спасибо. - person xamox; 21.08.2014
comment
Спасибо за слово признательности. Я рад, что мои усилия были полезны кому-то другому. Кстати, упомянутая производительность (37 КБ чтения / записи / мин) выглядит нереалистичной для стандарта RTU, который для бод ›19000 указывает T3.5 на 1,75 мс, что почти равно времени, необходимому для всего цикла связи (37 КБ чтения / записи / мин ~ = 617 циклов в секунду ~ = 1,62 мс / цикл). Вы использовали бинарный файл Modbys для достижения такого результата? - 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