Modbus Error: [Invalid Message] Incomplete message received, expected at least 2 bytes (0 received)

Modbus Error: [Invalid Message] Incomplete message received, expected at least 2 bytes (0 received)

问题

pymodbus master/client 可以向 slave/server 发送请求。 slave/server 准备好要 return 的东西,正在等待 master/client 来取。尽管 server/slave 已准备就绪,但 master/client 只是 return 错误 "Modbus Error: [Input/Output] Modbus Error: [Invalid Message] Incomplete message received, expected at least 2 bytes (0 received)"。

设置

我将笔记本电脑作为 server/slave 使用此适配器:https://www.amazon.com/dp/B076WVFXN8/ref=twister_B076X1BS4H?_encoding=UTF8&psc=1

我有一个 Raspberry Pi 3 / BananaPi 作为 master/client 连接了这个适配器: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 的引脚连接与教程中的一样。

我的笔记本电脑上有这个程序 server/slave:

#!/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()

server/slave 上的 python 版本是:

$ python3 --version
Python 3.5.2

我用这个命令启动它:

$ python3 pymodbus_sync_serv_example_2019.07.05-1316.py

我在 Raspberry Pi 3 / BananaPi 上有如下 master/client:

#!/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。相同的结果。

我试过 baudrate= 9600、38400,现在是 115200。

正如您在代码中所见,超时已经很高。

server/slave 的日志:

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'

上面的 server/slave 只是在最后一行日志之后用闪烁的光标等待...

master/client 的日志:

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)

master/client 上的 python 版本是:

$ python3 --version
Python 3.5.2

我用这个命令启动它:

$ python3 pymodbus_sync_client_example_2019.07.05-1319.py

Raspberry/BananaPi 上 /dev 的权限是:

$ 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

并且在 server/slave 笔记本电脑上:

$ 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.3V,不适用于 5V 引脚单元。尽管如此,所有教程似乎都忽略了这一事实和工作。 - 或者他们只是假装它有效? TTL 规范表示所有高于 2.5V 的电压都将被视为高电平。所以在理论上,3.3V 应该没问题,正如教程所建议的那样。

我故意没有在 tx/rx 导线上连接任何电阻以拉动 up/down。教程不建议它们。

我用modbus温湿度传感器测试了笔记本电脑上的RS85适配器。这似乎完美无缺。所以这个事实表明 BananaPi/Raspberry Pi 和 RS485 适配器组合 + 软件 + 设置存在某种缺陷。

首先,让我开始说很高兴回答这样一个很好的问题。并不是每个人都花那么多精力来解释他们做了什么以及他们是如何做的。你的是看完后的加一题

现在解决你的问题。您错过了您遵循的教程中的一个非常重要的步骤。正如您所说的 Modbus 是 half-duplex, you have only two wires and only one device is allowed to talk on the bus so you need a way to take control of the bus, so to speak. In your USB-to-RS485/422 cable, that is done automatically for you by the hardware on the cable (your cable, in particular, uses the ubiquitous FTDI chip that has a TXEN -TX enable- signal, see here 以获得更多详细信息),这就是您注意到电缆运行良好的原因。另一方面,你的 3 美元微型收发器是可怜的兄弟,它甚至没有 UART,它只是一个 single-ended 到差分转换器。这就是您需要为可怜的家伙提供 DE/~RE(驱动器 Enable/Not 读取启用)信号以了解何时允许控制总线的原因。

这是您没有从教程中获取的警告:

IMPORTANT: Before writing values to the RS-485 module the pins DE & RE must be made HIGH.

这似乎很容易,但如果您考虑 Modbus 的工作原理……实际上并不那么容易。这行代码:

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

如果您要与 RS485 half-duplex 成功通信,应该做很多事情:控制总线(在您的设置中将 RE/~DE 信号设置为高电平),发送 Modbus 查询在 UNIT ID 3 上请求两个寄存器的帧,在完成查询(3.5 个字符时间后)后立即释放总线控制(现在将 RE/~DE 设置为低电平)并从从机读取答案。

正如我在 link I already referred to above, there are several solutions to this problem. My preferred one (being more of a hardware guy) is doing the bus direction control signal by hardware (the best way is to have a transceiver that has this function implemented by hardware, like this one 中解释的那样,但在 link 中,您还会找到使用 555 计时器的 DIY 解决方案)。现在,如果您更喜欢以软件方式进行操作,您有一些选择。您可以调整 pymodbus 以根据 Modbus 需求切换控制线(我引用的答案中包含一些 links)或者,如果您更喜欢out-of-the-box 解决方案使用 libmodbus

如果您决定选择最后一个选项,您可以找到有关如何使用 Rpi 上的 GPIO 引脚构建和安装 lidmodbus 并提供 half-duplex 支持的所有详细信息,以及如果您想留在 Python,请安装包装器并测试基本示例。还有几个示波器屏幕截图可以看出通过软件与硬件切换线路之间的区别。对于大多数 in-house 或业余爱好者来说,您应该能够使用软件切换,但我不相信它会用于工业或更关键的应用程序。

最后,我认为值得一一回答您的所有问题:

As RS485 only works in the one way, I do not think that pymodbus is the problem (?)... (My logic says that pymodbus builds in the RS485 standard, and if that underlying layer of RS485 does not work, pymodbus will not. Is that assumption correct?)

好吧,是的,不是,也许......正如你在上面读到的,pymodbus 并不是真正的问题。它只是希望您或您的硬件能够处理控制谁访问总线的不太重要的细节。我认为大多数人都将这种库用于 Modbus TCP,因此这对大多数用户来说从来都不是问题。在一般的 Modbus 场景中,你有一个 PLC 通过 RS485 link 上的 Modbus RTU 与另一个设备通信,这个问题由硬件处理,所以你也不必担心它。

I know some people are talking about that the Raspberry Pi is 3.3V on the pins and does not work with 5V pin-units. Despite that does all tutorials seem to ignore that fact and work. - Or are they just faking that it works? The TTL specifications say that all above 2.5V will be accepted as HIGH. SO in THEORY, 3.3V should be OK, just as the tutorials suggest.

正确,MAX485 datahseet 指定了 VIH 和 VOL 的阈值,只要您使用 5V 作为收发器的电源,不同的逻辑电平就不会成为问题(在此特殊情况,请注意,这不是一般性陈述,如果混合逻辑级别,其他设备可能会发生故障或最终被破坏。

I have by purpose yet not attached any resistors on the tx/rx wires for pull up/down. The tutorials don't suggest them.

对于 in-house 项目,您很可能不需要将任何终端电阻连接到总线。对于长途总线(在设备可能相距数百米的工厂或设施中),您可能会担心这个问题。你的微型收发器实际上已经包含了这些终端电阻,所以最好不要增加更多电阻。对于您的电缆,我没有足够的耐心找到手册(我不知道是否有;我有一根类似的电缆,唯一可以确定的方法是取下盖子并查看其引擎盖)。

一切准备就绪后,运行 请注意在您的客户端上:

print(rr)

应该是:

print(rr.registers)

如果你想要的是显示你读过的值。

正如上面 Marcos G. 所建议的,我是否修改了 pymodbus 来控制所选的 GPIO。

我选择了软件解决方案,因为我现在只需要一些快速工作的东西,而无需订购新硬件并等待它。稍后我会找到一个suitable/better硬件。

修改pymodbus的软件解决方案

在文件夹 "client" 中找到文件 "sync.py",以修改设置的 client/master 端。

我在这里修改了client/master,因为我在那边有'poor' RS485 硬件。如果你有两个这样的 'poor' 硬件,你可能也需要修改服务器端。

文件 sync.py 可能在

中找到

~/.local/lib/python3.5/site-packages/pymodbus/client

这可能会因您使用的 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 Pi/BananaPi。

接下来向下滚动并找到名为

的部分
# --------------------------------------------------------------------------- #
# Modbus Serial Client Transport Implementation
# --------------------------------------------------------------------------- #

我修改了这部分,因为我使用 Modbus RTU 并因此使用串行传输数据。

在该部分中,您必须找到 "send":

的定义
    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 - Setting pin high/low")'行的原因是我可以在终端的日志中看到程序执行了这些事情,我可以放心如果他们被执行。如果他们没有出现在日志中,我已经在错误的地方完成了 - 或者其他......

之所以使用time.sleep(.300)是为了让硬件有时间行动。 .300 是 0.3 秒。在这种情况下是一个很大的数字。

当我使用上述解决方案时,我得到以下日志。

Slave/server:

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/client:

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 或其他软件,我想引用 from the following post in another threat:

Anyone running modbus on a multitasking OS such as linux or windows will never be able to meet the requirements of the serial spec, there is no debate on this, tasking is normally 10ms so meeting 3.5us timing requirement just doesn't fit and never will.

最好是硬件方面的解决方案。

感谢 Marcos G.