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"),