Ошибка Modbus: [Недопустимое сообщение] Получено неполное сообщение, ожидается не менее 2 байтов (получено 0) - PullRequest
5 голосов
/ 07 июля 2019

Задача

Ведущий / клиент pymodbus может отправить запрос подчиненному / серверу. Ведомый / сервер готовит вещи для возврата и ждет, пока мастер / клиент их заберет. Несмотря на готовность сервера / ведомого, ведущий / клиент просто возвращает ошибку «Ошибка Modbus: [Ввод / вывод] Ошибка Modbus: [Неверное сообщение] Получено неполное сообщение, ожидается не менее 2 байтов (получено 0)».

Настройка

Я использую ноутбук в качестве сервера / ведомого устройства с этим адаптером: https://www.amazon.com/dp/B076WVFXN8/ref=twister_B076X1BS4H?_encoding=UTF8&psc=1

У меня Raspberry Pi 3 / BananaPi в качестве мастера / клиента с этим адаптером: https://www.aliexpress.com/item/32781613765.html?spm=a2g0s.9042311.0.0.1aec4c4d0EXx8M

Я следую большей части этого урока для установки, за исключением того, что Arduino поменялся местами с адаптером ноутбука: https://circuitdigest.com/microcontroller-projects/rs485-serial-communication-between-arduino-and-raspberry-pi - контактные соединения для Raspberry - как в учебнике.

У меня есть эта программа в качестве сервера / ведомого на моем ноутбуке:

#!/usr/bin/env python
from pymodbus.server.sync import StartTcpServer
from pymodbus.server.sync import StartUdpServer
from pymodbus.server.sync import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusBinaryFramer

import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s'
          ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

def run_server():

    slave_store1 = ModbusSlaveContext(co=ModbusSequentialDataBlock(0, [1]*16))
    slave_store2 = ModbusSlaveContext(di=ModbusSequentialDataBlock(0, [1]*16))
    slave_store3 = ModbusSlaveContext(ir=ModbusSequentialDataBlock(0, [5]*16))
    slave_store4 = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [5]*16))

    slaves = {
        0x01: slave_store1,
        0x02: slave_store2,
        0x03: slave_store3,
        0x04: slave_store4,
    }

    context = ModbusServerContext(slaves=slaves, single=False)

    identity = ModbusDeviceIdentification()
    identity.VendorName = 'Pymodbus'
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'http://github.com/riptideio/pymodbus/'
    identity.ProductName = 'Pymodbus Server'
    identity.ModelName = 'Pymodbus Server'
    identity.MajorMinorRevision = '2.2.0'

    # RTU:
    StartSerialServer(context, framer=ModbusRtuFramer, identity=identity, port='/dev/ttyUSB0', timeout=4, baudrate=115200, stopbits=1, bytesize=8, parity='N') 

if __name__ == "__main__":
    run_server()

Версия Python на сервере / ведомом устройстве:

$ python3 --version
Python 3.5.2

И я запускаю его с помощью этой команды:

$ python3 pymodbus_sync_serv_example_2019.07.05-1316.py

У меня в качестве мастера / клиента на Raspberry Pi 3 / BananaPi есть следующее:

#!/usr/bin/env python

import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s '
'%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

UNIT = 0x1

def run_sync_client():

    client = ModbusClient(method='rtu', port='/dev/ttyS2', timeout=4, baudrate=115200, stopbits=1, bytesize=8, parity='N')

    print(client)

    client.connect()

    log.debug("===================================")
    log.debug("Read input registers")
    log.debug("")
    rr = client.read_input_registers(1, 2, unit=3)
    print(rr)

    client.close()

if __name__ == "__main__":
    #for _ in range(10):
    run_sync_client()

Проверка и анализ

Я пробовал Raspberry Pi 3, а также BananaPi. Те же результаты.

Я пробовал скорость передачи данных = 9600, 38400, а теперь 115200.

Тайм-аут уже высок, как вы можете видеть в коде.

Журналы для сервера / ведомого:

2019-07-07 13:35:00,333 MainThread      DEBUG    sync           :45       Client Connected [/dev/ttyUSB0:/dev/ttyUSB0]
2019-07-07 13:35:00,333 MainThread      DEBUG    sync           :522      Started thread to serve client
2019-07-07 13:35:08,341 MainThread      DEBUG    rtu_framer     :180      Getting Frame - 0x4 0x0 0x1 0x0 0x2
2019-07-07 13:35:08,341 MainThread      DEBUG    factory        :137      Factory Request[ReadInputRegistersRequest: 4]
2019-07-07 13:35:08,341 MainThread      DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
2019-07-07 13:35:08,342 MainThread      DEBUG    context        :64       validate: fc-[4] address-2: count-2
2019-07-07 13:35:08,342 MainThread      DEBUG    context        :78       getValues fc-[4] address-2: count-2
2019-07-07 13:35:08,342 MainThread      DEBUG    sync           :143      send: [ReadRegisterResponse (2)]- b'030404000500050846'

Приведенный выше сервер / ведомый просто ждет миганием курсора после этой последней строки журнала ...

Журналы для мастера / клиента:

ModbusSerialClient(rtu baud[115200])
2019-07-07 13:35:04,428 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:165      ===================================
2019-07-07 13:35:04,429 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:166      Read input registers
2019-07-07 13:35:04,430 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:167      
2019-07-07 13:35:04,430 MainThread      DEBUG    transaction    :111      Current transaction state - IDLE
2019-07-07 13:35:04,430 MainThread      DEBUG    transaction    :116      Running transaction 1
2019-07-07 13:35:04,431 MainThread      DEBUG    transaction    :215      SEND: 0x3 0x4 0x0 0x1 0x0 0x2 0x21 0xe9
2019-07-07 13:35:04,431 MainThread      DEBUG    sync           :73       New Transaction state 'SENDING'
2019-07-07 13:35:04,432 MainThread      DEBUG    transaction    :224      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2019-07-07 13:35:08,439 MainThread      DEBUG    transaction    :234      Transaction failed. (Modbus Error: [Invalid Message] Incomplete message received, expected at least 2 bytes (0 received)) 
2019-07-07 13:35:08,440 MainThread      DEBUG    rtu_framer     :235      Frame - [b''] not ready
2019-07-07 13:35:08,441 MainThread      DEBUG    transaction    :390      Getting transaction 3
2019-07-07 13:35:08,442 MainThread      DEBUG    transaction    :189      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
Modbus Error: [Input/Output] Modbus Error: [Invalid Message] Incomplete message received, expected at least 2 bytes (0 received)

Версия Python для мастера / клиента:

$ python3 --version
Python 3.5.2

И я запускаю его с помощью этой команды:

$ python3 pymodbus_sync_client_example_2019.07.05-1319.py

Права на / dev для Raspberry / BananaPi:

$ ls -l /dev/ttyS*
crw--w---- 1 root tty     249, 0 Jul  7 11:21 /dev/ttyS0
crw-rw---- 1 root dialout 249, 1 Jul  7 11:22 /dev/ttyS1
crw-rw---- 1 root dialout 249, 2 Jul  7 13:35 /dev/ttyS2
crw-rw---- 1 root dialout 249, 3 Jul  7 11:20 /dev/ttyS3

А на сервере / раб на ноутбуке:

$ ls -l /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 0 Jul  7 13:35 /dev/ttyUSB0

Я пытался отправить простые числа с протоколом RS485. Их можно отправлять с master / Raspberry / BananaPi на ноутбук, но не наоборот.

Неправильные ли у меня настройки прав для устройств? ...

Что я делаю не так? ...

Чего мне не хватает? ...

Поскольку RS485 работает только в одном направлении, я не думаю, что проблема связана с pymodbus (?) ... (Моя логика говорит, что pymodbus строится в стандарте RS485, и если этот базовый уровень RS485 не работает, pymodbus не будет. Это предположение верно?)

Я знаю, что некоторые люди говорят о том, что Raspberry Pi имеет 3,3 В на контактах и ​​не работает с 5V контактами. Несмотря на это, все учебники игнорируют этот факт и работают. - Или они просто притворяются, что это работает? Спецификации TTL говорят, что все выше 2,5 В будут приняты как ВЫСОКИЕ. ТАК В ТЕОРИИ 3.3В должно быть в порядке, как подсказывают учебники.

По назначению я еще не прикрепил резисторы к проводам tx / rx для подъема / опускания. Учебники не предлагают их.

Я протестировал адаптер RS85 на ноутбуке с датчиком температуры и влажности Modbus. Кажется, это работает безупречно. Таким образом, этот факт указывает на то, что BananaPi / Raspberry Pi и комбинация адаптера RS485 + программное обеспечение + настройки должны быть каким-то образом ошибочными.

Ответы [ 2 ]

5 голосов
/ 07 июля 2019

Прежде всего, позвольте мне начать говорить, что мне приятно отвечать на такой хорошо поставленный вопрос.Не каждый прилагает столько усилий, чтобы объяснить, что они сделали и как они это сделали.Ваш вопрос - плюс один сразу после того, как вы его прочитаете.

Теперь с вашей проблемой.Вы пропустили один очень важный шаг в учебнике, которому вы следовали.Поскольку вы говорите, что Modbus полудуплексный, у вас есть только два провода, и только одно устройство может общаться по шине, поэтому вам нужен способ, так сказать, управлять шиной.В вашем кабеле USB-RS485 / 422 это выполняется автоматически аппаратным обеспечением кабеля (в частности, в вашем кабеле используется вездесущий чип FTDI с сигналом включения TXEN -TX, см. здесь для более подробной информации), поэтому вы заметили, что кабель работает хорошо.С другой стороны, ваш крошечный 3 $ трансивер - плохой брат, и у него даже нет UART, это всего лишь однополярный дифференциальный преобразователь.По этой причине вам нужно подать сигнал DE / ~ RE (Drive Enable / Not Read Enable), чтобы бедняга узнал, когда ему разрешено управлять шиной.

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

ВАЖНО : Перед записью значений в модуль RS-485 контакты DE & RE должны быть ВЫСОКИМИ.

Это кажется достаточно простым, но если вы думаете, как работает Modbus ... на самом деле это не так просто.Эта строка кода:

rr = client.read_input_registers(1, 2, unit=3)

должна делать множество вещей, если вы хотите успешно общаться с полудуплексом RS485: взять под контроль шину (в вашей настройке настройка сигнала RE / ~ DEhigh), отправьте фрейм запроса Modbus с двумя регистрами на UNIT ID 3, сразу после окончания написания запроса (по истечении 3,5 символов) освободите управление шиной (теперь устанавливая RE / ~ DE low) и прочитав ответ изslave.

Как я объясняю в ссылке 1021 *, о которой я уже упоминал выше, существует несколько решений этой проблемы.Мой предпочтительный (будучи большим количеством аппаратного парня) делает сигнал управления направлением шины аппаратно (лучший способ - иметь приемопередатчик, у которого эта функция реализована аппаратно, как этот , но вПо ссылке вы также найдете решение «сделай сам» с использованием таймера 555).Теперь, если вы предпочитаете делать это программным способом, у вас есть несколько вариантов.Вы можете настроить pymodbus для переключения линии управления в соответствии с потребностями Modbus (есть некоторые ссылки, включенные в приведенный мною ответ) или, если вы предпочитаете использовать более готовое решение libmodbus .

Если вы выберете этот последний вариант, вы можете найти все подробности о том, как собрать и установить lidmodbus с полудуплексной поддержкой с использованием выводов GPIO на Rpi и хотите ли вы остатьсяна Python установите обертку и протестируйте базовый пример.Есть также несколько скриншотов области видимости, чтобы видеть различие между переключением линии с помощью программного и аппаратного обеспечения.В большинстве домашних или любительских целей вы должны иметь возможность использовать программное переключение, но я бы не стал доверять его для промышленных или более важных приложений.

Чтобы закончить, я думаю, что стоит ответить на все ваши вопросы один за другим:

Поскольку RS485 работает только в одном направлении, я не думаю, что проблема в pymodbus (?) ... (Моя логика говорит, что pymodbus построен в стандарте RS485, и если этот базовый уровень RS485 не работает, pymodbus не будет. Это предположение верно?)

Ну, даи нет и, может быть ... Как вы читали выше, pymodbus на самом деле не проблема.Вы просто ожидаете, что вы или ваше оборудование позаботитесь о не столь незначительной детализации контроля над доступом к шине.Я думаю, что большинство людей используют такие библиотеки для Modbus TCP, поэтому для большинства пользователей это никогда не является проблемой.В общем сценарии Modbus, когда у вас есть ПЛК, взаимодействующий с другим устройством через Modbus RTU по каналу RS485, проблема решается аппаратно, поэтому вам не придется беспокоиться об этом.

Я знаю, что некоторые люди говорят о том, что Raspberry Pi 3,3 В на контакты и не работает с 5V контактами. Несмотря на это делает все учебники, кажется, игнорируют этот факт и работают. - Или они просто притворяются что это работает? Спецификации TTL говорят, что все выше 2,5 В будет принято как ВЫСОКОЕ. ТАК в теории, 3.3В должно быть в порядке, так же, как учебники предлагают.

Правильно, MAX485 datahseet определяет пороговые значения для VIH и VOL, и до тех пор, пока вы используете 5V для питания ваших трансиверов, различные логические уровни не будут проблемой (в этом обратите внимание, что в конкретном случае это не общее утверждение, другие устройства могут выйти из строя или в конечном итоге разрушиться, если вы смешаете логические уровни).

Я по назначению еще не прикрепил резисторы к проводам tx / rx. для тянуть вверх / вниз. Учебники не предлагают их.

Скорее всего, вам не нужно будет подключать какие-либо нагрузочные резисторы к шине для внутреннего проекта. Для длинных автобусов (на фабрике или в оборудовании, где устройства могут находиться на расстоянии сотен метров друг от друга), вы, вероятно, беспокоитесь об этой проблеме. Ваш крошечный приемопередатчик на самом деле имеет уже подключенные нагрузочные резисторы, поэтому лучше не добавлять больше сопротивления. Что касается вашего кабеля, мне не хватило терпения, чтобы найти руководство (я не знаю, есть ли оно; у меня есть аналогичный кабель, и единственный способ убедиться в этом - снять крышку и заглянуть под ее капот).

Как только у вас все заработало, обратите внимание, что на вашем клиенте:

print(rr)

Должно быть:

print(rr.registers)

Если вам нужно показать прочитанные вами значения.

2 голосов
/ 08 июля 2019

Как и предполагал Маркос Г., я модифицировал pymodbus для управления выбранным GPIO.

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

программное решение, которое модифицирует pymodbus

Найдите файл «sync.py» в папке «клиент», чтобы изменить сторону клиента / мастера в вашей настройке.

Я изменяю клиент / мастер здесь, так как у меня «плохое» оборудование RS485 на этой стороне. Если у вас есть две из этих «плохих» аппаратных вещей, вам может потребоваться изменить и сторону сервера.

Файл sync.py, вероятно, можно найти в

~ / .local / Библиотека / python3.5 / сайт-пакеты / pymodbus / клиент

Это может варьироваться в зависимости от используемой вами версии Python. Мой 3,5 сейчас. Часть «~ /» означает, что она находится в вашей домашней папке. Точка перед «local» делает файл скрытым как стандартный. В терминале вы можете использовать команду "ls -al", чтобы показать скрытые файлы тоже. Графический интерфейс вашего дистрибутива Linux наверняка сможет как-то показывать скрытые файлы.

В начале файла "sync.py" добавьте следующий код:

import RPi.GPIO as GPIO
pin_de_re = 7
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(pin_de_re, GPIO.OUT, initial=GPIO.HIGH)

Это может выглядеть примерно так:

more imports ...

from pymodbus.transaction import ModbusSocketFramer, ModbusBinaryFramer
from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer
from pymodbus.client.common import ModbusClientMixin

import RPi.GPIO as GPIO
pin_de_re = 7
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(pin_de_re, GPIO.OUT, initial=GPIO.HIGH)

# --------------------------------------------------------------------------- #
# Logging
# --------------------------------------------------------------------------- #
import logging
_logger = logging.getLogger(__name__)

...more code

Установите номер пин-кода по своему усмотрению. У меня есть контрольный контакт GPIO4 - это 7 в Raspberry Pi / BananaPi.

Далее прокрутите вниз и найдите раздел под названием

# --------------------------------------------------------------------------- #
# Modbus Serial Client Transport Implementation
# --------------------------------------------------------------------------- #

Я изменяю этот раздел, так как я использую Modbus RTU и, следовательно, последовательный для передачи данных.

В этом разделе вы должны найти определение «отправить»:

    def _send(self, request):
        """ Sends data on the underlying socket

Внутри этой функции найдите строку:

            size = self.socket.write(request)

И обними его с помощью булавки:

            _logger.debug("GPIO - Setting pin high")
            GPIO.output(pin_de_re, 1)
            time.sleep(.300)
            size = self.socket.write(request)
            time.sleep(.300)
            _logger.debug("GPIO - Setting pin low")
            GPIO.output(pin_de_re, 0)

Причина, по которой я использую строки «_logger.debug (« GPIO - Настройка пин-кода высокого / низкого уровня »)», заключается в том, что я могу видеть в журнале терминала, что программа выполняет эти вещи, и я могу быть уверен если они выполняются. Если они не отображаются в журнале, я сделал это в ложном месте - или что-то еще ...

Причина использования time.sleep (.300) состоит в том, чтобы у оборудования было время для действия. .300 составляет 0,3 секунды. Большое число в этом контексте.

Когда я использую вышеупомянутое решение, я получаю следующие журналы.

Ведомый / сервер:

2019-07-07 23:08:43,532 MainThread      DEBUG    sync           :45       Client Connected [/dev/ttyUSB0:/dev/ttyUSB0]
2019-07-07 23:08:43,533 MainThread      DEBUG    sync           :522      Started thread to serve client
2019-07-07 23:08:47,534 MainThread      DEBUG    rtu_framer     :232      Frame check failed, ignoring!!
2019-07-07 23:08:47,535 MainThread      DEBUG    rtu_framer     :128      Resetting frame - Current Frame in buffer - 0x3 0x4 0x0 0x1 0x0 0x82
2019-07-07 23:08:59,543 MainThread      DEBUG    rtu_framer     :180      Getting Frame - 0x4 0x0 0x1 0x0 0x2
2019-07-07 23:08:59,544 MainThread      DEBUG    factory        :137      Factory Request[ReadInputRegistersRequest: 4]
2019-07-07 23:08:59,544 MainThread      DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
2019-07-07 23:08:59,544 MainThread      DEBUG    context        :64       validate: fc-[4] address-2: count-2
2019-07-07 23:08:59,544 MainThread      DEBUG    context        :78       getValues fc-[4] address-2: count-2
2019-07-07 23:08:59,545 MainThread      DEBUG    sync           :143      send: [ReadRegisterResponse (2)]- b'030404000500050846'

Master / клиент:

ModbusSerialClient(rtu baud[115200])
2019-07-07 23:08:55,839 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:165      ===================================
2019-07-07 23:08:55,840 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:166      Read input registers
2019-07-07 23:08:55,841 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:167      
2019-07-07 23:08:55,842 MainThread      DEBUG    transaction    :111      Current transaction state - IDLE
2019-07-07 23:08:55,842 MainThread      DEBUG    transaction    :116      Running transaction 1
2019-07-07 23:08:55,843 MainThread      DEBUG    transaction    :215      SEND: 0x3 0x4 0x0 0x1 0x0 0x2 0x21 0xe9
2019-07-07 23:08:55,843 MainThread      DEBUG    sync           :79       New Transaction state 'SENDING'
2019-07-07 23:08:55,844 MainThread      DEBUG    sync           :538      GPIO - Setting pin high
2019-07-07 23:08:55,845 MainThread      DEBUG    sync           :541      GPIO - Setting pin low
2019-07-07 23:08:55,845 MainThread      DEBUG    transaction    :224      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2019-07-07 23:08:59,516 MainThread      DEBUG    transaction    :300      Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
2019-07-07 23:08:59,518 MainThread      DEBUG    transaction    :229      RECV: 0x3 0x4 0x4 0x0 0x5 0x0 0x5 0x8 0x46
2019-07-07 23:08:59,519 MainThread      DEBUG    rtu_framer     :180      Getting Frame - 0x4 0x4 0x0 0x5 0x0 0x5
2019-07-07 23:08:59,519 MainThread      DEBUG    factory        :266      Factory Response[ReadInputRegistersResponse: 4]
2019-07-07 23:08:59,520 MainThread      DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
2019-07-07 23:08:59,521 MainThread      DEBUG    transaction    :379      Adding transaction 3
2019-07-07 23:08:59,522 MainThread      DEBUG    transaction    :390      Getting transaction 3
2019-07-07 23:08:59,522 MainThread      DEBUG    transaction    :189      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
ReadRegisterResponse (2)

Передача МОЖЕТ БЫТЬ не всегда, но она точно определяет причину проблемы и, следовательно, возможные решения.

Я пока не знаю, чем я закончу. Более стабильное оборудование наверняка.

В связи с изменением pymodbus или другого программного обеспечения в связи с этой проблемой, я хотел бы процитировать из следующего поста в другой угрозе :

Любой, кто запускает Modbus в многозадачной ОС, такой как Linux или Windows, будет никогда не сможет соответствовать требованиям серийной спецификации, нет дебаты по этому вопросу, задачи, как правило, 10 мс, поэтому встреча 3.5us времени требование просто не соответствует и никогда не будет.

Решение на стороне аппаратного обеспечения является предпочтительным.

Благодаря Маркосу Г.

...