This commit is contained in:
Your Name
2025-03-28 15:59:54 +01:00
parent c6b138220e
commit 4653fe44e3
8 changed files with 247 additions and 44 deletions

View File

@@ -5,7 +5,7 @@ Version Pro du ModuleAir avec CM4, SaraR4 et ecran Matrix LED p2 64x64.
## General ## General
``` ```
sudo apt update sudo apt update
sudo apt install git gh apache2 php php-sqlite3 python3 python3-pip jq g++ autossh i2c-tools python3-smbus -y sudo apt install git gh apache2 sqlite3 php php-sqlite3 python3 python3-pip jq g++ autossh i2c-tools python3-smbus -y
sudo pip3 install pyserial requests sensirion-shdlc-sfa3x RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil --break-system-packages sudo pip3 install pyserial requests sensirion-shdlc-sfa3x RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil --break-system-packages
sudo git clone http://gitea.aircarto.fr/PaulVua/moduleair_pro_4g.git /var/www/moduleair_pro_4g sudo git clone http://gitea.aircarto.fr/PaulVua/moduleair_pro_4g.git /var/www/moduleair_pro_4g
sudo mkdir /var/www/moduleair_pro_4g/logs sudo mkdir /var/www/moduleair_pro_4g/logs

View File

@@ -6,7 +6,6 @@
# @reboot /var/www/moduleair_pro_4g/boot_hotspot.sh >> /var/www/moduleair_pro_4g/logs/app.log 2>&1 # @reboot /var/www/moduleair_pro_4g/boot_hotspot.sh >> /var/www/moduleair_pro_4g/logs/app.log 2>&1
OUTPUT_FILE="/var/www/moduleair_pro_4g/wifi_list.csv" OUTPUT_FILE="/var/www/moduleair_pro_4g/wifi_list.csv"
JSON_FILE="/var/www/moduleair_pro_4g/config.json"
echo "-------------------" echo "-------------------"
echo "-------------------" echo "-------------------"
@@ -14,16 +13,18 @@ echo "-------------------"
echo "NebuleAir pro started at $(date)" echo "NebuleAir pro started at $(date)"
echo "getting SARA R4 serial number" echo "getting RPI serial number"
# Get the last 8 characters of the serial number and write to text file # Get the last 8 characters of the serial number and write to text file
serial_number=$(cat /proc/cpuinfo | grep Serial | awk '{print substr($3, length($3) - 7)}') serial_number=$(cat /proc/cpuinfo | grep Serial | awk '{print substr($3, length($3) - 7)}')
# Define the JSON file path # Define the JSON file path
# Use jq to update the "deviceID" in the JSON file # update Sqlite database
jq --arg serial_number "$serial_number" '.deviceID = $serial_number' "$JSON_FILE" > temp.json && mv temp.json "$JSON_FILE" echo "Updating SQLite database with device ID: $serial_number"
sqlite3 /var/www/moduleair_pro_4g/sqlite/sensors.db "UPDATE config_table SET value='$serial_number' WHERE key='deviceID';"
echo "id: $serial_number" echo "id: $serial_number"
#get the SSH port for tunneling #get the SSH port for tunneling
SSH_TUNNEL_PORT=$(jq -r '.sshTunnel_port' "$JSON_FILE") SSH_TUNNEL_PORT=$(sqlite3 /var/www/moduleair_pro_4g/sqlite/sensors.db "SELECT value FROM config_table WHERE key='sshTunnel_port'")
#need to wait for the network manager to be ready #need to wait for the network manager to be ready
sleep 20 sleep 20
@@ -39,10 +40,10 @@ if [ "$STATE" == "30 (disconnected)" ]; then
nmcli -f SSID,SIGNAL,SECURITY device wifi list | awk 'BEGIN { OFS=","; print "SSID,SIGNAL,SECURITY" } NR>1 { print $1,$2,$3 }' > "$OUTPUT_FILE" nmcli -f SSID,SIGNAL,SECURITY device wifi list | awk 'BEGIN { OFS=","; print "SSID,SIGNAL,SECURITY" } NR>1 { print $1,$2,$3 }' > "$OUTPUT_FILE"
# Start the hotspot # Start the hotspot
echo "Starting hotspot..." echo "Starting hotspot..."
sudo nmcli device wifi hotspot ifname wlan0 ssid nebuleair_pro password nebuleaircfg sudo nmcli device wifi hotspot ifname wlan0 ssid moduleair_pro password moduleaircfg
# Update JSON to reflect hotspot mode # Update JSON to reflect hotspot mode
jq --arg status "hotspot" '.WIFI_status = $status' "$JSON_FILE" > temp.json && mv temp.json "$JSON_FILE" sqlite3 /var/www/moduleair_pro_4g/sqlite/sensors.db "UPDATE config_table SET value='hotspot' WHERE key='WIFI_status'"
else else
@@ -50,10 +51,8 @@ else
CONN_SSID=$(nmcli -g GENERAL.CONNECTION device show wlan0) CONN_SSID=$(nmcli -g GENERAL.CONNECTION device show wlan0)
echo "Connection: $CONN_SSID" echo "Connection: $CONN_SSID"
#update config JSON file # Update SQLite to reflect hotspot mode
jq --arg status "connected" '.WIFI_status = $status' "$JSON_FILE" > temp.json && mv temp.json "$JSON_FILE" sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db "UPDATE config_table SET value='connected' WHERE key='WIFI_status'"
sudo chmod 777 "$JSON_FILE"
# Lancer le tunnel SSH # Lancer le tunnel SSH
#echo "Démarrage du tunnel SSH sur le port $SSH_TUNNEL_PORT..." #echo "Démarrage du tunnel SSH sur le port $SSH_TUNNEL_PORT..."

View File

@@ -54,7 +54,7 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col-sm-4"> <div class="col-sm-6">
<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>
@@ -67,19 +67,20 @@
<option value="30">30 dernières</option> <option value="30">30 dernières</option>
</select> </select>
</div> </div>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM',getSelectedLimit(),false)">Mesures PM</button> <button class="btn btn-primary mt-2" onclick="get_data_sqlite('data_NPM',getSelectedLimit(),false)">Mesures PM</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_CO2',getSelectedLimit(),false)">Mesures CO2</button> <button class="btn btn-primary mt-2" onclick="get_data_sqlite('data_CO2',getSelectedLimit(),false)">Mesures CO2</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)">Mesures Temp/Hum</button> <button class="btn btn-primary mt-2" 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 mt-2" 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 mt-2" 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> <button class="btn btn-primary mt-2" onclick="get_data_sqlite('data_sensirionSFA30',getSelectedLimit(),false)">Formaldheide</button>
<button class="btn btn-warning mt-2" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)">Timestamp Table</button>
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-8"> <div class="col-sm-6">
<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>
@@ -275,6 +276,11 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
<th>Timestamp</th> <th>Timestamp</th>
<th>CO2</th> <th>CO2</th>
`; `;
}else if (table === "data_sensirionSFA30") {
tableHTML += `
<th>Timestamp</th>
<th>CH2O</th>
`;
} }
tableHTML += `</tr></thead><tbody>`; tableHTML += `</tr></thead><tbody>`;
@@ -331,8 +337,14 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
<td>${columns[0]}</td> <td>${columns[0]}</td>
<td>${columns[1]}</td> <td>${columns[1]}</td>
`; `;
}else if (table === "data_sensirionSFA30") {
tableHTML += `
<td>${columns[0]}</td>
<td>${columns[1]}</td>
`;
} }
tableHTML += "</tr>"; tableHTML += "</tr>";
}); });

144
html/matrix_led.html Normal file
View File

@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ModuleAir</title>
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<script src="assets/js/chart.js"></script> <!-- Local Chart.js -->
<style>
body {
overflow-x: hidden;
}
#sidebar a.nav-link {
position: relative;
display: flex;
align-items: center;
}
#sidebar a.nav-link:hover {
background-color: rgba(0, 0, 0, 0.5);
}
#sidebar a.nav-link svg {
margin-right: 8px; /* Add spacing between icons and text */
}
#sidebar {
transition: transform 0.3s ease-in-out;
}
.offcanvas-backdrop {
z-index: 1040;
}
</style>
</head>
<body>
<!-- Topbar -->
<span id="topbar"></span>
<!-- Sidebar Offcanvas for Mobile -->
<div class="offcanvas offcanvas-start text-white bg-dark" tabindex="-1" id="sidebarOffcanvas" aria-labelledby="sidebarOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="sidebarOffcanvasLabel">NebuleAir</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" id="sidebar_mobile">
</div>
</div>
<div class="container-fluid mt-5">
<div class="row">
<aside class="col-md-2 col-lg-1 d-none d-md-block vh-100 position-fixed bg-dark text-white" id="sidebar">
</aside>
<!-- Main content -->
<main class="col-md-9 ms-sm-auto col-lg-10 offset-md-3 offset-lg-2 px-md-4">
<h1 class="mt-4">Ecran LED</h1>
<p>Votre capteur est équipé d'un écran LED.</p>
<div class="row mb-3">
</div>
</main>
</div>
</div>
<!-- JAVASCRIPT -->
<!-- Link Ajax locally -->
<script src="assets/jquery/jquery-3.7.1.min.js"></script>
<!-- Link Bootstrap JS and Popper.js locally -->
<script src="assets/js/bootstrap.bundle.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const elementsToLoad = [
{ id: 'topbar', file: 'topbar.html' },
{ id: 'sidebar', file: 'sidebar.html' },
{ id: 'sidebar_mobile', file: 'sidebar.html' }
];
elementsToLoad.forEach(({ id, file }) => {
fetch(file)
.then(response => response.text())
.then(data => {
const element = document.getElementById(id);
if (element) {
element.innerHTML = data;
}
})
.catch(error => console.error(`Error loading ${file}:`, error));
});
});
window.onload = function() {
//NEW way to get config (SQLite)
$.ajax({
url: 'launcher.php?type=get_config_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config table:");
console.log(response);
//device name_side bar
const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => {
element.innerText = response.deviceName;
});
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX
//get local RTC
$.ajax({
url: 'launcher.php?type=RTC_time',
dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Local RTC: " + response);
const RTC_Element = document.getElementById("RTC_time");
RTC_Element.textContent = response;
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
</script>
</body>
</html>

View File

@@ -13,6 +13,12 @@
</svg> </svg>
Capteurs Capteurs
</a> </a>
<a class="nav-link text-white" href="matrix_led.html">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-display" viewBox="0 0 16 16">
<path d="M0 4s0-2 2-2h12s2 0 2 2v6s0 2-2 2h-4q0 1 .25 1.5H11a.5.5 0 0 1 0 1H5a.5.5 0 0 1 0-1h.75Q6 13 6 12H2s-2 0-2-2zm1.398-.855a.76.76 0 0 0-.254.302A1.5 1.5 0 0 0 1 4.01V10c0 .325.078.502.145.602q.105.156.302.254a1.5 1.5 0 0 0 .538.143L2.01 11H14c.325 0 .502-.078.602-.145a.76.76 0 0 0 .254-.302 1.5 1.5 0 0 0 .143-.538L15 9.99V4c0-.325-.078-.502-.145-.602a.76.76 0 0 0-.302-.254A1.5 1.5 0 0 0 13.99 3H2c-.325 0-.502.078-.602.145"/>
</svg>
Matrix LED
</a>
<a class="nav-link text-white" href="database.html"> <a class="nav-link text-white" href="database.html">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-database" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-database" viewBox="0 0 16 16">
<path d="M4.318 2.687C5.234 2.271 6.536 2 8 2s2.766.27 3.682.687C12.644 3.125 13 3.627 13 4c0 .374-.356.875-1.318 1.313C10.766 5.729 9.464 6 8 6s-2.766-.27-3.682-.687C3.356 4.875 3 4.373 3 4c0-.374.356-.875 1.318-1.313M13 5.698V7c0 .374-.356.875-1.318 1.313C10.766 8.729 9.464 9 8 9s-2.766-.27-3.682-.687C3.356 7.875 3 7.373 3 7V5.698c.271.202.58.378.904.525C4.978 6.711 6.427 7 8 7s3.022-.289 4.096-.777A5 5 0 0 0 13 5.698M14 4c0-1.007-.875-1.755-1.904-2.223C11.022 1.289 9.573 1 8 1s-3.022.289-4.096.777C2.875 2.245 2 2.993 2 4v9c0 1.007.875 1.755 1.904 2.223C4.978 15.71 6.427 16 8 16s3.022-.289 4.096-.777C13.125 14.755 14 14.007 14 13zm-1 4.698V10c0 .374-.356.875-1.318 1.313C10.766 11.729 9.464 12 8 12s-2.766-.27-3.682-.687C3.356 10.875 3 10.373 3 10V8.698c.271.202.58.378.904.525C4.978 9.71 6.427 10 8 10s3.022-.289 4.096-.777A5 5 0 0 0 13 8.698m0 3V13c0 .374-.356.875-1.318 1.313C10.766 14.729 9.464 15 8 15s-2.766-.27-3.682-.687C3.356 13.875 3 13.373 3 13v-1.302c.271.202.58.378.904.525C4.978 12.71 6.427 13 8 13s3.022-.289 4.096-.777c.324-.147.633-.323.904-.525"/> <path d="M4.318 2.687C5.234 2.271 6.536 2 8 2s2.766.27 3.682.687C12.644 3.125 13 3.627 13 4c0 .374-.356.875-1.318 1.313C10.766 5.729 9.464 6 8 6s-2.766-.27-3.682-.687C3.356 4.875 3 4.373 3 4c0-.374.356-.875 1.318-1.313M13 5.698V7c0 .374-.356.875-1.318 1.313C10.766 8.729 9.464 9 8 9s-2.766-.27-3.682-.687C3.356 7.875 3 7.373 3 7V5.698c.271.202.58.378.904.525C4.978 6.711 6.427 7 8 7s3.022-.289 4.096-.777A5 5 0 0 0 13 5.698M14 4c0-1.007-.875-1.755-1.904-2.223C11.022 1.289 9.573 1 8 1s-3.022.289-4.096.777C2.875 2.245 2 2.993 2 4v9c0 1.007.875 1.755 1.904 2.223C4.978 15.71 6.427 16 8 16s3.022-.289 4.096-.777C13.125 14.755 14 14.007 14 13zm-1 4.698V10c0 .374-.356.875-1.318 1.313C10.766 11.729 9.464 12 8 12s-2.766-.27-3.682-.687C3.356 10.875 3 10.373 3 10V8.698c.271.202.58.378.904.525C4.978 9.71 6.427 10 8 10s3.022-.289 4.096-.777A5 5 0 0 0 13 8.698m0 3V13c0 .374-.356.875-1.318 1.313C10.766 14.729 9.464 15 8 15s-2.766-.27-3.682-.687C3.356 13.875 3 13.373 3 13v-1.302c.271.202.58.378.904.525C4.978 12.71 6.427 13 8 13s3.022-.289 4.096-.777c.324-.147.633-.323.904-.525"/>

View File

@@ -523,7 +523,7 @@ try:
rows = cursor.fetchall() rows = cursor.fetchall()
# Exclude the timestamp column (assuming first column is timestamp) # Exclude the timestamp column (assuming first column is timestamp)
data_values = [row[1:] for row in rows] # Exclude timestamp data_values = [row[2:] for row in rows] # Exclude timestamp
# Compute column-wise average # Compute column-wise average
num_columns = len(data_values[0]) num_columns = len(data_values[0])
averages = [round(sum(col) / len(col),1) for col in zip(*data_values)] averages = [round(sum(col) / len(col),1) for col in zip(*data_values)]

View File

@@ -12,6 +12,9 @@ sudo /var/www/moduleair_pro_4g/matrix/test_forms --led-no-hardware-pulse
Pour compiler: Pour compiler:
g++ -Iinclude -Llib test_forms.cc -o test_forms -lrgbmatrix g++ -Iinclude -Llib test_forms.cc -o test_forms -lrgbmatrix
Pour le lancer:
sudo /var/www/moduleair_pro_4g/matrix/test_forms
*/ */
#include "led-matrix.h" #include "led-matrix.h"

View File

@@ -1,38 +1,77 @@
''' '''
____ _____ _ _____ ___
/ ___|| ___/ \ |___ / / _ \
\___ \| |_ / _ \ |_ \| | | |
___) | _/ ___ \ ___) | |_| |
|____/|_|/_/ \_\____/ \___/
Read data from sensor (only once) Read data from sensor (only once)
/usr/bin/python3 /var/www/moduleair_pro_4g/sensirion/SFA30_read.py /usr/bin/python3 /var/www/moduleair_pro_4g/sensirion/SFA30_read.py
''' '''
import time import time
import json import json
import sqlite3
from sensirion_shdlc_driver import ShdlcSerialPort, ShdlcConnection from sensirion_shdlc_driver import ShdlcSerialPort, ShdlcConnection
from sensirion_shdlc_sfa3x import Sfa3xShdlcDevice from sensirion_shdlc_sfa3x import Sfa3xShdlcDevice
# Connect to the device with default settings: # Connect to the SQLite database
# - baudrate: 115200 DB_PATH = "/var/www/moduleair_pro_4g/sqlite/sensors.db"
# - slave address: 0 SERIAL_PORT = '/dev/ttyAMA5'
with ShdlcSerialPort(port='/dev/ttyAMA5', baudrate=115200) as port:
device = Sfa3xShdlcDevice(ShdlcConnection(port), slave_address=0)
device.device_reset()
# Print device information
#print("Device Marking: {}".format(device.get_device_marking()))
# Start measurement try:
device.start_measurement() # Connect to the SQLite database
#print("Measurement started... ") #print("Connecting to database...")
conn = sqlite3.connect(DB_PATH)
time.sleep(5.) cursor = conn.cursor()
hcho, humidity, temperature = device.read_measured_values() # Connect to the device with default settings:
# - baudrate: 115200
# - slave address: 0
#print(f"Connecting to Sensirion SFA3X on {SERIAL_PORT}...")
with ShdlcSerialPort(port=SERIAL_PORT, baudrate=115200) as port:
device = Sfa3xShdlcDevice(ShdlcConnection(port), slave_address=0)
device.device_reset()
# Prepare data as a JSON object # Print device information
data = { #print("Device Marking: {}".format(device.get_device_marking()))
"formaldehyde_ppb": hcho.ppb,
"humidity_percent": humidity.percent_rh, # Start measurement
"temperature_celsius": temperature.degrees_celsius, device.start_measurement()
} #print("Measurement started... ")
# Convert the dictionary to a JSON string time.sleep(5.)
json_output = json.dumps(data, indent=4) hcho, humidity, temperature = device.read_measured_values()
print(json_output)
# Prepare data as a JSON object
data = {
"formaldehyde_ppb": hcho.ppb,
"humidity_percent": humidity.percent_rh,
"temperature_celsius": temperature.degrees_celsius,
}
# Convert the dictionary to a JSON string
json_output = json.dumps(data, indent=4)
#print(json_output)
#print("Getting RTC time...")
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
row = cursor.fetchone()
rtc_time_str = row[1] if row else time.strftime("%Y-%m-%d %H:%M:%S")
# Save to database
#print(f"Saving readings to database: HCHO={hcho.ppb} ppb, Time={rtc_time_str}")
cursor.execute('''
INSERT INTO data_sensirionSFA30 (timestamp, CH2O) VALUES (?, ?)
''', (rtc_time_str, hcho.ppb))
conn.commit()
except Exception as e:
print(f"Error: {e}")
finally:
# Close database connection if it exists
if 'conn' in locals():
conn.close()
#print("Database connection closed")