Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am using modbus-tk to serially communicate with a device via Modbus RTU over a RS-485 network.

I am trying to figure out how to use function 23, READ_WRITE_MULTIPLE_REGISTERS. This is my first time using function 23. Here is my current implementation:

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

While running this command, I get the following error: Modbus Error: Exception code = 1

I looked up this exception code on Wikipedia, and see:

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

Do you think this means my device truly does not support this function code? Or do I have a syntax problem/am mis-using this function?

I have placed my full script below.


Full Code Example

Input

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

Output

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

Device Specifics

  • Device: SST Sensing's OXY-LC-485
  • Modbus RTU, 9600/8-N-1
  • User Guide (section 7.1.2.1 contains set of input registers)
  • Device is plugged into Windows machine that I run this Python script

Packages

I am using Python 3.6 on Windows 10.

pyserial==3.4
modbus-tk==1.1.0
like image 640
Intrastellar Explorer Avatar asked Jun 06 '26 17:06

Intrastellar Explorer


2 Answers

Further to the answer from @maxy; the modbus spec states that exception code 1 (ILLEGAL FUNCTION) means:

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.

So, in this case, I'd say that the device does not support this command.

However given that another user has reported an issue with this command I thought it was worth checking the encoding:

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)

This looks correct to me with one exception; it's not clear where the "Write Starting Address" comes from (and suspicious that it's the same as the function code). Looking at the source:

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

This looks wrong to me (defines.READ_WRITE_MULTIPLE_REGISTERS will always be 23). The code was changed to this in commit dcb0a2f115d7a9d63930c9b4466c4501039880a3; previously it was:

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

This makes more sense to me (you need a way to pass in the address to start writing and the current interface does not seem to provide this). I have added a note re this to the github issue.

So in conclusion your issue is probably due to the device but even if the device supported the command I don't think it would work due to a bug in modbus-tk.

like image 79
Brits Avatar answered Jun 08 '26 07:06

Brits


Your debug output helpfully contains the following trace:

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

Consider the following:

  • The second byte is 23, so the correct function code was sent.
  • You actually got an "illegal function code" on the wire, which must have been generated by the device on purpose. You don't get a CRC error or "illegal address" or "illegal value".
  • It's possible (but I guess somewhat unlikely) that the device supports code 23, but only for some addresses.

The only thing left that could have been wrong on your side is that the library botched the encoding of the actual request. I haven't checked the other bytes, but as commented by Brits, there might be a bug in modbus-tk with the encoding. It's possible that the person implementing the slave decided to respond with "illegal function code" to a malformed request.

It seems also plausible to me that they just didn't bother to implement this function code. For example, simplymodbus doesn't even list it.

like image 45
maxy Avatar answered Jun 08 '26 06:06

maxy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!