diff --git a/__pycache__/config.cpython-312.pyc b/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000..48167fb Binary files /dev/null and b/__pycache__/config.cpython-312.pyc differ diff --git a/__pycache__/sensor_bridge.cpython-312.pyc b/__pycache__/sensor_bridge.cpython-312.pyc new file mode 100644 index 0000000..fa56461 Binary files /dev/null and b/__pycache__/sensor_bridge.cpython-312.pyc differ diff --git a/config.py b/config.py new file mode 100644 index 0000000..a227835 --- /dev/null +++ b/config.py @@ -0,0 +1,20 @@ +import time + +# Modbus configuration +MODBUS_HOST = "10.84.48.153" +MODBUS_PORT = 505 +UNIT_ID = 1 + +# MQTT configuration +MQTT_BROKER = "mqtt.service.mesh" +MQTT_PORT = 1883 +MQTT_TOPIC = "Temperature_Humidity" +MQTT_CLIENT_ID = f"modbus-mqtt-client-{int(time.time())}" +MQTT_USERNAME = "relay" +MQTT_PASSWORD = "Sey@K9c&Q4^" + +# Location information +LOCATION = "Office" + +# Read and publish cycle configuration (seconds) +PUBLISH_INTERVAL = 10 \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..3d35d87 --- /dev/null +++ b/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +""" +Modbus to MQTT Bridge Service + +This service reads temperature and humidity data from a Modbus TCP server +and publishes the data to an MQTT broker. + +Usage: + python main.py + +Author: POE Project +""" + +from sensor_bridge import main_loop + +if __name__ == "__main__": + main_loop() \ No newline at end of file diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md new file mode 100644 index 0000000..e73e812 --- /dev/null +++ b/memory-bank/activeContext.md @@ -0,0 +1,17 @@ +# Active Context + +## Current Work Focus +- The script is stable and functional, continuously reading temperature and humidity from a Modbus TCP device and publishing the data to an MQTT broker in pretty-printed JSON format. +- The main focus is on reliability, error handling, and clear logging. + +## Recent Changes +- Improved JSON formatting for MQTT payloads (pretty print with indent). +- Refined temperature calibration logic (subtracting 40 from raw value after scaling). +- Enhanced logging for all major events and error conditions. +- All configuration is now at the top of the script for easier modification. + +## Next Steps +- Optional: Parameterize configuration via environment variables or a config file for easier deployment. +- Optional: Add support for additional Modbus registers or sensors. +- Optional: Implement more advanced error recovery or alerting (e.g., email/SMS on repeated failures). +- Optional: Containerize the application for easier deployment in production environments. \ No newline at end of file diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md new file mode 100644 index 0000000..2aa1d5d --- /dev/null +++ b/memory-bank/productContext.md @@ -0,0 +1,20 @@ +# Product Context + +## Why This Project Exists +Many industrial and environmental sensors use the Modbus protocol for data communication, while modern IoT and monitoring systems often rely on MQTT for data ingestion and distribution. This project bridges the gap between legacy Modbus devices and MQTT-based platforms, enabling seamless integration and real-time data flow. + +## Problems Solved +- Eliminates the need for manual data collection from Modbus sensors. +- Automates the process of converting and forwarding sensor data to cloud or local MQTT brokers. +- Provides a reliable, scriptable, and extensible solution for integrating Modbus sensors into IoT ecosystems. + +## How It Should Work +- The script runs continuously, connecting to a Modbus TCP device to read temperature and humidity data at regular intervals. +- Data is processed and formatted as JSON, then published to a specified MQTT topic. +- The system handles connection issues gracefully, with logging for troubleshooting. + +## User Experience Goals +- Simple configuration via code variables (host, port, credentials, topic, etc.). +- Clear, timestamped logging for all major events and errors. +- Data is published in a structured, readable JSON format for easy consumption by MQTT subscribers. +- Minimal setup required; designed for headless/server environments. \ No newline at end of file diff --git a/memory-bank/progress.md b/memory-bank/progress.md new file mode 100644 index 0000000..daa740e --- /dev/null +++ b/memory-bank/progress.md @@ -0,0 +1,23 @@ +# Progress + +## What Works +- The script successfully connects to both the Modbus TCP device and the MQTT broker. +- Temperature and humidity data are read from the correct Modbus registers and calibrated as per device documentation. +- Data is published to the MQTT topic in a pretty-printed JSON format. +- Logging provides clear feedback on all major events and errors. +- The script handles connection failures and retries in the main loop. + +## What's Left to Build +- (Optional) Configuration via environment variables or external config file. +- (Optional) Support for additional sensors or Modbus registers. +- (Optional) Advanced error notification (e.g., email/SMS alerts). +- (Optional) Dockerfile or deployment scripts for production use. + +## Current Status +- The project is functional and meets its core requirements for a Modbus-to-MQTT bridge. +- Ready for deployment in environments matching the current configuration. + +## Known Issues +- All configuration is hardcoded; not ideal for dynamic or multi-environment deployments. +- No persistent storage or buffering if MQTT broker is temporarily unavailable. +- No web UI or REST API for monitoring or control. \ No newline at end of file diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md new file mode 100644 index 0000000..d3932cb --- /dev/null +++ b/memory-bank/projectbrief.md @@ -0,0 +1,25 @@ +# Project Brief + +## Project Name +Modbus-to-MQTT Bridge for Environmental Sensor + +## Overview +This project implements a Python-based bridge that reads temperature and humidity data from a Modbus TCP device and publishes the readings to an MQTT broker in JSON format. The system is designed for continuous, automated data acquisition and integration with IoT or monitoring platforms. + +## Core Requirements +- Connect to a Modbus TCP device (sensor gateway) to read holding registers for temperature and humidity. +- Process and calibrate the raw sensor data as per device documentation. +- Publish the processed data to a specified MQTT topic at a configurable interval. +- Support MQTT authentication (username/password). +- Provide clear logging for connection, data acquisition, and publishing events. + +## Goals +- Enable seamless integration of Modbus-based sensors with MQTT-based IoT systems. +- Ensure reliable, periodic data transfer with error handling and reconnection logic. +- Maintain code clarity and extensibility for future enhancements (e.g., more sensors, additional data fields). + +## Scope +- Single Python script (poe.py) as the main application. +- No web interface or GUI; headless operation via command line. +- Focus on environmental data (temperature, humidity) but extensible for other Modbus registers. +- No persistent storage; data is transient and only sent to MQTT. \ No newline at end of file diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md new file mode 100644 index 0000000..4356519 --- /dev/null +++ b/memory-bank/systemPatterns.md @@ -0,0 +1,21 @@ +# System Patterns + +## System Architecture +- **Single-process, event-driven script**: The application runs as a single Python process, using a main loop to periodically read from Modbus and publish to MQTT. +- **Polling pattern**: Data is acquired from the Modbus device at a fixed interval (configurable via `PUBLISH_INTERVAL`). +- **Bridge pattern**: The script acts as a bridge between two protocols (Modbus TCP and MQTT), translating and forwarding data. + +## Key Technical Decisions +- **Direct variable configuration**: All connection and operational parameters are set as variables at the top of the script for simplicity. +- **Error handling and reconnection**: The script checks and re-establishes connections to both Modbus and MQTT as needed, with logging for all failures. +- **JSON formatting**: Data is published in pretty-printed JSON for readability and ease of integration. +- **Separation of concerns**: Reading/publishing logic is encapsulated in a dedicated function (`read_and_publish_data`), while the main loop handles orchestration and lifecycle. + +## Design Patterns +- **Callback pattern**: Used for MQTT events (on_connect, on_publish). +- **Try/except/finally**: Used for robust error handling and resource cleanup. +- **Headless operation**: No UI; all feedback is via logs and MQTT messages. + +## Component Relationships +- The Modbus client and MQTT client are instantiated and managed independently, but coordinated within the main loop. +- Data flows from Modbus -> Python processing -> MQTT publish. \ No newline at end of file diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md new file mode 100644 index 0000000..ed2520d --- /dev/null +++ b/memory-bank/techContext.md @@ -0,0 +1,21 @@ +# Technical Context + +## Technologies Used +- **Python 3.x**: Main programming language. +- **pymodbus**: For Modbus TCP client communication. +- **paho-mqtt**: For MQTT client functionality. +- **Logging**: Python's built-in logging module for event and error tracking. + +## Dependencies +- `pymodbus` (>=2.5, <4.0 recommended for current code) +- `paho-mqtt` + +## Development Setup +- No external configuration files; all settings are in `poe.py` as variables. +- Script is intended to run on any system with Python 3.x and the above dependencies installed. + +## Technical Constraints +- Assumes the Modbus device is accessible via TCP/IP and supports reading holding registers for temperature and humidity. +- MQTT broker must be reachable from the host running the script. +- No persistent storage or database integration; data is transient. +- No web UI or REST API; all interaction is via logs and MQTT. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0b055fb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pymodbus>=3.5.0 +paho-mqtt>=1.6.0 \ No newline at end of file diff --git a/sensor_bridge.py b/sensor_bridge.py new file mode 100644 index 0000000..f5cfcb4 --- /dev/null +++ b/sensor_bridge.py @@ -0,0 +1,151 @@ +import logging +import json +import time +from datetime import datetime +from pymodbus.client import ModbusTcpClient +from pymodbus.exceptions import ModbusException + +# Add MQTT client +import paho.mqtt.client as mqtt + +# Import configuration +from config import ( + MODBUS_HOST, MODBUS_PORT, UNIT_ID, + MQTT_BROKER, MQTT_PORT, MQTT_TOPIC, MQTT_CLIENT_ID, + MQTT_USERNAME, MQTT_PASSWORD, LOCATION, PUBLISH_INTERVAL +) + +# Setting logging basic to see output +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# MQTT callbacks +def on_connect(client, userdata, flags, rc): + if rc == 0: + logging.info("Connected to MQTT Broker!") + else: + logging.error(f"Cannot connect to MQTT Broker. Return code: {rc}") + +def on_publish(client, userdata, mid): + logging.info(f"Successfully sent message with ID: {mid}") + +def read_and_publish_data(mqtt_client, modbus_client): + """Read data from Modbus and publish to MQTT""" + try: + # Check connection to Modbus server + if not modbus_client.is_socket_open(): + if not modbus_client.connect(): + logging.error("Cannot connect to Modbus server.") + return False + + # Read temperature (register 0) + result_temp = modbus_client.read_holding_registers(address=0, count=1, slave=UNIT_ID) + + # Read humidity (register 1) + result_hum = modbus_client.read_holding_registers(address=1, count=1, slave=UNIT_ID) + + # Initialize data to publish + data = { + "time": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), + "location": LOCATION + } + + # Process temperature + if hasattr(result_temp, 'registers') and result_temp.registers: + raw_temp = result_temp.registers[0] + raw_temperature = raw_temp * 0.1 + temperature = raw_temperature - 40 + logging.info(f"Raw temperature: {raw_temperature:.1f}°C (raw: {raw_temp})") + logging.info(f"Corrected temperature: {temperature:.1f}°C") + data["temperature"] = round(temperature, 1) + else: + logging.error(f"Error reading temperature: {result_temp}") + return False + + # Xử lý độ ẩm + if hasattr(result_hum, 'registers') and result_hum.registers: + raw_hum = result_hum.registers[0] + humidity = raw_hum * 0.1 + logging.info(f"Humidity: {humidity:.1f}%RH (raw: {raw_hum})") + data["humidity"] = round(humidity, 1) + else: + logging.error(f"Error reading humidity: {result_hum}") + return False + + # Convert data to JSON with a better format + # indent=2 creates whitespace and newlines for JSON + payload = json.dumps(data, indent=2) + logging.info(f"Publishing data: {payload}") + result = mqtt_client.publish(MQTT_TOPIC, payload) + + # Ensure data is sent + result.wait_for_publish() + + if result.is_published(): + logging.info(f"Successfully published data to topic '{MQTT_TOPIC}'") + return True + else: + logging.error("Cannot publish data") + return False + + except Exception as e: + logging.error(f"Error in reading/writing data: {e}", exc_info=True) + return False + +def main_loop(): + """Main function to connect and publish data in cycles""" + # Initialize MQTT client + mqtt_client = mqtt.Client(client_id=MQTT_CLIENT_ID) + mqtt_client.on_connect = on_connect + mqtt_client.on_publish = on_publish + + # Set username and password if needed + if MQTT_USERNAME and MQTT_PASSWORD: + mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) + + # Initialize Modbus TCP client + modbus_client = ModbusTcpClient( + host=MODBUS_HOST, + port=MODBUS_PORT, + timeout=30 + ) + + try: + # Connect to MQTT broker + mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60) + mqtt_client.loop_start() + logging.info(f"Attempting to connect to Modbus TCP server: {MODBUS_HOST}:{MODBUS_PORT}...") + + # Connect to Modbus server + if not modbus_client.connect(): + logging.error("Cannot connect to Modbus server initially.") + return + + logging.info("Successfully connected to Modbus server.") + logging.info(f"Starting data reading cycle every {PUBLISH_INTERVAL} seconds...") + + try: + # Main loop to read and publish data + while True: + success = read_and_publish_data(mqtt_client, modbus_client) + + if not success: + logging.warning("Error occurred in current cycle, will retry in next cycle.") + + # Wait for next publish cycle + logging.info(f"Waiting {PUBLISH_INTERVAL} seconds until next publish...") + time.sleep(PUBLISH_INTERVAL) + + except KeyboardInterrupt: + logging.info("Received stop signal from user.") + + except Exception as e: + logging.error(f"Unexpected error in main loop: {e}", exc_info=True) + + finally: + # Close Modbus connection + modbus_client.close() + logging.info("Successfully closed Modbus connection.") + + mqtt_client.loop_stop() + mqtt_client.disconnect() + logging.info("Successfully closed MQTT connection.") \ No newline at end of file