pythonmodbusmodbus-tk

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

Packages

I am using Python 3.6 on Windows 10.

pyserial==3.4
modbus-tk==1.1.0

Solution

  • 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.