用于 Modbus RTU 的 modbus-tk,read/write 多个寄存器(fn 代码 23),returns 异常代码 1

modbus-tk for Modbus RTU, read/write multiple registers (fn code 23), returns exception code 1

我正在使用 modbus-tk 通过 RS-485 网络通过 Modbus RTU 与设备进行串行通信。

我正在尝试弄清楚如何使用函数 23,READ_WRITE_MULTIPLE_REGISTERS。这是我第一次使用函数 23。这是我当前的实现:

response = modbus_master.execute(
    slave=SLAVE_NUM,
    function_code=cst.READ_WRITE_MULTIPLE_REGISTERS,
    starting_address=2,
    quantity_of_x=1,
    output_value=[1],
)

运行执行此命令时,出现以下错误:Modbus Error: Exception code = 1

我在 Wikipedia 上查找了这个异常代码,然后看到:

Function code received in the query is not recognized or allowed by slave

你觉得我的设备真的不支持这个功能码吗?还是我的语法 problem/am 误用了这个函数?

我已将完整的脚本放在下面。


完整代码示例

输入

#!/usr/bin/env python3


import time
from collections import namedtuple
from logging import Logger

from serial import Serial
from modbus_tk.modbus_rtu import RtuMaster
import modbus_tk.defines as cst  # cst = constants
from modbus_tk.utils import create_logger


PORT = "COM3"
SLAVE_NUM = 1
MODBUS_MASTER_TIMEOUT_SEC = 5.0

ModbusHoldingReg = namedtuple(
    "ModbusHoldingRegister", ["name", "address", "last_read_value", "to_write_value"]
)
shutdown_delay = ModbusHoldingReg("shutdown delay", 2, 0, None)  # sec

logger = create_logger(name="console")  # type: Logger

serial_ = Serial(PORT)
modbus_master = RtuMaster(serial_)
modbus_master.set_timeout(MODBUS_MASTER_TIMEOUT_SEC)
modbus_master.set_verbose(True)
# Sleep some time per [1]
# [1]: https://github.com/ljean/modbus-tk/issues/73#issuecomment-284800980
time.sleep(2.0)

# Read/write from/to multiple registers
response = modbus_master.execute(
    slave=SLAVE_NUM,
    function_code=cst.READ_WRITE_MULTIPLE_REGISTERS,
    starting_address=shutdown_delay.address,
    quantity_of_x=1,
    output_value=[1],
)  # type: tuple
print(response)

输出

2020-01-31 10:43:24,885 INFO    modbus_rtu.__init__     MainThread      RtuMaster COM3 is opened
2020-01-31 10:43:26,890 DEBUG   modbus.execute  MainThread      -> 1-23-0-2-0-1-0-23-0-1-2-0-1-55-131
2020-01-31 10:43:31,933 DEBUG   modbus.execute  MainThread      <- 1-151-1-143-240
---------------------------------------------------------------------------
ModbusError                               Traceback (most recent call last)
<ipython-input-1-f42d200d6c09> in <module>
     37     starting_address=shutdown_delay.address,
     38     quantity_of_x=1,
---> 39     output_value=[1],
     40 )  # type: tuple
     41 print(response)

c:\path\to\venv\lib\site-packages\modbus_tk\utils.py in new(*args, **kwargs)
     37             ret = fcn(*args, **kwargs)
     38         except Exception as excpt:
---> 39             raise excpt
     40         finally:
     41             if threadsafe:

c:\path\to\venv\lib\site-packages\modbus_tk\utils.py in new(*args, **kwargs)
     35             lock.acquire()
     36         try:
---> 37             ret = fcn(*args, **kwargs)
     38         except Exception as excpt:
     39             raise excpt

c:\path\to\venv\lib\site-packages\modbus_tk\modbus.py in execute(self, slave, function_code, starting_address, quantity_of_x, output_value, data_format, expected_length)
    312                 # the slave has returned an error
    313                 exception_code = byte_2
--> 314                 raise ModbusError(exception_code)
    315             else:
    316                 if is_read_function:

ModbusError: Modbus Error: Exception code = 1

设备规格

我在 Windows 10 上使用 Python 3.6。

pyserial==3.4
modbus-tk==1.1.0

您的调试输出包含以下跟踪信息:

-> 1-23-0-2-0-1-0-23-0-1-2-0-1-55-131
<- 1-151-1-143-240

考虑以下几点:

  • 第二个字节是23,所以发送了正确的功能代码。
  • 你实际上在线路上得到了一个 "illegal function code",这一定是设备故意生成的。您没有收到 CRC 错误或 "illegal address" 或 "illegal value".
  • 设备有可能(但我猜不太可能)支持代码 23,但仅适用于某些地址。

方面唯一可能出错的地方是库搞砸了实际请求的编码。我没有检查其他字节,但由于 ,modbus-tk 中可能存在编码错误。有可能实施奴隶的人决定用 "illegal function code" 来响应格式错误的请求。

在我看来,他们只是懒得去实现这个功能代码,这似乎也是有道理的。例如,simplymodbus 甚至没有列出它。

进一步了解@maxy的回答; modbus spec 指出异常代码 1(非法功能)表示:

The function code received in the query is not an allowable action for the server (or slave). This may be because the function code is only applicable to newer devices, and was not implemented in the unit selected. It could also indicate that the server (or slave) is in the wrong state to process a request of this type, for example because it is unconfigured and is being asked to return register values.

因此,在这种情况下,我会说设备不支持此命令。

然而,鉴于另一个用户报告了此命令的问题,我认为值得检查编码:

1- Slave ID
23- Function Code
0, 2- Read Starting Address
0, 1- Quantity to Read
0, 23- Write Starting Address
0, 1 - Quantity to write
2, Write Byte Count
0,1, - Write Registers value
55,131 - CRC (have not checked)

除了一个例外,这在我看来是正确的;目前尚不清楚 "Write Starting Address" 来自哪里(并且怀疑它与功能代码相同)。查看 source:

pdu = struct.pack(
    ">BHHHHB",
    function_code, starting_address, quantity_of_x, defines.READ_WRITE_MULTIPLE_REGISTERS,
    len(output_value), byte_count
)

我觉得这不对(defines.READ_WRITE_MULTIPLE_REGISTERS 永远是 23)。 commit dcb0a2f115d7a9d63930c9b4466c4501039880a3; 中的代码被更改为这个;以前是:

pdu = struct.pack(
    ">BHHHHB",
    function_code, starting_address, quantity_of_x, starting_addressW_FC23,
    len(output_value), byte_count
)

这对我来说更有意义(你需要一种方法来传入地址来开始写入,而目前的接口似乎没有提供这个)。我已经在 github issue.

中添加了注释

所以总而言之,您的问题可能是由设备引起的,但即使设备支持该命令,我也不认为它会起作用,因为 modbus-tk 中存在错误。

基于@maxy 的严谨回答,然后是@Brits 的回答,我决定进一步调查。目标是确定根本原因是 modbus-tk 错误,还是我的设备不支持功能代码 23。

modbus-tk Issue #121 中,OP 提到 pymodbus 使用函数代码 23,read/write 多个寄存器。


所以我安装了 pymodbus==2.3.0,然后试了一下。这是我使用的代码:

输入

#!/usr/bin/env python3


import sys
import logging
from collections import namedtuple

from pymodbus.pdu import ModbusResponse, ExceptionResponse
from pymodbus.client.sync import ModbusSerialClient
from pymodbus.register_read_message import ReadWriteMultipleRegistersResponse


log = logging.getLogger()
log.addHandler(logging.StreamHandler(sys.stdout))
log.setLevel(logging.DEBUG)


ModbusHoldingReg = namedtuple(
    "ModbusHoldingRegister", ["name", "address", "last_read_value", "to_write_value"]
)

sensor_mode = ModbusHoldingReg("sensor on, off, and standby enum", 0, None, None)


PORT = "COM3"
SLAVE_NUM = 1
BAUD_RATE = 9600


with ModbusSerialClient(
    method="rtu", port=PORT, baudrate=BAUD_RATE, strict=False
) as modbus_client:
    regs_to_write = [0, 1, 3]
    response = modbus_client.readwrite_registers(
        read_address=sensor_mode.address,
        read_count=len(regs_to_write),
        write_address=sensor_mode.address,
        write_registers=regs_to_write,
        unit=SLAVE_NUM,
    )  # type: ModbusResponse

    if response.isError():
        response: ExceptionResponse
        print(
            f"Exception!  Original function code = {response.original_code}, "
            f"exception_code = {response.exception_code}."
        )
    else:
        response: ReadWriteMultipleRegistersResponse
        print(f"Success!  response.registers = {response.registers}.")

输出

Current transaction state - IDLE
Running transaction 1
SEND: 0x1 0x17 0x0 0x0 0x0 0x3 0x0 0x0 0x0 0x3 0x6 0x0 0x0 0x0 0x1 0x0 0x3 0x5d 0xce
New Transaction state 'SENDING'
Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
RECV: 0x1 0x97 0x1 0x8f 0xf0
Getting Frame - 0x97 0x1
Factory Response[151]
Frame advanced, resetting header!!
Adding transaction 1
Getting transaction 1
Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
Original function code = 23, exception code = 1.

结论

可以看到设备响应异常代码 1,Illegal Function。所以我相信这个设备不支持功能代码 23.

如果我找到支持 fn 代码 23 的设备,我会回头。

我有同样的问题,但我知道我的奴隶符合功能代码 23,它是 wago 750-362。 我可以读取数据,但函数似乎写入了错误的地址。我没有功能代码错误。

这是我发送的指令:

inputExt = master.execute(1, cst.READ_WRITE_MULTIPLE_REGISTERS, 0, 5, output_value=[32767,32767,32767,32767,0x00ff])

这是我用 wireshark 捕获看到的:

Modbus/TCP
    Transaction Identifier: 35394
    Protocol Identifier: 0
    Length: 21
    Unit Identifier: 1
Modbus
    .001 0111 = Function Code: Read Write Register (23)
    Read Reference Number: 0
    Read Word Count: 5
    Write Reference Number: 23
    Write Word Count: 5
    Byte Count: 10
    Data: 7fff7fff7fff7fff00ff

为什么Write reference Number本应是我们写入的地址和读取的地址是23而不是0?读取引用正常