A simple Modbus/TCP client library for Python
—
Utility functions for data type conversion, bit manipulation, IEEE 754 floating point encoding/decoding, and protocol validation. These functions are essential for working with Modbus data types and converting between different representations.
Functions for converting between different data representations commonly used in industrial automation.
def word_list_to_long(val_list, big_endian=True, long_long=False):
"""
Convert word list to long integers.
Parameters:
val_list (list[int]): List of 16-bit words (0-65535)
big_endian (bool): Byte order (default: True)
long_long (bool): Use 64-bit integers instead of 32-bit (default: False)
Returns:
list[int]: List of 32-bit or 64-bit integers
"""
def long_list_to_word(val_list, big_endian=True, long_long=False):
"""
Convert long integers to word list.
Parameters:
val_list (list[int]): List of 32-bit or 64-bit integers
big_endian (bool): Byte order (default: True)
long_long (bool): Input integers are 64-bit (default: False)
Returns:
list[int]: List of 16-bit words (0-65535)
"""
def get_2comp(val_int, val_size=16):
"""
Convert unsigned integer to two's complement signed integer.
Parameters:
val_int (int): Unsigned integer value
val_size (int): Bit size (8, 16, 32, or 64, default: 16)
Returns:
int: Signed integer in two's complement format
"""
def get_list_2comp(val_list, val_size=16):
"""
Convert list of unsigned integers to two's complement signed integers.
Parameters:
val_list (list[int]): List of unsigned integer values
val_size (int): Bit size for each value (default: 16)
Returns:
list[int]: List of signed integers in two's complement format
"""Functions for encoding and decoding IEEE 754 floating point numbers commonly used in Modbus applications.
def decode_ieee(val_int, double=False):
"""
Decode IEEE 754 floating point from integer representation.
Parameters:
val_int (int): Integer representation of IEEE 754 float
double (bool): Use double precision (64-bit) instead of single (32-bit, default: False)
Returns:
float: Decoded floating point value
"""
def encode_ieee(val_float, double=False):
"""
Encode floating point value as IEEE 754 integer representation.
Parameters:
val_float (float): Floating point value to encode
double (bool): Use double precision (64-bit) instead of single (32-bit, default: False)
Returns:
int: IEEE 754 integer representation
"""Functions for working with individual bits within integers.
def get_bits_from_int(val_int, val_size=16):
"""
Extract bit list from integer value.
Parameters:
val_int (int): Integer value
val_size (int): Number of bits to extract (default: 16)
Returns:
list[bool]: List of bit values (LSB first)
"""
def test_bit(value, offset):
"""
Test if a specific bit is set in an integer.
Parameters:
value (int): Integer value to test
offset (int): Bit position (0 = LSB)
Returns:
bool: True if bit is set, False otherwise
"""
def set_bit(value, offset):
"""
Set a specific bit in an integer.
Parameters:
value (int): Original integer value
offset (int): Bit position to set (0 = LSB)
Returns:
int: Integer with specified bit set
"""
def reset_bit(value, offset):
"""
Clear a specific bit in an integer.
Parameters:
value (int): Original integer value
offset (int): Bit position to clear (0 = LSB)
Returns:
int: Integer with specified bit cleared
"""
def toggle_bit(value, offset):
"""
Toggle a specific bit in an integer.
Parameters:
value (int): Original integer value
offset (int): Bit position to toggle (0 = LSB)
Returns:
int: Integer with specified bit toggled
"""
def byte_length(bit_length):
"""
Calculate the number of bytes needed to store a given number of bits.
Parameters:
bit_length (int): Number of bits
Returns:
int: Number of bytes needed
"""Functions for protocol validation and checksum calculation.
def crc16(frame):
"""
Calculate CRC16 checksum for Modbus RTU frames.
Parameters:
frame (bytes): Frame data to calculate checksum for
Returns:
int: CRC16 checksum value
"""
def valid_host(host_str):
"""
Validate hostname or IP address format.
Parameters:
host_str (str): Hostname or IP address string
Returns:
bool: True if format is valid, False otherwise
"""from pyModbusTCP.utils import encode_ieee, decode_ieee, long_list_to_word, word_list_to_long
from pyModbusTCP.client import ModbusClient
client = ModbusClient(host="192.168.1.100", auto_open=True)
# Write a floating point value
temperature = 23.45
encoded_temp = encode_ieee(temperature)
register_list = long_list_to_word([encoded_temp], big_endian=True)
client.write_multiple_registers(100, register_list)
# Read and decode floating point value
registers = client.read_holding_registers(100, 2)
if registers:
temp_as_int = word_list_to_long(registers, big_endian=True)[0]
decoded_temp = decode_ieee(temp_as_int)
print(f"Temperature: {decoded_temp}°C")from pyModbusTCP.utils import get_bits_from_int, set_bit, reset_bit, test_bit
# Extract individual bits from a register value
register_value = 0b1101001010110011 # 54451
bits = get_bits_from_int(register_value, 16)
print(f"Bits: {bits}") # [True, True, False, False, ...]
# Manipulate individual bits
value = 0b00001111 # 15
value = set_bit(value, 4) # Set bit 4: 0b00011111 (31)
value = reset_bit(value, 0) # Clear bit 0: 0b00011110 (30)
is_set = test_bit(value, 1) # Test bit 1: True
print(f"Final value: {value}, bit 1 set: {is_set}")from pyModbusTCP.utils import word_list_to_long, long_list_to_word, get_2comp
# Convert 32-bit integers to register pairs
values_32bit = [1234567890, -987654321]
registers = long_list_to_word(values_32bit, big_endian=True)
print(f"Registers: {registers}") # [18838, 722, 50042, 35023]
# Convert back to 32-bit integers
converted_back = word_list_to_long(registers, big_endian=True)
print(f"Converted back: {converted_back}") # [1234567890, 4265312975]
# Handle signed values with two's complement
signed_values = [get_2comp(val, 32) for val in converted_back]
print(f"Signed values: {signed_values}") # [1234567890, -987654321]from pyModbusTCP.utils import encode_ieee, decode_ieee, long_list_to_word, word_list_to_long
# Encode double precision float (64-bit)
precise_value = 3.141592653589793
encoded = encode_ieee(precise_value, double=True)
print(f"Encoded: {encoded}")
# Convert to 4 registers for Modbus transmission
registers = long_list_to_word([encoded], big_endian=True, long_long=True)
print(f"Registers: {registers}") # List of 4 registers
# Decode back to double precision
combined = word_list_to_long(registers, big_endian=True, long_long=True)[0]
decoded = decode_ieee(combined, double=True)
print(f"Decoded: {decoded}") # 3.141592653589793from pyModbusTCP.utils import valid_host, crc16
# Validate host addresses
hosts = ["192.168.1.1", "localhost", "invalid@host", "::1", "example.com"]
for host in hosts:
is_valid = valid_host(host)
print(f"{host}: {'valid' if is_valid else 'invalid'}")
# Calculate CRC16 for Modbus RTU frame
frame = b'\x01\x03\x00\x00\x00\x0A' # Read holding registers request
checksum = crc16(frame)
print(f"CRC16: 0x{checksum:04X}")
# Complete frame with CRC
full_frame = frame + checksum.to_bytes(2, 'little')
print(f"Complete frame: {full_frame.hex()}")from pyModbusTCP.utils import get_bits_from_int, byte_length
# Analyze status register bits
status_register = 0b1010001100110101 # 41269
status_bits = get_bits_from_int(status_register, 16)
# Define bit meanings
bit_names = [
"System Ready", "Error Active", "Manual Mode", "Auto Mode",
"Motor 1 Run", "Motor 2 Run", "Pump Active", "Heater On",
"Alarm 1", "Alarm 2", "Service Required", "Door Open",
"Temperature OK", "Pressure OK", "Flow OK", "Reserved"
]
print("System Status:")
for i, (name, state) in enumerate(zip(bit_names, status_bits)):
print(f" Bit {i:2d} - {name:15s}: {'ON' if state else 'OFF'}")
# Calculate storage requirements
num_status_bits = 16
bytes_needed = byte_length(num_status_bits)
print(f"Storage required: {bytes_needed} bytes for {num_status_bits} bits")These utility functions integrate seamlessly with the client and server operations:
from pyModbusTCP.client import ModbusClient
from pyModbusTCP.server import ModbusServer, DataBank
from pyModbusTCP.utils import encode_ieee, decode_ieee, word_list_to_long, long_list_to_word
# Server side: Store floating point values
data_bank = DataBank()
temp_values = [23.45, 24.67, 22.89] # Temperature readings
for i, temp in enumerate(temp_values):
encoded = encode_ieee(temp)
registers = long_list_to_word([encoded], big_endian=True)
data_bank.set_holding_registers(i * 2, registers) # 2 registers per float
server = ModbusServer(data_bank=data_bank)
server.start()
# Client side: Read and decode floating point values
client = ModbusClient(auto_open=True)
temperatures = []
for i in range(3):
registers = client.read_holding_registers(i * 2, 2)
if registers:
temp_int = word_list_to_long(registers, big_endian=True)[0]
temperature = decode_ieee(temp_int)
temperatures.append(temperature)
print(f"Temperatures: {temperatures}")