Data Types
Convert between Modbus registers and Python data types with PyModbus. Integers, floats, strings, BCD, bit manipulation, and endianness handling.
Data Types in PyModbus
Modbus only knows 16-bit registers and single bits. You need BinaryPayloadDecoder and BinaryPayloadBuilder to work with real data types.
16-bit Integer (Single Register)
# Unsigned (0-65535)
result = client.read_holding_registers(100, 1)
value = result.registers[0]
# Signed (-32768 to 32767)
if value > 32767:
signed_value = value - 65536
else:
signed_value = value
# Or use struct
import struct
bytes_val = struct.pack('>H', value)
signed = struct.unpack('>h', bytes_val)[0]Log register data automatically
TofuPilot records test results from your PyModbus scripts, tracks pass/fail rates, and generates compliance reports. Free to start.
32-bit Integer (Two Registers)
from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder
from pymodbus.constants import Endian
# Read
result = client.read_holding_registers(100, 2)
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
value = decoder.decode_32bit_int() # or decode_32bit_uint()
# Write
builder = BinaryPayloadBuilder(
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
builder.add_32bit_int(-123456)
client.write_registers(100, builder.to_registers())Float (32-bit IEEE 754)
from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder
from pymodbus.constants import Endian
# Read
result = client.read_holding_registers(100, 2)
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
float_value = decoder.decode_32bit_float()
# Write
builder = BinaryPayloadBuilder(
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
builder.add_32bit_float(3.14159)
client.write_registers(100, builder.to_registers())Double (64-bit Float)
# Read (4 registers)
result = client.read_holding_registers(100, 4)
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
double_value = decoder.decode_64bit_float()
# Write
builder = BinaryPayloadBuilder(
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
builder.add_64bit_float(3.141592653589793)
client.write_registers(100, builder.to_registers())Strings
# Read (8 registers = 16 characters)
result = client.read_holding_registers(100, 8)
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG
)
string_value = decoder.decode_string(16).decode('ascii').strip('\x00')
# Write
builder = BinaryPayloadBuilder(byteorder=Endian.BIG)
text = "HELLO WORLD".ljust(16, '\x00')
builder.add_string(text)
client.write_registers(100, builder.to_registers())Bits (From Registers)
result = client.read_holding_registers(100, 1)
value = result.registers[0]
# Extract individual bits
bit_0 = bool(value & 0x0001)
bit_1 = bool(value & 0x0002)
bit_7 = bool(value & 0x0080)
bit_15 = bool(value & 0x8000)
# Extract all 16 bits
bits = [(value >> i) & 1 for i in range(16)]
print(f"Bits: {bits}")
# Set specific bits
value = 0
value |= (1 << 0) # Set bit 0
value |= (1 << 5) # Set bit 5
value |= (1 << 15) # Set bit 15
client.write_register(100, value)Endianness (Byte Order)
Different devices use different byte orders. When values look wrong, try all four combinations.
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian
def try_all_endianness(registers):
combinations = [
(Endian.BIG, Endian.BIG, "Big-Big"),
(Endian.BIG, Endian.LITTLE, "Big-Little"),
(Endian.LITTLE, Endian.BIG, "Little-Big"),
(Endian.LITTLE, Endian.LITTLE, "Little-Little"),
]
for byte_order, word_order, name in combinations:
decoder = BinaryPayloadDecoder.fromRegisters(
registers,
byteorder=byte_order,
wordorder=word_order
)
value = decoder.decode_32bit_float()
print(f"{name}: {value}")
result = client.read_holding_registers(100, 2)
try_all_endianness(result.registers)Scaled Values
Many devices use scaled integers instead of floats. Check the device manual for the scaling factor.
# Temperature scaled by 10 (23.5C = 235)
result = client.read_holding_registers(100, 1)
temperature = result.registers[0] / 10.0
# Pressure scaled by 100 (1.23 bar = 123)
result = client.read_holding_registers(101, 1)
pressure = result.registers[0] / 100.0
# Write scaled value
temp_scaled = int(23.5 * 10) # 235
client.write_register(100, temp_scaled)BCD (Binary Coded Decimal)
Some devices (especially older meters and PLCs) use BCD encoding.
def bcd_to_int(bcd_value):
result = 0
multiplier = 1
while bcd_value > 0:
digit = bcd_value & 0x0F
result += digit * multiplier
multiplier *= 10
bcd_value >>= 4
return result
def int_to_bcd(value):
result = 0
shift = 0
while value > 0:
digit = value % 10
result |= (digit << shift)
shift += 4
value //= 10
return result
# Read BCD value
result = client.read_holding_registers(100, 1)
bcd = result.registers[0]
value = bcd_to_int(bcd)
print(f"BCD {bcd:04X} = {value}")
# Write BCD value
bcd = int_to_bcd(1234)
client.write_register(100, bcd)Custom Data Structures
For devices with a fixed register layout, a dataclass maps cleanly to register blocks.
from dataclasses import dataclass
from typing import List
from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder
from pymodbus.constants import Endian
@dataclass
class MotorData:
speed: float # RPM
current: float # Amps
temperature: float # Celsius
status: int # Status bits
@classmethod
def from_registers(cls, registers: List[int]):
decoder = BinaryPayloadDecoder.fromRegisters(
registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
return cls(
speed=decoder.decode_32bit_float(),
current=decoder.decode_32bit_float(),
temperature=decoder.decode_32bit_float(),
status=decoder.decode_16bit_uint()
)
def to_registers(self) -> List[int]:
builder = BinaryPayloadBuilder(
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
builder.add_32bit_float(self.speed)
builder.add_32bit_float(self.current)
builder.add_32bit_float(self.temperature)
builder.add_16bit_uint(self.status)
return builder.to_registers()
# Read motor data (7 registers: 3 floats + 1 uint16)
result = client.read_holding_registers(100, 7)
motor = MotorData.from_registers(result.registers)
print(f"Speed={motor.speed} RPM, Current={motor.current} A")
# Write motor data
motor = MotorData(speed=1500.0, current=2.5, temperature=45.0, status=1)
client.write_registers(100, motor.to_registers())Time and Date
from datetime import datetime
def read_datetime(client, address):
"""Read date/time from 3 registers in BCD format."""
result = client.read_holding_registers(address, 3)
year = bcd_to_int(result.registers[0]) + 2000
month = bcd_to_int(result.registers[1] >> 8)
day = bcd_to_int(result.registers[1] & 0xFF)
hour = bcd_to_int(result.registers[2] >> 8)
minute = bcd_to_int(result.registers[2] & 0xFF)
return datetime(year, month, day, hour, minute)
def write_datetime(client, address, dt):
"""Write date/time to 3 registers in BCD format."""
registers = [
int_to_bcd(dt.year - 2000),
(int_to_bcd(dt.month) << 8) | int_to_bcd(dt.day),
(int_to_bcd(dt.hour) << 8) | int_to_bcd(dt.minute)
]
client.write_registers(address, registers)
dt = read_datetime(client, 100)
print(f"Device time: {dt}")
write_datetime(client, 100, datetime.now())Float Arrays
from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder
from pymodbus.constants import Endian
def read_float_array(client, address, count):
result = client.read_holding_registers(address, count * 2)
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
return [decoder.decode_32bit_float() for _ in range(count)]
def write_float_array(client, address, values):
builder = BinaryPayloadBuilder(
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
for v in values:
builder.add_32bit_float(v)
client.write_registers(address, builder.to_registers())
temperatures = read_float_array(client, 100, 10)
write_float_array(client, 200, [20.0, 21.5, 22.0, 23.5, 25.0])Quick Reference
| Decoder method | Builder method | Registers | Python type |
|---|---|---|---|
decode_8bit_int() | add_8bit_int(v) | 0.5 | int |
decode_8bit_uint() | add_8bit_uint(v) | 0.5 | int |
decode_16bit_int() | add_16bit_int(v) | 1 | int |
decode_16bit_uint() | add_16bit_uint(v) | 1 | int |
decode_32bit_int() | add_32bit_int(v) | 2 | int |
decode_32bit_uint() | add_32bit_uint(v) | 2 | int |
decode_64bit_int() | add_64bit_int(v) | 4 | int |
decode_64bit_uint() | add_64bit_uint(v) | 4 | int |
decode_32bit_float() | add_32bit_float(v) | 2 | float |
decode_64bit_float() | add_64bit_float(v) | 4 | float |
decode_string(size) | add_string(v) | size/2 | bytes/str |
decode_bits() | add_bits(v) | 1 | list[bool] |
Always check your device documentation for data type, scaling factor, and byte order. When in doubt, try all four endianness combinations.