This commit is contained in:
Your Name
2025-03-27 11:04:51 +01:00
parent 76a676925d
commit a67530ced6
5 changed files with 169 additions and 74 deletions

View File

@@ -9,6 +9,37 @@ Script to read time from RTC module and save it to DB
I2C connection I2C connection
Address 0x68 Address 0x68
/usr/bin/python3 /var/www/moduleair_pro_4g/RTC/save_to_db.py /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 smbus2
import time import time
@@ -17,8 +48,7 @@ from datetime import datetime
import sqlite3 import sqlite3
# Connect to (or create if not existent) the database # Connect to (or create if not existent) the database
conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db") DB_PATH = "/var/www/moduleair_pro_4g/sqlite/sensors.db"
cursor = conn.cursor()
# DS3231 I2C address # DS3231 I2C address
DS3231_ADDR = 0x68 DS3231_ADDR = 0x68
@@ -42,48 +72,57 @@ def read_time(bus):
return datetime(year, month, day, hours, minutes, seconds) return datetime(year, month, day, hours, minutes, seconds)
except OSError: except OSError:
return None # RTC module not connected return None # RTC module not connected
def main(): def main():
# Read RTC time # Read RTC time
bus = smbus2.SMBus(1) bus = smbus2.SMBus(1)
# Try to read RTC time
rtc_time = read_time(bus)
# Get current system time while True:
system_time = datetime.now() #local # Open a new database connection inside the loop to prevent connection loss
utc_time = datetime.utcnow() #UTC conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# If RTC is not connected, set default message # Try to read RTC time
# Calculate time difference (in seconds) if RTC is connected rtc_time = read_time(bus)
if rtc_time: # Get current system time
rtc_time_str = rtc_time.strftime('%Y-%m-%d %H:%M:%S') system_time = datetime.now() #local
time_difference = int((utc_time - rtc_time).total_seconds()) # Convert to int utc_time = datetime.utcnow() #UTC
else:
rtc_time_str = "not connected"
time_difference = "N/A" # Not applicable
# Print both times # If RTC is not connected, set default message
#print(f"RTC module Time: {rtc_time.strftime('%Y-%m-%d %H:%M:%S')}") # Calculate time difference (in seconds) if RTC is connected
#print(f"Sys local Time: {system_time.strftime('%Y-%m-%d %H:%M:%S')}") if rtc_time:
#print(f"Sys UTC Time: {utc_time.strftime('%Y-%m-%d %H:%M:%S')}") 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
# Create JSON output # Print both times
time_data = { #print(f"RTC module Time: {rtc_time.strftime('%Y-%m-%d %H:%M:%S')}")
"rtc_module_time":rtc_time_str, #print(f"Sys local Time: {system_time.strftime('%Y-%m-%d %H:%M:%S')}")
"system_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')}")
"system_utc_time": utc_time.strftime('%Y-%m-%d %H:%M:%S'),
"time_difference_seconds": time_difference
}
#print(json.dumps(time_data, indent=4)) # 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
}
cursor.execute("UPDATE timestamp_table SET last_updated = ? WHERE id = 1", (rtc_time_str,)) #print(json.dumps(time_data, indent=4))
# Commit and close the connection # Save to database
conn.commit() try:
conn.close() 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__": if __name__ == "__main__":

View File

@@ -73,6 +73,7 @@
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)">Mesures Temp/Hum</button> <button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)">Mesures Temp/Hum</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)">Mesures PM (5 canaux)</button> <button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)">Mesures PM (5 canaux)</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)">Sonde Cairsens</button> <button class="btn btn-primary" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)">Sonde Cairsens</button>
<button class="btn btn-warning" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)">Timestamp Table</button>
</div> </div>
</div> </div>
@@ -259,6 +260,10 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
<th>O3</th> <th>O3</th>
`; `;
}else if (table === "timestamp_table") {
tableHTML += `
<th>Timestamp</th>
`;
}else if (table === "data_CO2") { }else if (table === "data_CO2") {
tableHTML += ` tableHTML += `
<th>Timestamp</th> <th>Timestamp</th>
@@ -310,6 +315,10 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
<td>${columns[5]}</td> <td>${columns[5]}</td>
`; `;
}else if (table === "timestamp_table") {
tableHTML += `
<td>${columns[1]}</td>
`;
} }
else if (table === "data_CO2") { else if (table === "data_CO2") {
tableHTML += ` tableHTML += `

121
master.py
View File

@@ -53,27 +53,92 @@ Specific scripts can be disabled with config.json
import time import time
import threading import threading
import subprocess import subprocess
import json
import os import os
import sqlite3
# Base directory where scripts are stored # Base directory where scripts are stored
SCRIPT_DIR = "/var/www/moduleair_pro_4g/" 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): 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 script_path = os.path.join(SCRIPT_DIR, script_name) # Build full path
next_run = time.monotonic() + delay # Apply the initial delay next_run = time.monotonic() + delay # Apply the initial delay
while True: while True:
config = load_config() if is_script_enabled(script_name):
if config.get(script_name, True): # Default to True if not found # Special handling for SARA script to prevent concurrent runs
subprocess.run(["python3", script_path]) 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 # Wait until the next exact interval
next_run += interval next_run += interval
@@ -81,38 +146,20 @@ def run_script(script_name, interval, delay=0):
time.sleep(sleep_time) time.sleep(sleep_time)
# ✅ **Scripts that should start immediately** # Define scripts and their execution intervals (seconds)
IMMEDIATE_SCRIPTS = [ SCRIPTS = [
("RTC/save_to_db.py", 1, 0), # Format: (script_name, interval_in_seconds, start_delay_in_seconds)
("NPM/get_data_modbus_v3.py", 10, 0), ("NPM/get_data_modbus_v3.py", 10, 0), # Get NPM data (modbus 5 channels) every 10s
("envea/read_value_v2.py", 10, 0), ("MH-Z19/write_data.py", 10, 0), # Get Envea data every 10s
("MH-Z19/write_data.py", 10, 0), ("loop/SARA_send_data_v2.py", 60, 1), # Send data every 60 seconds, with 1s delay
("loop/SARA_send_data_v2.py", 60, 1), ("sqlite/flush_old_data.py", 86400, 0) # Flush old data inside db every day
("BME280/get_data_v2.py", 120, 0),
("sqlite/flush_old_data.py", 86400, 0)
] ]
# ✅ **The compiled binary that must wait for welcome_screen** # Start threads for scripts
WAIT_BINARY = "/var/www/moduleair_pro_4g/matrix/test_forms" # Adjust path if needed for script_name, interval, delay in SCRIPTS:
# 🚀 **Start all scripts that can run immediately**
for script_name, interval, delay in IMMEDIATE_SCRIPTS:
thread = threading.Thread(target=run_script, args=(script_name, interval, delay), daemon=True) thread = threading.Thread(target=run_script, args=(script_name, interval, delay), daemon=True)
thread.start() 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 # Keep the main script running
while True: while True:
time.sleep(1) time.sleep(1)

View File

@@ -33,9 +33,9 @@ script_configs = [
("NPM/get_data_modbus_v3.py", True), ("NPM/get_data_modbus_v3.py", True),
("loop/SARA_send_data_v2.py", True), ("loop/SARA_send_data_v2.py", True),
("RTC/save_to_db.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), ("envea/read_value_v2.py", False),
("MPPT/read.py", False), ("MH-Z19/write_data.py", True),
("windMeter/read.py", False), ("windMeter/read.py", False),
("sqlite/flush_old_data.py", True) ("sqlite/flush_old_data.py", True)
] ]
@@ -55,7 +55,7 @@ config_entries = [
("longitude_raw", "0", "int"), ("longitude_raw", "0", "int"),
("latitude_precision", "0", "int"), ("latitude_precision", "0", "int"),
("longitude_precision", "0", "int"), ("longitude_precision", "0", "int"),
("deviceName", "NebuleAir-proXXX", "str"), ("deviceName", "ModuleAir-proXXX", "str"),
("SaraR4_baudrate", "115200", "int"), ("SaraR4_baudrate", "115200", "int"),
("NPM_solo_port", "/dev/ttyAMA5", "str"), ("NPM_solo_port", "/dev/ttyAMA5", "str"),
("sshTunnel_port", "59228", "int"), ("sshTunnel_port", "59228", "int"),