modbus-tk适用于Modbus RTU,可读/写多个寄存器(fn代码23),返回异常码1。

3
我正在使用modbus-tk通过Modbus RTU在RS-485网络上与设备进行串行通信。
我正在尝试弄清如何使用功能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错误:异常代码=1

我在Wikipedia上查找了此异常代码,并看到:

查询中接收到的功能代码未被从设备识别或允许

您认为这意味着我的设备确实不支持此功能代码?还是我存在语法问题/错误使用此功能?

我在下方放置了完整的脚本。


完整代码示例

输入

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

设备规格

  • 设备:SST Sensing的OXY-LC-485
  • Modbus RTU,9600/8-N-1
  • 用户指南(第7.1.2.1节包含一组输入寄存器)
  • 设备插入运行此Python脚本的Windows计算机中

软件包

我在Windows 10上使用Python 3.6。

pyserial==3.4
modbus-tk==1.1.0

1
你不是唯一一个在这方面遇到问题的人。 - Brits
4个回答

3
根据@maxy的回答,modbus规范指出异常代码1(非法功能)意味着:
查询中收到的功能码不是服务器(或从设备)允许的操作。这可能是因为功能码仅适用于较新的设备,并且未在所选单元中实现。它还可能表明服务器(或从机)处于处理此类请求的错误状态,例如因为它未配置并被要求返回寄存器值。
因此,在这种情况下,我想说该设备不支持此命令。
然而,考虑到另一个用户已经报告了该命令的问题,我认为检查编码值得一试。
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)

这个看起来对我来说是正确的,唯一的例外是,“写入起始地址”从哪里来不太清楚(并且可疑的是它与函数代码相同)。查看 源代码

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)。在提交 dcb0a2f115d7a9d63930c9b4466c4501039880a3 中更改了代码;之前的代码是:

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

这对我来说更有意义(需要一种传递地址以开始写入的方法,当前接口似乎没有提供此功能)。 我已经在github问题中添加了关于此的说明。

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


2
基于 @maxy 回答的严谨性和 @Brits 的回答,我决定进一步调查。目标是确定根本原因是 modbus-tk 的一个错误,还是我的设备不支持功能码 23。
modbus-tk Issue #121 中,OP 提到 pymodbus 使用功能码 23,读/写多个寄存器。
我安装了 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,即“非法功能”。因此,我认为该设备不支持功能码23。
如果我找到支持fn代码23的设备,我会回来再说。

2

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

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

考虑以下内容:
- 第二个字节是23,因此正确的功能码已被发送。 - 实际上,在传输过程中出现了“非法功能码”,这一定是设备故意生成的。您没有收到CRC错误或“非法地址”或“非法值”。 - 设备可能(但我认为可能性较小)支持代码23,但仅适用于某些地址。
唯一可能出错的地方在于库对实际请求的编码出错了。我没有检查其他字节,但正如Brits在评论中提到的,modbus-tk可能存在编码错误。可能是实现从设备决定对格式错误的请求响应“非法功能码”。
我认为他们只是没费心去实现这个功能码也是有可能的。例如,simplymodbus甚至没有列出它。

1
我有同样的问题,但我知道我的从机符合功能码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?读取引用是正确的。

我不确定 @Testor 所说的写入参考号是从哪里来的。也许在 Brits 的回答中链接的 Github 问题中发布您的观察结果会很有用。除了您的问题外,我有一个建议。为了避免使用 Wireshark,modbus-tk 具有集成的调试记录器。请随意查看原始问题中提供的代码示例以获取它的使用方法。 - Intrastellar Explorer
“看起来函数写入了错误的地址” - 这是正确的。modbus-tk似乎将功能码(23)用作“写入起始地址”(因此它发送了两次功能码)。这似乎是modbus-tk中的一个错误。 - Brits

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