3

我正在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上查找了这个异常代码,然后看到:

查询中收到的功能码不被从机识别或允许

你认为这意味着我的设备真的不支持这个功能码吗?还是我有语法问题/我误用了这个函数?

我把我的完整脚本放在下面。


完整代码示例

输入

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

4 回答 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 中的错误,我认为它也不会起作用。

于 2020-02-02T00:14:41.760 回答
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甚至没有列出它。

于 2020-02-01T22:28:12.800 回答
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 Illegal Function,。所以我认为这个设备不支持功能码23。

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

于 2020-02-04T01:20:31.033 回答
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

为什么写参考号,应该是我们写的地址,和我们读的一样,是23而不是0?读取参考是好的。

于 2020-02-05T10:03:05.100 回答