''' __ __ _ | \/ | __ _ ___| |_ ___ _ __ | |\/| |/ _` / __| __/ _ \ '__| | | | | (_| \__ \ || __/ | |_| |_|\__,_|___/\__\___|_| Master Python script that will trigger other scripts at every chosen time pace This script is triggered as a systemd service used as an alternative to cronjobs Attention: to do -> prevent SARA R4 Script to run again if it's taking more than 60 secs to finish (using a lock file ??) First time: need to create the service file --> sudo nano /etc/systemd/system/master_nebuleair.service ⬇️ [Unit] Description=Master manager for the Python loop scripts After=network.target [Service] ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/master.py Restart=always User=root WorkingDirectory=/var/www/nebuleair_pro_4g StandardOutput=append:/var/www/nebuleair_pro_4g/logs/master.log StandardError=append:/var/www/nebuleair_pro_4g/logs/master_errors.log [Install] WantedBy=multi-user.target ⬆️ Reload systemd (first time after creating the service): sudo systemctl daemon-reload Enable (once), start (once and after stopping) and restart (after modification)systemd: sudo systemctl enable master_nebuleair.service sudo systemctl start master_nebuleair.service sudo systemctl restart master_nebuleair.service Check the service status: sudo systemctl status master_nebuleair.service Specific scripts can be disabled with config.json Exemple: stop gathering data from NPM Exemple: stop sending data with SARA R4 ''' import time import threading import subprocess import os import sqlite3 # Base directory where scripts are stored SCRIPT_DIR = "/var/www/nebuleair_pro_4g/" DB_PATH = "/var/www/nebuleair_pro_4g/sqlite/sensors.db" # Lock file path for SARA script SARA_LOCK_FILE = "/var/www/nebuleair_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 run_script(script_name, interval, delay=0): """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: 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 sleep_time = max(0, next_run - time.monotonic()) # Prevent negative sleep times time.sleep(sleep_time) # 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 ("envea/read_value_v2.py", 10, 0), # Get Envea data every 10s ("loop/SARA_send_data_v2.py", 60, 1), # Send data every 60 seconds, with 1s delay ("BME280/get_data_v2.py", 120, 0), # Get BME280 data every 120 seconds ("MPPT/read.py", 120, 0), # Get MPPT data every 120 seconds ("sqlite/flush_old_data.py", 86400, 0) # Flush old data inside db every day ] # 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() # Keep the main script running while True: time.sleep(1)