Simple Python Modbus Crawler that reads registers of a Modbus server/slave based on a register secification in a data frame/csv/...
Generic Modbus crawler that reads and writes registers according to device and register spec.
Author: Christian Seitl, christian.seitl@ait.ac.at
Registers are specified either in a pandas data frame or a csv-file. The columns are:
Register_start: start address of modbus read register command.Register_end: end address of modbus read register commandRegister_type: either i for input register, h for holding register, c for coil and d for discrete input.Data_type: Data type of variable. Depending on type the variable is kept in 1, 2 or 4 16-bit registers.
Name of variable.Unit of variable (optional, not used in the code).Scaling of variable (optional column, default None): Often fixed point values are stored in a scaled INT register,
which is an easy way to store comma values in one single register
U_L1 = 23174 and Scaling = 0.01 which lead to U_L1 = 231.74 .Used (optional column, default True): If False, the block will be ignored.
Unit_ID (optional column, default 1): Unit ID (Slave ID) when sending the read register command.
description (optional column, not used int the code): Description of the variable.mode (optional column, default ‘r’): If the variable should be read (‘r’) or written (‘w’) or both (‘rw’).
Example:
| Register_start | Register_end | Register_type | Data_type | Name | Unit | Scaling |
|---|---|---|---|---|---|---|
| 100 | 100 | i | INT16 | I_L1 | A | 0.01 |
| 110 | 111 | i | SINGLE | f | Hz | 1 |
| 200 | 205 | i | SINGLE | U_L1 | V | 1 |
| x | x | i | SINGLE | U_L2 | V | 1 |
| x | x | i | SINGLE | U_L3 | V | 1 |
Register_start until the line above the next number in the
column Register_start.INT16 multiple with 0.01U_L1, the next two into
U_L2 and so ondatatype -> datatypeDataType -> datatypeDATATYPE -> datatypedata_type -> datatypeRegister_type and Data_type are case-insensitive too, but don’t use snake casedata_type: stringX where X is the number of registers the
string uses which is always half the size of the string bytes because one register has 2 bytes. When writing the
string will be padded with spaces to the right to fill the registers. Up to 128 registers can be read at once (256
bytes).
string10 reads 10 characters from the registers striping white spaces at the start or endint: int16, shortuint: uint16, ushortdint: int32, longfloat: float32, single, real
andi: 4, 0x04, ir, inputregister, inputregh: 3, 0x03, hr, holdingregister, holdingregregister_spec_df = pd.read_csv("register_spec.csv")
modbus_device = ModbusTcpDevice("localhost", 502, byteorder=Endian.BIG, wordorder=Endian.BIG, auto_connect=True,
registers_spec_df=register_spec_df, register_specs_file_name=None)
modbus_device.connect()
registers = modbus_device.read_registers_as_dict()
modbus_device.disconnect()
Functions in ModbusTcpDevice:
def connect(self):
"""Connects to the Modbus device."""
def disconnect(self):
"""Disconnects from the Modbus device."""
@property
def connected(self) -> bool:
"""Returns True if connected to the Modbus device."""
def read_registers_as_dict(self) -> dict[str, float]:
"""Reads all registers and returns them as dictionary."""
def read_registers(self) -> list[ModbusRegister]:
"""Reads all registers with mode `r` or `rw` and returns them as list of ModbusRegister objects."""
def read_register(self, register: str | int) -> ModbusRegister:
"""Reads a single register with mode `r` or `rw` and returns it as ModbusRegister object."""
def write_register(self, register: str | int, value):
"""Writes a single register."""