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)

On this page

Deploy your PyModbus test scripts with TofuPilot

Trusted by Lyft, Cobot, Pacific Defense, and more.

TofuPilot provides dashboards and analytics to track serial device testing at scale.

TofuPilot
Get Started