Troubleshooting a pymodbus Modbus TCP ExceptionResponse

Daniel Price2 min read
ModbusOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

The TCP connection to 10.98.237.80:502 succeeds, but the server rejects the read request. The subsequent Python AttributeError is a secondary error caused by treating a Modbus ExceptionResponse as a successful register response.

Decode the Modbus TCP exchange

Evidence Meaning
Connection to Modbus server established The client opened a TCP connection to the configured server and port.
Unit identifier 1, function 3, starting protocol address 60, quantity 2.
The server returned an exception for function 3. Setting the high bit changes to 0x83; the exception byte is 0xFC.
Factory Response[131] Decimal 131 equals 0x83, confirming an exception response to the holding-register read.

The evidence does not define the meaning of exception code 0xFC. Treat it as a server-reported code and check the controller's documentation or diagnostics instead of assigning it a standard meaning without evidence.

Prevent the Python AttributeError

A successful read exposes register data, but this request returned an ExceptionResponse, which has no registers attribute. Test the response before accessing that attribute.

from pymodbus.client.sync import ModbusTcpClient as ModbusClient

client = ModbusClient("10.98.237.80", port=502, auto_open=True)
client.connect()
rr = client.read_holding_registers(60, 2, unit=1)

if rr.isError():
    print(rr)
else:
    print(rr.registers)

This change exposes the Modbus failure without masking it behind a Python attribute-access error. It does not correct the server-side rejection.

Validate the address and requested range

The transmitted starting address is , or decimal 60, and the quantity is 2. Confirm that the server implements function 3 for both protocol addresses 60 and 61. If the controller documentation uses one-based register labels, translate those labels to zero-based protocol addresses according to that controller's mapping; do not silently change the call to address 59 unless the documented mapping requires it.

  1. Verify that the intended data resides in the holding-register table, not another Modbus data table.
  2. Verify that the complete two-register range beginning at protocol address 60 is defined and readable.
  3. Compare unit identifier 1 with the controller or gateway configuration. An IP address selects the TCP endpoint, but the trace confirms that the request still carries a unit identifier.
  4. Repeat the request and confirm that the response is no longer an exception before reading rr.registers.

Separate network success from application success

The established socket proves basic TCP reachability, not that the requested Modbus data is valid. The matching transaction identifier 1 and returned exception show that a Modbus endpoint received the request and deliberately answered it. Focus diagnosis on the requested function, address range, unit identifier, and the controller-specific meaning of 0xFC.

FAQ

Why does pymodbus say ExceptionResponse has no registers?

The server returned 0x83, an exception response to function 3, so the object contains no register list. Call rr.isError() before accessing rr.registers.

What address does read_holding_registers(60, 2) send?

The trace shows starting protocol address 60 () and quantity 2, covering addresses 60 and 61. Apply any one-based documentation offset only after confirming the controller's mapping.

Does a successful Modbus TCP connection prove the registers are readable?

No. The connection to port 502 succeeded, but the server returned exception function 0x83 with code 0xFC. Verify the function, full address range, unit identifier, and the controller's definition of that exception code.

Back to blog