PyModbus
PyModbusDocs

Reading Registers

Read Modbus holding registers with PyModbus. Function signatures, data conversion, batch reading, and troubleshooting common issues.

Reading Registers with PyModbus

read_holding_registers (function code 0x03) is the most common Modbus operation. It reads 16-bit read/write registers that store configuration, setpoints, and sensor values.

Holding registers use addresses 40001-49999 in Modbus notation, but PyModbus uses 0-based addressing (0-9998).

Basic Read

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()

result = client.read_holding_registers(address=0, count=10, slave=1)

if not result.isError():
    print(f"Register values: {result.registers}")
else:
    print(f"Error: {result}")

client.close()

Log Modbus readings automatically

TofuPilot records test results from your PyModbus scripts, tracks pass/fail rates, and generates compliance reports. Free to start.

Function Signature

read_holding_registers(
    address: int,       # Starting register address (0-based)
    count: int = 1,     # Number of registers to read (1-125)
    slave: int = 1,     # Slave/Unit ID
    **kwargs
) -> ModbusResponse
ParameterTypeDefaultDescription
addressintrequiredStarting register address (0-based)
countint1Number of registers to read (max 125)
slaveint1Slave/Unit ID of the target device

Data Conversion

Registers are 16-bit unsigned integers. To read floats, 32-bit ints, or strings, use BinaryPayloadDecoder.

16-bit Integer

result = client.read_holding_registers(address=0, count=1)
if not result.isError():
    value = result.registers[0]  # Unsigned (0-65535)
    signed = value if value < 32768 else value - 65536  # Signed

32-bit Float

from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian

result = client.read_holding_registers(address=0, count=2)
if not result.isError():
    decoder = BinaryPayloadDecoder.fromRegisters(
        result.registers,
        byteorder=Endian.BIG,
        wordorder=Endian.BIG
    )
    value = decoder.decode_32bit_float()

32-bit Integer

result = client.read_holding_registers(address=0, count=2)
if not result.isError():
    decoder = BinaryPayloadDecoder.fromRegisters(
        result.registers,
        byteorder=Endian.BIG,
        wordorder=Endian.BIG
    )
    value = decoder.decode_32bit_uint()  # or decode_32bit_int() for signed

String

result = client.read_holding_registers(address=0, count=8)  # 16 characters
if not result.isError():
    decoder = BinaryPayloadDecoder.fromRegisters(
        result.registers,
        byteorder=Endian.BIG
    )
    text = decoder.decode_string(16).decode('ascii').strip('\x00')

Batch Reading

Read multiple parameters in fewer round trips by grouping contiguous registers.

from pymodbus.client import ModbusTcpClient
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian

def read_device_data(client):
    readings = [
        ('voltage', 0, 2),
        ('current', 2, 2),
        ('power', 4, 2),
        ('frequency', 6, 1),
        ('status', 10, 1),
    ]

    data = {}
    for name, address, count in readings:
        result = client.read_holding_registers(address=address, count=count, slave=1)
        if not result.isError():
            if count == 1:
                data[name] = result.registers[0]
            else:
                decoder = BinaryPayloadDecoder.fromRegisters(
                    result.registers,
                    byteorder=Endian.BIG,
                    wordorder=Endian.BIG
                )
                data[name] = decoder.decode_32bit_float()
    return data

client = ModbusTcpClient('192.168.1.100')
client.connect()
print(read_device_data(client))
client.close()

Read with Retry

import time
from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ModbusException

def read_with_retry(client, address, count, max_retries=3):
    for attempt in range(max_retries):
        try:
            result = client.read_holding_registers(address=address, count=count, slave=1)
            if not result.isError():
                return result.registers
            print(f"Modbus error on attempt {attempt + 1}: {result}")
        except ModbusException as e:
            print(f"Exception on attempt {attempt + 1}: {e}")

        if attempt < max_retries - 1:
            time.sleep(0.5)

    return None

RTU vs TCP

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()
result = client.read_holding_registers(address=0, count=10, slave=1)
client.close()
from pymodbus.client import ModbusSerialClient

client = ModbusSerialClient(
    port='/dev/ttyUSB0',
    baudrate=9600,
    parity='N',
    stopbits=1,
    bytesize=8
)
client.connect()
result = client.read_holding_registers(address=0, count=10, slave=1)
client.close()
from pymodbus.client import ModbusTcpClient
from pymodbus.framer import ModbusRtuFramer

client = ModbusTcpClient(
    '192.168.1.100',
    port=502,
    framer=ModbusRtuFramer
)
client.connect()
result = client.read_holding_registers(address=0, count=10, slave=1)
client.close()

Troubleshooting

None or Empty Response

if not client.connect():
    print("Failed to connect")

result = client.read_holding_registers(address=0, count=1, slave=1)

if result.isError():
    print(f"Exception code: {result.exception_code}")
    # 1 = Illegal Function
    # 2 = Illegal Data Address
    # 3 = Illegal Data Value
    # 4 = Slave Device Failure

Wrong Values (Byte Order)

from pymodbus.constants import Endian

for byte_order in [Endian.BIG, Endian.LITTLE]:
    for word_order in [Endian.BIG, Endian.LITTLE]:
        decoder = BinaryPayloadDecoder.fromRegisters(
            result.registers,
            byteorder=byte_order,
            wordorder=word_order
        )
        value = decoder.decode_32bit_float()
        print(f"Byte: {byte_order}, Word: {word_order}, Value: {value}")

Address Offset

# Device docs say register 40001
# PyModbus uses 0-based addressing
modbus_address = 40001
pymodbus_address = modbus_address - 40001  # = 0

result = client.read_holding_registers(address=pymodbus_address, count=1)

Performance Tips

  1. Read contiguous blocks instead of individual registers.
  2. Keep the connection open across multiple reads.
  3. Use async when polling multiple devices.
# Single request for 10 registers (fast)
result = client.read_holding_registers(address=0, count=10)

# 10 separate requests (slow)
for i in range(10):
    result = client.read_holding_registers(address=i, count=1)