diff --git a/RTC/save_to_db.py b/RTC/save_to_db.py index ab069c5..844f4e9 100755 --- a/RTC/save_to_db.py +++ b/RTC/save_to_db.py @@ -9,6 +9,37 @@ Script to read time from RTC module and save it to DB I2C connection Address 0x68 /usr/bin/python3 /var/www/moduleair_pro_4g/RTC/save_to_db.py + +This need to be run as a system service + +--> sudo nano /etc/systemd/system/rtc_save_to_db.service + +⬇️ +[Unit] +Description=RTC Save to DB Script +After=network.target + +[Service] +ExecStart=/usr/bin/python3 /var/www/moduleair_pro_4g/RTC/save_to_db.py +Restart=always +RestartSec=1 +User=root +WorkingDirectory=/var/www/moduleair_pro_4g +StandardOutput=append:/var/www/moduleair_pro_4g/logs/rtc_save_to_db.log +StandardError=append:/var/www/moduleair_pro_4g/logs/rtc_save_to_db_errors.log + +[Install] +WantedBy=multi-user.target +⬆️ + + +sudo systemctl daemon-reload +sudo systemctl enable rtc_save_to_db.service + +sudo systemctl start rtc_save_to_db.service + +sudo systemctl status rtc_save_to_db.service + ''' import smbus2 import time @@ -17,8 +48,7 @@ from datetime import datetime import sqlite3 # Connect to (or create if not existent) the database -conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db") -cursor = conn.cursor() +DB_PATH = "/var/www/moduleair_pro_4g/sqlite/sensors.db" # DS3231 I2C address DS3231_ADDR = 0x68 @@ -42,48 +72,57 @@ def read_time(bus): return datetime(year, month, day, hours, minutes, seconds) except OSError: return None # RTC module not connected - def main(): # Read RTC time bus = smbus2.SMBus(1) - # Try to read RTC time - rtc_time = read_time(bus) - - # Get current system time - system_time = datetime.now() #local - utc_time = datetime.utcnow() #UTC - # If RTC is not connected, set default message - # Calculate time difference (in seconds) if RTC is connected - if rtc_time: - rtc_time_str = rtc_time.strftime('%Y-%m-%d %H:%M:%S') - time_difference = int((utc_time - rtc_time).total_seconds()) # Convert to int - else: - rtc_time_str = "not connected" - time_difference = "N/A" # Not applicable + while True: + # Open a new database connection inside the loop to prevent connection loss + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() - # Print both times - #print(f"RTC module Time: {rtc_time.strftime('%Y-%m-%d %H:%M:%S')}") - #print(f"Sys local Time: {system_time.strftime('%Y-%m-%d %H:%M:%S')}") - #print(f"Sys UTC Time: {utc_time.strftime('%Y-%m-%d %H:%M:%S')}") + # Try to read RTC time + rtc_time = read_time(bus) + # Get current system time + system_time = datetime.now() #local + utc_time = datetime.utcnow() #UTC - # Create JSON output - time_data = { - "rtc_module_time":rtc_time_str, - "system_local_time": system_time.strftime('%Y-%m-%d %H:%M:%S'), - "system_utc_time": utc_time.strftime('%Y-%m-%d %H:%M:%S'), - "time_difference_seconds": time_difference - } + # If RTC is not connected, set default message + # Calculate time difference (in seconds) if RTC is connected + if rtc_time: + rtc_time_str = rtc_time.strftime('%Y-%m-%d %H:%M:%S') + time_difference = int((utc_time - rtc_time).total_seconds()) # Convert to int + else: + rtc_time_str = "not connected" + time_difference = "N/A" # Not applicable - #print(json.dumps(time_data, indent=4)) + # Print both times + #print(f"RTC module Time: {rtc_time.strftime('%Y-%m-%d %H:%M:%S')}") + #print(f"Sys local Time: {system_time.strftime('%Y-%m-%d %H:%M:%S')}") + #print(f"Sys UTC Time: {utc_time.strftime('%Y-%m-%d %H:%M:%S')}") - cursor.execute("UPDATE timestamp_table SET last_updated = ? WHERE id = 1", (rtc_time_str,)) + # Create JSON output + time_data = { + "rtc_module_time":rtc_time_str, + "system_local_time": system_time.strftime('%Y-%m-%d %H:%M:%S'), + "system_utc_time": utc_time.strftime('%Y-%m-%d %H:%M:%S'), + "time_difference_seconds": time_difference + } - # Commit and close the connection - conn.commit() - conn.close() + #print(json.dumps(time_data, indent=4)) + + # Save to database + try: + cursor.execute("UPDATE timestamp_table SET last_updated = ? WHERE id = 1", (rtc_time_str,)) + conn.commit() + #print("Sensor data saved successfully!") + + except sqlite3.Error as e: + print(f"Database error: {e}") + + conn.close() # Close connection to avoid database locking issues + time.sleep(1) # Wait for 1 second before reading again - #print("Sensor data saved successfully!") if __name__ == "__main__": diff --git a/html/database.html b/html/database.html index 696e2a0..4eecb64 100755 --- a/html/database.html +++ b/html/database.html @@ -73,6 +73,7 @@ + @@ -259,6 +260,10 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "") O3 `; + }else if (table === "timestamp_table") { + tableHTML += ` + Timestamp + `; }else if (table === "data_CO2") { tableHTML += ` Timestamp @@ -310,6 +315,10 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "") ${columns[5]} `; + }else if (table === "timestamp_table") { + tableHTML += ` + ${columns[1]} + `; } else if (table === "data_CO2") { tableHTML += ` diff --git a/master.py b/master.py index 72763f0..5a801dc 100755 --- a/master.py +++ b/master.py @@ -53,27 +53,92 @@ Specific scripts can be disabled with config.json import time import threading import subprocess -import json import os +import sqlite3 + # Base directory where scripts are stored SCRIPT_DIR = "/var/www/moduleair_pro_4g/" -CONFIG_FILE = "/var/www/moduleair_pro_4g/config.json" +DB_PATH = "/var/www/moduleair_pro_4g/sqlite/sensors.db" + +# Lock file path for SARA script +SARA_LOCK_FILE = "/var/www/moduleair_pro_4g/sara_script.lock" + + +def is_script_enabled(script_name): + """Check if a script is enabled in the database.""" + try: + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + cursor.execute( + "SELECT enabled FROM config_scripts_table WHERE script_path = ?", + (script_name,) + ) + + result = cursor.fetchone() + conn.close() + + if result is None: + return True # Default to enabled if not found in database + + return bool(result[0]) + except Exception: + # If any database error occurs, default to enabled + return True + + +def create_lock_file(): + """Create a lock file for the SARA script.""" + with open(SARA_LOCK_FILE, 'w') as f: + f.write(str(int(time.time()))) + + +def remove_lock_file(): + """Remove the SARA script lock file.""" + if os.path.exists(SARA_LOCK_FILE): + os.remove(SARA_LOCK_FILE) + + +def is_script_locked(): + """Check if the SARA script is currently locked.""" + if not os.path.exists(SARA_LOCK_FILE): + return False + + # Check if lock is older than 60 seconds (stale) + with open(SARA_LOCK_FILE, 'r') as f: + try: + lock_time = int(f.read().strip()) + if time.time() - lock_time > 60: + # Lock is stale, remove it + remove_lock_file() + return False + except ValueError: + # Invalid lock file content + remove_lock_file() + return False + + return True -def load_config(): - """Load the configuration file to determine which scripts to run.""" - with open(CONFIG_FILE, "r") as f: - return json.load(f) def run_script(script_name, interval, delay=0): - """Run a PYTHON script in a synchronized loop with an optional start delay.""" + """Run a script in a synchronized loop with an optional start delay.""" script_path = os.path.join(SCRIPT_DIR, script_name) # Build full path next_run = time.monotonic() + delay # Apply the initial delay while True: - config = load_config() - if config.get(script_name, True): # Default to True if not found - subprocess.run(["python3", script_path]) + if is_script_enabled(script_name): + # Special handling for SARA script to prevent concurrent runs + if script_name == "loop/SARA_send_data_v2.py": + if not is_script_locked(): + create_lock_file() + try: + subprocess.run(["python3", script_path]) + finally: + remove_lock_file() + else: + # Run other scripts normally + subprocess.run(["python3", script_path]) # Wait until the next exact interval next_run += interval @@ -81,38 +146,20 @@ def run_script(script_name, interval, delay=0): time.sleep(sleep_time) -# ✅ **Scripts that should start immediately** -IMMEDIATE_SCRIPTS = [ - ("RTC/save_to_db.py", 1, 0), - ("NPM/get_data_modbus_v3.py", 10, 0), - ("envea/read_value_v2.py", 10, 0), - ("MH-Z19/write_data.py", 10, 0), - ("loop/SARA_send_data_v2.py", 60, 1), - ("BME280/get_data_v2.py", 120, 0), - ("sqlite/flush_old_data.py", 86400, 0) +# Define scripts and their execution intervals (seconds) +SCRIPTS = [ + # Format: (script_name, interval_in_seconds, start_delay_in_seconds) + ("NPM/get_data_modbus_v3.py", 10, 0), # Get NPM data (modbus 5 channels) every 10s + ("MH-Z19/write_data.py", 10, 0), # Get Envea data every 10s + ("loop/SARA_send_data_v2.py", 60, 1), # Send data every 60 seconds, with 1s delay + ("sqlite/flush_old_data.py", 86400, 0) # Flush old data inside db every day ] -# ✅ **The compiled binary that must wait for welcome_screen** -WAIT_BINARY = "/var/www/moduleair_pro_4g/matrix/test_forms" # Adjust path if needed - -# 🚀 **Start all scripts that can run immediately** -for script_name, interval, delay in IMMEDIATE_SCRIPTS: +# Start threads for scripts +for script_name, interval, delay in SCRIPTS: thread = threading.Thread(target=run_script, args=(script_name, interval, delay), daemon=True) thread.start() -# 🚀 **Run welcome_screen in parallel** -print("🚀 Running welcome screen...") -welcome_process = subprocess.Popen(["sudo", "/var/www/moduleair_pro_4g/matrix/welcome_screen"]) -print("✅ Welcome screen started in parallel with other scripts.") - -# 🕒 **Wait for welcome_screen to finish before running WAIT_BINARY** -welcome_process.wait() -print("✅ Welcome screen finished. Starting another_screen...") - -# 🚀 **Now run the compiled binary `another_screen`** -subprocess.run(["sudo", WAIT_BINARY]) -print("✅ another_screen execution completed.") - # Keep the main script running while True: time.sleep(1) diff --git a/config.json.dist b/old/config.json.dist similarity index 100% rename from config.json.dist rename to old/config.json.dist diff --git a/sqlite/set_config.py b/sqlite/set_config.py index 7ecb030..71eea5b 100644 --- a/sqlite/set_config.py +++ b/sqlite/set_config.py @@ -33,9 +33,9 @@ script_configs = [ ("NPM/get_data_modbus_v3.py", True), ("loop/SARA_send_data_v2.py", True), ("RTC/save_to_db.py", True), - ("BME280/get_data_v2.py", True), + ("BME280/get_data_v2.py", False), ("envea/read_value_v2.py", False), - ("MPPT/read.py", False), + ("MH-Z19/write_data.py", True), ("windMeter/read.py", False), ("sqlite/flush_old_data.py", True) ] @@ -55,7 +55,7 @@ config_entries = [ ("longitude_raw", "0", "int"), ("latitude_precision", "0", "int"), ("longitude_precision", "0", "int"), - ("deviceName", "NebuleAir-proXXX", "str"), + ("deviceName", "ModuleAir-proXXX", "str"), ("SaraR4_baudrate", "115200", "int"), ("NPM_solo_port", "/dev/ttyAMA5", "str"), ("sshTunnel_port", "59228", "int"),