''' Loop to run every minutes * * * * * /usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_modbus_loop.py ttyAMA5 saves data (avaerage) to a json file /var/www/nebuleair_pro_4g/NPM/data/data.json saves data (all) to a sqlite database first time running the script? sudo mkdir /var/www/nebuleair_pro_4g/NPM/data sudo touch /var/www/nebuleair_pro_4g/NPM/data/data.json ''' import serial import sys import crcmod import time import json import os import sqlite3 import smbus2 # For RTC DS3231 from datetime import datetime # Ensure a port argument is provided if len(sys.argv) < 2: print("Usage: python3 get_data_modbus.py ") sys.exit(1) port = '/dev/' + sys.argv[1] # Initialize serial communication try: ser = serial.Serial( port=port, baudrate=115200, parity=serial.PARITY_EVEN, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=0.5 ) except Exception as e: print(f"Error opening serial port {port}: {e}") sys.exit(1) # Define Modbus CRC-16 function crc16 = crcmod.predefined.mkPredefinedCrcFun('modbus') # Request frame without CRC data = b'\x01\x03\x00\x80\x00\x0A' # Calculate CRC crc = crc16(data) crc_low = crc & 0xFF crc_high = (crc >> 8) & 0xFF # Append CRC to the frame request = data + bytes([crc_low, crc_high]) # Log request frame print(f"Request frame: {request.hex()}") # Initialize SQLite database conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db") cursor = conn.cursor() # RTC Module (DS3231) Setup RTC_I2C_ADDR = 0x68 # DS3231 I2C Address bus = smbus2.SMBus(1) def bcd_to_dec(bcd): return (bcd // 16 * 10) + (bcd % 16) def get_rtc_time(): """Reads time from RTC module (DS3231)""" try: data = bus.read_i2c_block_data(RTC_I2C_ADDR, 0x00, 7) seconds = bcd_to_dec(data[0] & 0x7F) minutes = bcd_to_dec(data[1]) hours = bcd_to_dec(data[2] & 0x3F) day = bcd_to_dec(data[4]) month = bcd_to_dec(data[5]) year = bcd_to_dec(data[6]) + 2000 return datetime(year, month, day, hours, minutes, seconds).strftime("%Y-%m-%d %H:%M:%S") except Exception as e: print(f"RTC Read Error: {e}") return "N/A" # Initialize storage for averaging num_samples = 6 channel_sums = [0] * 5 # 5 channels #do not start immediately to prevent multiple write/read over NPM serial port time.sleep(3) # Loop 6 times to collect data every 10 seconds for i in range(num_samples): print(f"\nIteration {i+1}/{num_samples}") ser.write(request) rtc_timestamp = get_rtc_time() try: byte_data = ser.readline() formatted = ''.join(f'\\x{byte:02x}' for byte in byte_data) print(f"Raw Response: {formatted}") if len(byte_data) < 23: print("Incomplete response, skipping this sample.") time.sleep(9) continue # Extract and process the 5 channels channels = [] for j in range(5): lsw = int.from_bytes(byte_data[3 + j*4 : 5 + j*4], byteorder='little') msw = int.from_bytes(byte_data[5 + j*4 : 7 + j*4], byteorder='little') raw_value = (msw << 16) | lsw channels.append(raw_value) # Accumulate sum for each channel for j in range(5): channel_sums[j] += channels[j] # Print collected values print(f"Timestamp (RTC): {rtc_timestamp}") for j in range(5): print(f"Channel {j+1}: {channels[j]}") # Save the individual reading to the database cursor.execute(''' INSERT INTO data (timestamp, PM_ch1, PM_ch2, PM_ch3, PM_ch4, PM_ch5) VALUES (?, ?, ?, ?, ?, ?) ''', (rtc_timestamp, channels[0], channels[1], channels[2], channels[3], channels[4])) conn.commit() except Exception as e: print(f"Error reading data: {e}") # Wait 10 seconds before next reading time.sleep(10) # Compute the average values channel_means = [int(s / num_samples) for s in channel_sums] # Create JSON structure data_json = { "channel_1": channel_means[0], "channel_2": channel_means[1], "channel_3": channel_means[2], "channel_4": channel_means[3], "channel_5": channel_means[4] } # Print final JSON data print("\nFinal JSON Data (Averaged over 6 readings):") print(json.dumps(data_json, indent=4)) # Define JSON file path output_file = "/var/www/nebuleair_pro_4g/NPM/data/data.json" # Write results to a JSON file try: with open(output_file, "w") as f: json.dump(data_json, f, indent=4) print(f"\nAveraged JSON data saved to {output_file}") except Exception as e: print(f"Error writing to file: {e}") # Close serial connection ser.close()