Other GitHub Projects
Full project available on GitHub: https://github.com/hngjesse
This project addresses common challenges encountered in data logging using the Modbus communication protocol. Modbus was introduced in 1979 by Modicon (now Schneider Electric) to standardize communication between industrial electronic devices. It defines a universal protocol that allows different devices to exchange data reliably. Since 2004, the protocol has been maintained by the Modbus Organization, Inc.
One classification of Modbus communication is the Master–Slave model, also known as Modbus RTU (Remote Terminal Unit). In this model, the master device initiates commands to request data from one or more slave devices. Slaves only respond when queried by the master. Typically, Modbus RTU operates over RS-485 or RS-232 serial connections and supports one master with multiple slaves.
Another type is the Client–Server model, referred to as Modbus TCP. Here, communication occurs over Ethernet or a local network. Although conceptually similar to the master–slave architecture, Modbus TCP allows multiple clients and servers to communicate simultaneously, enhancing scalability and flexibility.
Modbus supports several function codes, each representing a different type of request:
Depending on the type of data required, the user selects the appropriate function code to retrieve information from a Modbus device.
The main working directory of this Python project is Modbus_loggers
. It contains three key subdirectories:
configs
data_storage
utils
The core script, modbus_logger.py
, is located in the Modbus_loggers
directory.
This script provides the foundational structure for the entire data logging system. It defines the main
workflow and executes essential functions sequentially or in a continuous loop.
Users are not advised to modify this file unless they fully understand its internal logic.
The configs
subdirectory contains JSON configuration files that define communication
and logging parameters for each Modbus device.
{
"modbus": {
"type": "serial",
"timeout": 0.2,
"port": "/dev/ttyS0",
"baudrate": 19200,
"stopbits": 1,
"bytesize": 8,
"parity": "N"
},
"device": {
"name": "dcm_3366",
"start_addr": 0,
"reg_count": 40,
"id_range": [1,2,3,4,5,6,7,8]
},
"logging": {
"base_folder": "/mnt/data_storage/Modbus_loggers/data_storage/dcm_3366",
"log_retention_days": 30,
"file_suffix": "dcm_3366",
"header": ["Datetime", "Device_ID",
"Forward_energy_kWh", "Active_power_kW",
"Current_A", "Voltage_V", "Error"],
"time_step": 2
}
}
{
"modbus": {
"type": "tcp",
"timeout": 0.2,
"host": "192.168.1.10",
"port": 502
},
"device": {
"name": "list_regis",
"start_addr": 4096,
"reg_count": 125,
"id_range": [1]
},
"logging": {
"base_folder": "/mnt/data_storage/Modbus_loggers/data_storage/list_regis",
"log_retention_days": 30,
"file_suffix": "regis",
"header": ["Datetime", "Device_ID"],
"time_step": 2
}
}
Modbus Section:
type
: Connection type — "serial"
(RTU) or "tcp"
.timeout
: Maximum time (seconds) to wait for a device response.port
: Serial port (e.g., /dev/ttyS0
) or TCP port number.Device Section:
name
: Corresponds to a device-specific function in utils
.start_addr
: Starting register address.reg_count
: Number of registers to read.id_range
: List of Modbus device IDs to poll.Logging Section:
base_folder
: Directory for log files.log_retention_days
: How long logs are retained.file_suffix
: Label for filenames.header
: Defines CSV structure.time_step
: Logging interval in seconds.
The utils
directory includes:
__init__.py
common_utils.py
validate_config.py
device_specific_func.py
common_utils.py
provides helper functions used by the main script.
validate_config.py
verifies JSON files for formatting or logical errors.
The device_specific_func.py
file is where users can define custom functions for their Modbus devices.
This modular approach allows users to log data from different devices by simply writing their own device-specific function
without modifying the main logging structure.
import struct
import csv
import logging
from datetime import datetime
from pymodbus.client import ModbusSerialClient, ModbusTcpClient
logger = logging.getLogger(__name__)
def dcm_3366(client: ModbusSerialClient, start_addr: int, reg_count: int,
csv_file: str, device_range: range) -> None:
"""Read DC meter (DCM3366) and save readings."""
for device_id in device_range:
logger.info(f"[dcm_3366] Reading device with Modbus ID = {device_id} ...")
try:
response = client.read_holding_registers(
address=start_addr, count=reg_count, device_id=device_id
)
except Exception as e:
logger.error(f"Error reading device {device_id}: {e}")
now = datetime.now().isoformat()
with open(csv_file, "a", newline="") as f:
csv.writer(f).writerow([now, device_id, None, None, None, None, "Error"])
continue
regs = response.registers
Forward_energy = (regs[0] << 16) + regs[1]
Active_power = (regs[20] << 16) + regs[21]
Current = (regs[22] << 16) + regs[23]
Voltage = (regs[24] << 16) + regs[25]
now = datetime.now().isoformat()
with open(csv_file, "a", newline="") as f:
csv.writer(f).writerow([
now, device_id,
round(Forward_energy / 100, 3),
round(Active_power / 1000, 3),
round(Current / 10000, 3),
round(Voltage / 10000, 1),
"No error"
])
Full project available on GitHub: https://github.com/hngjesse
📧 Email: hngjesse@gmail.com
🔗 GitHub: hngjesse