This commit is contained in:
PaulVua
2025-02-10 17:25:34 +01:00
parent e609c38ca0
commit 62c729b63b
8 changed files with 223 additions and 41 deletions

View File

@@ -1,12 +1,19 @@
''' '''
_ _ ____ __ __
| \ | | _ \| \/ |
| \| | |_) | |\/| |
| |\ | __/| | | |
|_| \_|_| |_| |_|
Script to get NPM data via Modbus Script to get NPM data via Modbus
need parameter: port need parameter: port
/usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_modbus.py ttyAMA5 /usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_modbus.py
Modbus RTU Modbus RTU
[Slave Address][Function Code][Starting Address][Quantity of Registers][CRC] [Slave Address][Function Code][Starting Address][Quantity of Registers][CRC]
Pour récupérer les 5 cannaux (a partir du registre 0x80) Pour récupérer les 5 cannaux (a partir du registre 0x80)
Donnée actualisée toutes les 10 secondes
Request Request
\x01\x03\x00\x80\x00\x0A\xE4\x1E \x01\x03\x00\x80\x00\x0A\xE4\x1E
@@ -24,13 +31,28 @@ import requests
import json import json
import sys import sys
import crcmod import crcmod
import sqlite3
parameter = sys.argv[1:] # Exclude the script name # Connect to the SQLite database
#print("Parameters received:") conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
port='/dev/'+parameter[0] cursor = conn.cursor()
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Load the configuration data
config_file = '/var/www/nebuleair_pro_4g/config.json'
config = load_config(config_file)
npm_solo_port = config.get('NPM_solo_port', '') #port du NPM solo
ser = serial.Serial( ser = serial.Serial(
port=port, port=npm_solo_port,
baudrate=115200, baudrate=115200,
parity=serial.PARITY_EVEN, parity=serial.PARITY_EVEN,
stopbits=serial.STOPBITS_ONE, stopbits=serial.STOPBITS_ONE,
@@ -51,15 +73,20 @@ crc_high = (crc >> 8) & 0xFF
# Append CRC to the frame # Append CRC to the frame
request = data + bytes([crc_low, crc_high]) request = data + bytes([crc_low, crc_high])
print(f"Request frame: {request.hex()}") #print(f"Request frame: {request.hex()}")
ser.write(request) ser.write(request)
#GET RTC TIME from SQlite
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
row = cursor.fetchone() # Get the first (and only) row
rtc_time_str = row[1] # '2025-02-07 12:30:45'
while True: while True:
try: try:
byte_data = ser.readline() byte_data = ser.readline()
formatted = ''.join(f'\\x{byte:02x}' for byte in byte_data) formatted = ''.join(f'\\x{byte:02x}' for byte in byte_data)
print(formatted) #print(formatted)
# Extract LSW (first 2 bytes) and MSW (last 2 bytes) # Extract LSW (first 2 bytes) and MSW (last 2 bytes)
lsw_channel1 = int.from_bytes(byte_data[3:5], byteorder='little') lsw_channel1 = int.from_bytes(byte_data[3:5], byteorder='little')
@@ -82,12 +109,18 @@ while True:
msw_chanel5 = int.from_bytes(byte_data[21:23], byteorder='little') msw_chanel5 = int.from_bytes(byte_data[21:23], byteorder='little')
raw_value_channel5 = (msw_chanel5 << 16) | lsw_channel5 raw_value_channel5 = (msw_chanel5 << 16) | lsw_channel5
print(f"Channel 1 (0.2->0.5): {raw_value_channel1}") #print(f"Channel 1 (0.2->0.5): {raw_value_channel1}")
print(f"Channel 2 (0.5->1.0): {raw_value_channel2}") #print(f"Channel 2 (0.5->1.0): {raw_value_channel2}")
print(f"Channel 3 (1.0->2.5): {raw_value_channel3}") #print(f"Channel 3 (1.0->2.5): {raw_value_channel3}")
print(f"Channel 4 (2.5->5.0): {raw_value_channel4}") #print(f"Channel 4 (2.5->5.0): {raw_value_channel4}")
print(f"Channel 5 (5.0->10.): {raw_value_channel5}") #print(f"Channel 5 (5.0->10.): {raw_value_channel5}")
cursor.execute('''
INSERT INTO data_NPM_5channels (timestamp,PM_ch1, PM_ch2, PM_ch3, PM_ch4, PM_ch5) VALUES (?,?,?,?,?,?)'''
, (rtc_time_str,raw_value_channel1,raw_value_channel2,raw_value_channel3,raw_value_channel4,raw_value_channel5))
# Commit and close the connection
conn.commit()
break break
@@ -101,3 +134,4 @@ while True:
time.sleep(3) time.sleep(3)
exit() exit()
conn.close()

View File

@@ -5,7 +5,7 @@
| |\ | __/| | | | | |\ | __/| | | |
|_| \_|_| |_| |_| |_| \_|_| |_| |_|
Script to get NPM values Script to get NPM values (PM1, PM2.5 and PM10)
PM and the sensor temp/hum PM and the sensor temp/hum
And store them inside sqlite database And store them inside sqlite database
Uses RTC module for timing (from SQLite db) Uses RTC module for timing (from SQLite db)

View File

@@ -4,6 +4,7 @@
"boot_log": true, "boot_log": true,
"modem_config_mode": false, "modem_config_mode": false,
"NPM/get_data_v2.py": true, "NPM/get_data_v2.py": true,
"NPM/get_data_modbus.py":false,
"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": true,

View File

@@ -58,8 +58,21 @@
<div class="card text-dark bg-light"> <div class="card text-dark bg-light">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Consulter la base de donnée</h5> <h5 class="card-title">Consulter la base de donnée</h5>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM',10,false)">Mesures PM</button> <!-- Dropdown to select number of records -->
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',10,false)">Mesures Temp/Hum</button> <div class="d-flex align-items-center mb-3">
<label for="records_limit" class="form-label me-2">Nombre de mesures:</label>
<select id="records_limit" class="form-select w-auto">
<option value="10" selected>10 dernières</option>
<option value="20">20 dernières</option>
<option value="30">30 dernières</option>
</select>
</div>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM',getSelectedLimit(),false)">Mesures PM</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>
</div> </div>
</div> </div>
</div> </div>
@@ -68,8 +81,18 @@
<div class="card text-dark bg-light"> <div class="card text-dark bg-light">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Télécharger les données</h5> <h5 class="card-title">Télécharger les données</h5>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM',10,true)">Mesures PM</button> <!-- Date selection for download -->
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',10,true)">Mesures Temp/Hum</button> <div class="d-flex align-items-center gap-3 mb-3">
<label for="start_date" class="form-label">Date de début:</label>
<input type="date" id="start_date" class="form-control w-auto">
<label for="end_date" class="form-label">Date de fin:</label>
<input type="date" id="end_date" class="form-control w-auto">
</div>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM',10,true, getStartDate(), getEndDate())">Mesures PM</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',10,true, getStartDate(), getEndDate())">Mesures Temp/Hum</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',10,true, getStartDate(), getEndDate())">Mesures PM (5 canaux)</button>
</table> </table>
</div> </div>
@@ -123,11 +146,6 @@
window.onload = function() { window.onload = function() {
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json' fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
.then(response => response.json()) // Parse response as JSON .then(response => response.json()) // Parse response as JSON
@@ -168,11 +186,21 @@
// TABLE PM // TABLE PM
function get_data_sqlite(table, limit, download) { function get_data_sqlite(table, limit, download , startDate = "", endDate = "") {
console.log("Getting data for table mesure PM"); console.log(`Getting data for table: ${table}, limit: ${limit}, download: ${download}, start: ${startDate}, end: ${endDate}`);
// Construct URL parameters dynamically
let url = `launcher.php?type=table_mesure&table=${table}&limit=${limit}&download=${download}`;
// Add date parameters if downloading
if (download) {
url += `&start_date=${startDate}&end_date=${endDate}`;
}
console.log(url);
$.ajax({ $.ajax({
url: 'launcher.php?type=table_mesurePM&table='+table+'&limit='+limit+'&download='+download, url: url,
dataType: 'text', // Specify that you expect a JSON response dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs method: 'GET', // Use GET or POST depending on your needs
success: function(response) { success: function(response) {
@@ -207,6 +235,16 @@ function get_data_sqlite(table, limit, download) {
<th>Humidity (%)</th> <th>Humidity (%)</th>
<th>Pressure (hPa)</th> <th>Pressure (hPa)</th>
`; `;
} else if (table === "data_NPM_5channels") {
tableHTML += `
<th>Timestamp</th>
<th>PM_ch1 (nb/L)</th>
<th>PM_ch2 (nb/L)</th>
<th>PM_ch3 (nb/L)</th>
<th>PM_ch4 (nb/L)</th>
<th>PM_ch5 (nb/L)</th>
`;
} }
tableHTML += `</tr></thead><tbody>`; tableHTML += `</tr></thead><tbody>`;
@@ -233,6 +271,17 @@ function get_data_sqlite(table, limit, download) {
<td>${columns[3]}</td> <td>${columns[3]}</td>
`; `;
} }
else if (table === "data_NPM_5channels") {
tableHTML += `
<td>${columns[0]}</td>
<td>${columns[1]}</td>
<td>${columns[2]}</td>
<td>${columns[3]}</td>
<td>${columns[4]}</td>
<td>${columns[5]}</td>
`;
}
tableHTML += "</tr>"; tableHTML += "</tr>";
}); });
@@ -250,6 +299,19 @@ function get_data_sqlite(table, limit, download) {
} }
function getSelectedLimit() {
return document.getElementById("records_limit").value;
}
function getStartDate() {
return document.getElementById("start_date").value || "2025-01-01"; // Default to a safe date
}
function getEndDate() {
return document.getElementById("end_date").value || "2025-12-31"; // Default to a safe date
}
function downloadCSV(response, table) { function downloadCSV(response, table) {
let rows = response.trim().split("\n"); let rows = response.trim().split("\n");
@@ -257,9 +319,12 @@ function downloadCSV(response, table) {
// Add headers based on table type // Add headers based on table type
if (table === "data_NPM") { if (table === "data_NPM") {
csvContent += "Timestamp,PM1,PM2.5,PM10,Temperature (°C),Humidity (%)\n"; csvContent += "TimestampUTC,PM1,PM2.5,PM10,Temperature_sensor,Humidity_sensor\n";
} else if (table === "data_BME280") { } else if (table === "data_BME280") {
csvContent += "Timestamp,Temperature (°C),Humidity (%),Pressure (hPa)\n"; csvContent += "TimestampUTC,Temperature (°C),Humidity (%),Pressure (hPa)\n";
}
else if (table === "data_NPM_5channels") {
csvContent += "TimestampUTC,PM_ch1,PM_ch2,PM_ch3,PM_ch4,PM_ch5\n";
} }
// Format rows as CSV // Format rows as CSV

View File

@@ -203,14 +203,23 @@ if ($type == "BME280") {
} }
if ($type == "table_mesurePM") { if ($type == "table_mesure") {
$table=$_GET['table']; $table=$_GET['table'];
$limit=$_GET['limit']; $limit=$_GET['limit'];
$download=$_GET['download']; $download=$_GET['download'];
if ($download==="false") {
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read.py '.$table.' '.$limit; $command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read.py '.$table.' '.$limit;
$output = shell_exec($command); $output = shell_exec($command);
echo $output; echo $output;
} else{
$start_date=$_GET['start_date'];
$end_date=$_GET['end_date'];
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read_select_date.py '.$table.' '.$start_date.' '.$end_date;
$output = shell_exec($command);
echo $output;
}
} }
# SARA R4 COMMANDS # SARA R4 COMMANDS

View File

@@ -64,26 +64,33 @@ def load_config():
with open(CONFIG_FILE, "r") as f: with open(CONFIG_FILE, "r") as f:
return json.load(f) return json.load(f)
def run_script(script_name, interval): def run_script(script_name, interval, delay=0):
"""Run a script in a loop with a 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
while True: while True:
config = load_config() config = load_config()
if config.get(script_name, True): # Default to True if not found if config.get(script_name, True): # Default to True if not found
subprocess.run(["python3", script_path]) subprocess.run(["python3", script_path])
time.sleep(interval)
# 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) # Define scripts and their execution intervals (seconds)
SCRIPTS = [ SCRIPTS = [
("NPM/get_data_v2.py", 60), # Get NPM data every 60s ("RTC/save_to_db.py", 1, 0), # SAVE RTC time every 1 second, no delay
("loop/SARA_send_data_v2.py", 60), # Send data every 60 seconds ("NPM/get_data_v2.py", 60, 0), # Get NPM data every 60s, no delay
("RTC/save_to_db.py", 1), # SAVE RTC time every 1 second ("NPM/get_data_modbus.py", 10, 2), # Get NPM data (modbus 5 channels) every 10s, with 2s delay
("BME280/get_data_v2.py", 120) # Get BME280 data every 120 seconds ("loop/SARA_send_data_v2.py", 60, 1), # Send data every 60 seconds, with 2s delay
("BME280/get_data_v2.py", 120, 0) # Get BME280 data every 120 seconds, no delay
] ]
# Start threads for enabled scripts # Start threads for enabled scripts
for script_name, interval in SCRIPTS: for script_name, interval, delay in SCRIPTS:
thread = threading.Thread(target=run_script, args=(script_name, interval), daemon=True) thread = threading.Thread(target=run_script, args=(script_name, interval, delay), daemon=True)
thread.start() thread.start()
# Keep the main script running # Keep the main script running

View File

@@ -8,6 +8,13 @@
Script to read data from a sqlite database Script to read data from a sqlite database
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read.py data_NPM 10 /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read.py data_NPM 10
Available table are
data_NPM
data_NPM_5channels
data_BME280
data_envea
timestamp_table
''' '''
import sqlite3 import sqlite3

View File

@@ -0,0 +1,59 @@
'''
____ ___ _ _ _
/ ___| / _ \| | (_) |_ ___
\___ \| | | | | | | __/ _ \
___) | |_| | |___| | || __/
|____/ \__\_\_____|_|\__\___|
Script to read data from a sqlite database using start date and end date
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read_select_date.py data_NPM 2025-02-09 2025-02-11
Available table are
data_NPM
data_NPM_5channels
data_BME280
data_envea
timestamp_table
'''
import sqlite3
import sys
parameter = sys.argv[1:] # Exclude the script name
#print("Parameters received:")
table_name=parameter[0]
start_date=parameter[1]
end_date=parameter[2]
# Convert to full timestamp range
start_timestamp = f"{start_date} 00:00:00"
end_timestamp = f"{end_date} 23:59:59"
# Connect to the SQLite database
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
# Retrieve the last 10 sensor readings
#cursor.execute("SELECT * FROM data_NPM ORDER BY timestamp DESC LIMIT 10")
#cursor.execute("SELECT * FROM data_BME280 ORDER BY timestamp DESC LIMIT 10")
#cursor.execute("SELECT * FROM timestamp_table")
if table_name == "timestamp_table":
cursor.execute("SELECT * FROM timestamp_table")
else:
query = f"SELECT * FROM {table_name} WHERE timestamp BETWEEN ? AND ? ORDER BY timestamp ASC"
cursor.execute(query, (start_timestamp, end_timestamp))
rows = cursor.fetchall()
rows.reverse() # Reverse the order in Python (to get ascending order)
# Display the results
for row in rows:
print(row)
# Close the database connection
conn.close()