PyModbus read_holding_registers - Complete Guide with Examples
Master PyModbus read_holding_registers with practical examples. Learn to read Modbus holding registers, handle responses, and troubleshoot common issues.
Reading Holding Registers with PyModbus
Master the most common Modbus operation - reading holding registers. This guide covers everything from basic usage to advanced techniques with real-world examples.
What are Holding Registers?
Holding registers are 16-bit read/write registers in Modbus devices (function code 0x03). They typically store:
- Configuration parameters
- Setpoints
- Sensor values
- Status information
Register Addressing: Holding registers use addresses 40001-49999 in Modbus notation, but PyModbus uses 0-based addressing (0-9998).
Basic Usage
Synchronous Client
from pymodbus.client import ModbusTcpClient
# Connect to Modbus TCP device
client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()
# Read 10 holding registers starting at address 0
result = client.read_holding_registers(address=0, count=10, slave=1)
if not result.isError():
registers = result.registers
print(f"Register values: {registers}")
else:
print(f"Error: {result}")
client.close()
Asynchronous Client
import asyncio
from pymodbus.client import AsyncModbusTcpClient
async def read_registers():
async with AsyncModbusTcpClient('192.168.1.100') as client:
result = await client.read_holding_registers(
address=0,
count=10,
slave=1
)
if not result.isError():
return result.registers
return None
# Run async function
registers = asyncio.run(read_registers())
print(f"Registers: {registers}")
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 # Additional parameters
) -> ModbusResponse
Real-World Examples
Reading Temperature from Sensor
from pymodbus.client import ModbusTcpClient
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian
def read_temperature(client, address=100):
"""Read temperature value from Modbus sensor."""
# Many sensors store float values across 2 registers
result = client.read_holding_registers(address=address, count=2)
if not result.isError():
# Decode the registers as a 32-bit float
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
temperature = decoder.decode_32bit_float()
return round(temperature, 2)
return None
# Usage
client = ModbusTcpClient('192.168.1.100')
client.connect()
temp = read_temperature(client)
print(f"Temperature: {temp}°C")
client.close()
Reading Multiple Values Efficiently
def read_device_data(client):
"""Read multiple parameters from a Modbus device."""
data = {}
# Read different register blocks
readings = [
('voltage', 0, 2), # Address 0, 2 registers
('current', 2, 2), # Address 2, 2 registers
('power', 4, 2), # Address 4, 2 registers
('frequency', 6, 1), # Address 6, 1 register
('status', 10, 1), # Address 10, 1 register
]
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:
# Convert two registers to float
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
data[name] = decoder.decode_32bit_float()
return data
Reading with Error Handling
import time
from pymodbus.exceptions import ModbusException
def read_with_retry(client, address, count, max_retries=3):
"""Read registers with automatic retry on failure."""
for attempt in range(max_retries):
try:
result = client.read_holding_registers(
address=address,
count=count,
slave=1
)
if result.isError():
print(f"Modbus error on attempt {attempt + 1}: {result}")
if attempt < max_retries - 1:
time.sleep(0.5) # Wait before retry
continue
else:
return result.registers
except ModbusException as e:
print(f"Exception on attempt {attempt + 1}: {e}")
if attempt < max_retries - 1:
time.sleep(0.5)
continue
return None # Failed after all retries
Working with Different Data Types
Integer Values (16-bit)
# Read single 16-bit integer
result = client.read_holding_registers(address=0, count=1)
if not result.isError():
value = result.registers[0] # Direct 16-bit value
signed_value = value if value < 32768 else value - 65536 # Convert to signed
Long Integers (32-bit)
# Read 32-bit integer from 2 registers
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
Float Values
# Read 32-bit float from 2 registers
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()
String Values
# Read string from multiple registers
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')
RTU vs TCP Examples
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', # or 'COM3' on Windows
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()
Common Issues and Solutions
Issue: None or Empty Response
# Check connection first
if not client.connect():
print("Failed to connect")
# Verify slave ID
result = client.read_holding_registers(
address=0,
count=1,
slave=1 # Make sure this matches your device
)
# Check for Modbus exception
if result.isError():
print(f"Modbus exception: {result.exception_code}")
# 1 = Illegal Function
# 2 = Illegal Data Address
# 3 = Illegal Data Value
# 4 = Slave Device Failure
Issue: Wrong Values
# Try different byte/word order
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}")
Issue: Address Offset
# Some devices use Modbus notation (40001+)
# PyModbus uses 0-based addressing
modbus_address = 40001 # Device documentation
pymodbus_address = modbus_address - 40001 # Convert to 0-based
result = client.read_holding_registers(
address=pymodbus_address,
count=1
)
Performance Tips
-
Read Multiple Registers at Once
# Good - Single request result = client.read_holding_registers(address=0, count=10) # Bad - Multiple requests for i in range(10): result = client.read_holding_registers(address=i, count=1)
-
Use Connection Pooling
# Keep connection open for multiple reads with ModbusTcpClient('192.168.1.100') as client: for _ in range(100): result = client.read_holding_registers(0, 10) # Process result time.sleep(1)
-
Async for Multiple Devices
async def read_all_devices(devices): tasks = [] for ip in devices: async with AsyncModbusTcpClient(ip) as client: task = client.read_holding_registers(0, 10) tasks.append(task) results = await asyncio.gather(*tasks) return results
Complete Example: Energy Meter Reader
#!/usr/bin/env python3
"""Read energy meter data using PyModbus."""
from pymodbus.client import ModbusTcpClient
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian
import time
class EnergyMeter:
def __init__(self, host, port=502, slave=1):
self.client = ModbusTcpClient(host, port=port)
self.slave = slave
def connect(self):
return self.client.connect()
def disconnect(self):
self.client.close()
def read_voltage(self):
"""Read voltage from registers 0-1 (32-bit float)."""
result = self.client.read_holding_registers(
address=0,
count=2,
slave=self.slave
)
if not result.isError():
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
return decoder.decode_32bit_float()
return None
def read_current(self):
"""Read current from registers 2-3 (32-bit float)."""
result = self.client.read_holding_registers(
address=2,
count=2,
slave=self.slave
)
if not result.isError():
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
return decoder.decode_32bit_float()
return None
def read_power(self):
"""Read power from registers 4-5 (32-bit float)."""
result = self.client.read_holding_registers(
address=4,
count=2,
slave=self.slave
)
if not result.isError():
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
return decoder.decode_32bit_float()
return None
def read_all(self):
"""Read all meter values."""
return {
'voltage': self.read_voltage(),
'current': self.read_current(),
'power': self.read_power(),
'timestamp': time.time()
}
# Usage
if __name__ == "__main__":
meter = EnergyMeter('192.168.1.100')
if meter.connect():
print("Connected to energy meter")
# Continuous monitoring
while True:
data = meter.read_all()
print(f"Voltage: {data['voltage']:.2f} V")
print(f"Current: {data['current']:.2f} A")
print(f"Power: {data['power']:.2f} W")
print("-" * 30)
time.sleep(5)
else:
print("Failed to connect to meter")
Next Steps
- Writing to Registers - Learn to write data to Modbus devices
- Async Operations - Build high-performance async clients
- Error Handling - Handle Modbus exceptions properly
How is this guide?