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| Parameter | Type | Default | Description |
|---|---|---|---|
address | int | required | Starting register address (0-based) |
count | int | 1 | Number of registers to read (max 125) |
slave | int | 1 | Slave/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 # Signed32-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 signedString
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 NoneRTU 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 FailureWrong 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
- Read contiguous blocks instead of individual registers.
- Keep the connection open across multiple reads.
- 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)