modbus RTU的modbus tk,读取/写入多个寄存器(fn代码23),返回异常代码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

我在维基百科上查找了这个异常代码,看到:

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

您认为这是否意味着我的设备真的不支持此功能代码?或者我有语法问题/我错误地使用了这个函数

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


完整代码示例

输入

#!/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:pathtovenvlibsite-packagesmodbus_tkutils.py in new(*args, **kwargs)
37             ret = fcn(*args, **kwargs)
38         except Exception as excpt:
---> 39             raise excpt
40         finally:
41             if threadsafe:
c:pathtovenvlibsite-packagesmodbus_tkutils.py in new(*args, **kwargs)
35             lock.acquire()
36         try:
---> 37             ret = fcn(*args, **kwargs)
38         except Exception as excpt:
39             raise excpt
c:pathtovenvlibsite-packagesmodbus_tkmodbus.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传感的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

进一步了解@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中的错误,我认为它也不会工作。

您的调试输出包含以下跟踪,非常有用:

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

考虑以下内容:

  • 第二个字节是23,因此发送了正确的函数代码
  • 实际上,你在电线上得到了一个"非法函数代码",它一定是设备故意生成的。您不会得到CRC错误或"非法地址"或"非法值">
  • 该设备支持代码23是可能的(但我想有点不太可能),但仅适用于某些地址

您的方面,唯一可能出错的是库对实际请求的编码搞砸了。我还没有检查其他字节,但正如英国人所评论的,modbus-tk中可能存在编码错误。实现slave的人可能决定用"非法函数代码"来响应格式错误的请求。

对我来说,他们只是没有费心实现这个函数代码,这似乎也是合理的。例如,simplymodbus甚至没有列出它

基于@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.

结论

可以看出,该设备以异常代码1Illegal 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

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