update
This commit is contained in:
@@ -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
|
|
||||||
system_time = datetime.now() #local
|
|
||||||
utc_time = datetime.utcnow() #UTC
|
|
||||||
|
|
||||||
# If RTC is not connected, set default message
|
while True:
|
||||||
# Calculate time difference (in seconds) if RTC is connected
|
# Open a new database connection inside the loop to prevent connection loss
|
||||||
if rtc_time:
|
conn = sqlite3.connect(DB_PATH)
|
||||||
rtc_time_str = rtc_time.strftime('%Y-%m-%d %H:%M:%S')
|
cursor = conn.cursor()
|
||||||
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 both times
|
# Try to read RTC time
|
||||||
#print(f"RTC module Time: {rtc_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
rtc_time = read_time(bus)
|
||||||
#print(f"Sys local Time: {system_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
# Get current system time
|
||||||
#print(f"Sys UTC Time: {utc_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
system_time = datetime.now() #local
|
||||||
|
utc_time = datetime.utcnow() #UTC
|
||||||
|
|
||||||
# Create JSON output
|
# If RTC is not connected, set default message
|
||||||
time_data = {
|
# Calculate time difference (in seconds) if RTC is connected
|
||||||
"rtc_module_time":rtc_time_str,
|
if rtc_time:
|
||||||
"system_local_time": system_time.strftime('%Y-%m-%d %H:%M:%S'),
|
rtc_time_str = rtc_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
"system_utc_time": utc_time.strftime('%Y-%m-%d %H:%M:%S'),
|
time_difference = int((utc_time - rtc_time).total_seconds()) # Convert to int
|
||||||
"time_difference_seconds": time_difference
|
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
|
#print(json.dumps(time_data, indent=4))
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
# 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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -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
121
master.py
@@ -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)
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
Reference in New Issue
Block a user