From ad87c01f344d03fc4dd51f4534f9a66dc3c8e1ad Mon Sep 17 00:00:00 2001 From: naab Date: Thu, 29 May 2025 11:06:22 +0700 Subject: [PATCH] Push POE project --- __pycache__/config.cpython-312.pyc | Bin 0 -> 593 bytes __pycache__/sensor_bridge.cpython-312.pyc | Bin 0 -> 7145 bytes config.py | 20 +++ main.py | 17 +++ memory-bank/activeContext.md | 17 +++ memory-bank/productContext.md | 20 +++ memory-bank/progress.md | 23 ++++ memory-bank/projectbrief.md | 25 ++++ memory-bank/systemPatterns.md | 21 +++ memory-bank/techContext.md | 21 +++ requirements.txt | 2 + sensor_bridge.py | 151 ++++++++++++++++++++++ 12 files changed, 317 insertions(+) create mode 100644 __pycache__/config.cpython-312.pyc create mode 100644 __pycache__/sensor_bridge.cpython-312.pyc create mode 100644 config.py create mode 100644 main.py create mode 100644 memory-bank/activeContext.md create mode 100644 memory-bank/productContext.md create mode 100644 memory-bank/progress.md create mode 100644 memory-bank/projectbrief.md create mode 100644 memory-bank/systemPatterns.md create mode 100644 memory-bank/techContext.md create mode 100644 requirements.txt create mode 100644 sensor_bridge.py diff --git a/__pycache__/config.cpython-312.pyc b/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48167fbc93de5bee3171f3fe931d21046df57e37 GIT binary patch literal 593 zcmYjNO>fgc5MAfXi5-VTjS52Q1*D)JVo^1y2_Qs1EX8VSyS786B{VA84Kc(?u^lAm znKz@IuY$k}+ z09(3Z?Uc{nqnD7d!8)0TylS zj;!Y_0HkPQ`xrY%-ps-M_RO}B<@xh9dx~Dp+;S)?55^Xrcx94w@c77|^9J^x?$!pS z{mLOpsly>1xC+m)DUgI`*_iNqYOB#T`fb%P{}D~qO~Q5++3d?Ln%y_eenVGxC7om< z5;brUG*wM*lDIYTNG?h=JF=uiLLuT^L(-M{o<#mqt!@|xs@@{$j@qo7vZ|20)@^iT zqurMkQ_`Q+JE!0xywK(C*2CVx)Opi0vFr6TRq73WBkOqMO(wW zxi6?Yk24JO6Y%FLaAPCjm$?FU93QY@;ts{xe2`fRcLQ#@R{G?g)J`g&Yp1ovqwn{2 j&Pvs@LM_NG^EU%wncoNmD#{jvG!4b|Zy@ii(+z(B=S-sr literal 0 HcmV?d00001 diff --git a/__pycache__/sensor_bridge.cpython-312.pyc b/__pycache__/sensor_bridge.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa564610d50269bb52dd2dd06ad5abadcac89dbf GIT binary patch literal 7145 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 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