Modbus错误:[无效消息] 收到不完整的消息,期望至少2个字节(收到0个)。

11

问题

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

我在树莓派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()

测试和分析

我已经尝试了树莓派3和香蕉派。结果相同。

我已经尝试了波特率为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文件夹的权限如下:

$ 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协议发送简单的数字。它们可以从主控/Raspberry/BananaPi发送到笔记本电脑,但反过来就不行。

我设置了错误的设备权限吗?...

我做错了什么?...

我错过了什么?...

由于RS485只能在单向上工作,我不认为pymodbus是问题所在(?)...(我的逻辑认为pymodbus内置了RS485标准,如果RS485底层不工作,pymodbus也不会。这个假设正确吗?)

我知道有些人谈论树莓派在引脚上是3.3V,并且不能与5V引脚单元一起使用。尽管如此,所有教程似乎都忽略了这一点并正常工作。-或者他们只是在假装它工作吗? TTL规格表明,高于2.5V的全部接受为HIGH。所以理论上,3.3V应该没问题,就像教程中建议的那样。

我故意没有在tx/rx线上附加任何电阻器进行上/下拉。教程中也没有建议。

我已经用一个modbus温湿度传感器在笔记本电脑上测试了RS85适配器。这似乎运行无误。因此,这一事实指向BananaPi/Raspberry Pi和RS485适配器组合+软件+设置出现了某些问题。

2个回答

13
首先,很高兴回答这样一个问题。并不是每个人都会花费这么多的精力来解释他们做了什么以及如何做到的。在你读完问题之后,你的问题值得加一分。
现在说说你的问题。你在遵循教程时错过了一个非常重要的步骤。就像你所说的Modbus是半双工1,你只有两根线,而且只允许一个设备在总线上通信,所以你需要一种方式来控制总线。在你的USB转RS485/422电缆中,由电缆上的硬件自动完成了这项工作(特别是你的电缆使用了无处不在的FTDI芯片,该芯片具有TXEN -TX enable-信号,请参见here以获取更多详细信息),这就是你注意到该电缆工作正常的原因。另一方面,你的微型3美元收发器则比较简单,并没有UART,只是一个单端到差分转换器。这就是你需要为可怜的家伙提供DE /〜RE(驱动启用/未读启用)信号的原因,以便它知道何时可以控制总线。
这是你从教程中没有注意到的警告:

IMPORTANT:在向RS-485模块写入值之前,必须将引脚DE和RE置为高电平。

这看起来很简单,但如果你考虑Modbus的工作原理……实际上并不那么容易。这行代码:

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

如果要成功地与RS485半双工通信,您需要做很多事情:控制总线(在设置中将RE /〜DE信号设置为高),发送Modbus查询帧以请求UNIT ID 3上的两个寄存器,在编写查询后立即释放总线控制权(在3.5个字符时间之后,现在将RE /〜DE设置为低),并读取从从设备返回的答案。正如我在上面提到的link中所解释的那样,这个问题有几个解决方案。我的首选方法(更多的是硬件方面)是通过硬件来控制总线方向控制信号(最好的方法是使用具有此功能的收发器来实现硬件,例如this one,但在链接中您也会找到使用555计时器的DIY解决方案)。现在,如果您更喜欢使用软件方式,则有一些选择。您可以调整pymodbus以根据Modbus需求切换控制线(在我引用的答案中包含了一些链接),或者,如果您更喜欢一个更开箱即用的解决方案,请使用libmodbus
如果你选择最后一种选项,你可以找到所有关于如何使用Rpi的GPIO引脚构建和安装支持半双工的lidmodbus的详细信息,如果你想使用Python,安装包装器并测试基本示例。还有一些示波器截图,以便看到通过软件与硬件切换线之间的差异。对于大多数内部或业余爱好者用途,你应该能够使用软件切换,但我不会相信它用于工业或更关键的应用。
为了结束,我认为逐个回答你所有的问题是值得的:
“由于RS485只能单向工作,我不认为pymodbus是问题所在(?)...(我的逻辑是pymodbus内置了RS485标准,如果RS485的底层不起作用,pymodbus也不会工作。这个假设是正确的吗?)”
嗯,是和否,也许...正如你上面读到的那样,pymodbus并不是真正的问题所在。它只是期望你或你的硬件来处理控制谁访问总线的这个不太重要的细节。我认为大多数人使用这种库来进行Modbus TCP通信,因此这对大多数用户来说从来不是问题。在一般的Modbus场景中,你有一个PLC通过RS485链路与另一台设备进行Modbus RTU通信,问题是由硬件处理的,因此你也不必担心它。
我知道有些人在谈论树莓派的引脚是3.3V,不支持5V引脚单元。尽管如此,所有教程都似乎忽略了这一事实并正常工作。或者他们只是假装它能工作?TTL规范说明所有高于2.5V的电平都将被接受为高电平。因此,理论上,3.3V应该可以,就像教程所建议的那样。
正确,MAX485 datasheet指定了VIH和VOL的阈值值,只要您为收发器的电源使用5V,不同的逻辑电平就不会成为问题(在这种特殊情况下,请注意这不是一般性的陈述,如果混合逻辑电平可能会导致其他设备失败或损坏)。
我故意没有在tx / rx线路上附加任何拉起/下拉电阻。教程也没有建议这样做。对于一个内部项目,您很可能不需要在总线上附加任何终止电阻。对于长总线(在工厂或设施中,设备之间可能相距数百米),您可能会担心这个问题。您的微型收发器实际上已经包含了这些终止电阻,因此最好不要增加更多电阻。对于您的电缆,我没有耐心找到手册(我不知道是否有手册;我有一个类似的电缆,唯一确定的方法是拆下盖子并查看其内部)。
一旦您拥有了所有运行的内容,请注意在客户端上:
print(rr)

Should be:

print(rr.registers)

如果您想展示您所读取的数值。

很棒的回答。深入的解释和认真的建议提供了一个解决方案。- 我现在选择通过修改pymodbus来采用软件解决方案,以获得一些快速的结果。它有效了。我一定会考虑你的硬件建议。 - Andreas
你说 Modbus 是半双工协议...但据我所知这不是协议的一部分,对吗?据我所知,Modbus 是一种串行协议,无论它运行在 RS-232、RS-485、半双工还是全双工上,都没有区别,因为它只定义了数据包如何携带信息和验证。 - Michel Feinstein
是的,你说得对,“Modbus是半双工”的说法可能有点难以理解。实际上,Modbus并不关心总线上使用的传输模式,但如果你在全双工链路上建立一个Modbus网络,它将始终以半双工模式运行。正如我在其他地方提到的那样,用RS232(三线,RX、TX和GND)替换两线RS485(实际上是三根线:A、B和GND),可以解决我们非常熟悉的总线方向控制信号缺失问题(如果可能的话:短总线)。 - Marcos G.

3

正如Marcos G.所建议的那样,我修改了pymodbus以控制所选GPIO。

我选择了软件解决方案,因为我现在只需要一些快速可用的东西,而不需要订购新硬件并等待。稍后我会找到一个合适/更好的硬件。

修改pymodbus的软件解决方案

在文件夹“client”中找到文件“sync.py”,以修改设置的客户端/主机端。

我在此处修改了客户端/主机端,因为我在那一侧有“差劲”的RS485硬件。如果您有两个这些“差劲”的硬件设备,则可能还需要修改服务器端。

文件sync.py可能可以在以下位置找到

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

这可能因您使用的Python版本而异。目前我的版本是3.5。"~/"部分表示该文件位于您的主文件夹中。在终端中,您可以使用"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 - 在Raspberry Pi / BananaPi中为引脚7。

接下来,向下滚动并找到名为的部分

# --------------------------------------------------------------------------- #
# 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秒,这在这个上下文中是一个很大的数字。

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

从机/服务器:

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'

主控/客户端:

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或其他软件来解决这个问题,我想引用另一个帖子中的以下内容

在像Linux或Windows这样的多任务操作系统上运行Modbus的任何人都将无法满足串行规范的要求,这是毫无争议的,任务通常为10ms,因此无法满足3.5us的时间要求。

硬件方面的解决方案更可取。
感谢Marcos G。

很高兴你成功接近一个可行的解决方案,Andreas。也许你可以通过减少延迟来提高解决方案的可靠性。对于Modbus,你需要等待3.5个字符时间,也就是在你使用的波特率(115.2kbps)下大约是0.3毫秒(我认为你在原帖中引用的3.5微秒是笔误)。如果你将等待时间缩短到0.5毫秒,一切应该能够相当可靠地工作。至少这是我在玩pymodbus时得到的结果。 - Marcos G.
如果您让pyserial以阻塞方式工作(端口在总线上写入所有数据之前不会返回;这应该在Rpi上是可能的,但对于大多数USB适配器来说并不是一个选项),甚至可以完全省略延迟。如果您查看我上面链接的原始帖子,您可以看到使用libmodbus获取的范围捕获,从最后一个边缘到总线释放之间有500-600微秒的延迟。这就是为什么我修改了我的FTDI电缆以使用TXEN信号的原因。 - Marcos G.
但是,正如你所说,在这种情况下,硬件解决方案会更可靠。这个软件技巧对我们来说足够好,可以在家里玩,但任何专业的Modbus实现都应该使用硬件。或者可能使用硬实时操作系统。 - Marcos G.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接