v1.5.2: page capteurs NPM via get_data_modbus_v3.py --dry-run

- NPM: mode --dry-run (print JSON sans ecriture en base)
- launcher.php: endpoint npm appelle get_data_modbus_v3.py --dry-run
- sensors.html: affichage PM + temp + humidite + status NPM decode
- Suppression unite ug/m3 sur le champ status

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
PaulVua
2026-03-18 13:10:56 +01:00
parent e733cd27e8
commit b3c019c27b
5 changed files with 108 additions and 47 deletions

View File

@@ -40,6 +40,9 @@ import crcmod
import sqlite3 import sqlite3
import time import time
# Dry-run mode: print JSON output without writing to database
dry_run = "--dry-run" in sys.argv
# Connect to the SQLite database # Connect to the SQLite database
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db") conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor() cursor = conn.cursor()
@@ -207,15 +210,29 @@ except Exception as e:
# Variables already set to -1 at the beginning # Variables already set to -1 at the beginning
finally: finally:
# Always save data to database, even if all values are -1 if dry_run:
cursor.execute(''' # Print JSON output without writing to database
INSERT INTO data_NPM_5channels (timestamp,PM_ch1, PM_ch2, PM_ch3, PM_ch4, PM_ch5) VALUES (?,?,?,?,?,?)''' result = {
, (rtc_time_str, channel_1, channel_2, channel_3, channel_4, channel_5)) "PM1": pm1_10s,
"PM25": pm25_10s,
"PM10": pm10_10s,
"temperature": temperature,
"humidity": relative_humidity,
"npm_status": npm_status,
"npm_status_hex": f"0x{npm_status:02X}"
}
print(json.dumps(result))
else:
# Always save data to database, even if all values are 0
cursor.execute('''
INSERT INTO data_NPM_5channels (timestamp,PM_ch1, PM_ch2, PM_ch3, PM_ch4, PM_ch5) VALUES (?,?,?,?,?,?)'''
, (rtc_time_str, channel_1, channel_2, channel_3, channel_4, channel_5))
cursor.execute(''' cursor.execute('''
INSERT INTO data_NPM (timestamp,PM1, PM25, PM10, temp_npm, hum_npm, npm_status) VALUES (?,?,?,?,?,?,?)''' INSERT INTO data_NPM (timestamp,PM1, PM25, PM10, temp_npm, hum_npm, npm_status) VALUES (?,?,?,?,?,?,?)'''
, (rtc_time_str, pm1_10s, pm25_10s, pm10_10s, temperature, relative_humidity, npm_status)) , (rtc_time_str, pm1_10s, pm25_10s, pm10_10s, temperature, relative_humidity, npm_status))
# Commit and close the connection
conn.commit()
# Commit and close the connection
conn.commit()
conn.close() conn.close()

View File

@@ -1 +1 @@
1.5.1 1.5.2

View File

@@ -1,5 +1,25 @@
{ {
"versions": [ "versions": [
{
"version": "1.5.2",
"date": "2026-03-18",
"changes": {
"features": [
"Page capteurs: lecture NPM via get_data_modbus_v3.py --dry-run (meme script que le timer)",
"Page capteurs: affichage temperature et humidite interne du NPM",
"Page capteurs: decodage npm_status avec flags d'erreur individuels"
],
"improvements": [
"NPM get_data_modbus_v3.py: mode --dry-run (print JSON sans ecriture en base)",
"Page capteurs: status NPM affiche en vert (OK) ou orange/rouge (erreurs decodees)"
],
"fixes": [
"Page capteurs: suppression unite ug/m3 sur le champ message/status"
],
"compatibility": []
},
"notes": "La page capteurs utilise maintenant le meme script Modbus que le timer systemd, en mode dry-run pour eviter les conflits d'ecriture SQLite. Le status NPM est decode bit par bit."
},
{ {
"version": "1.5.1", "version": "1.5.1",
"date": "2026-03-18", "date": "2026-03-18",

View File

@@ -805,8 +805,7 @@ if ($type == "reboot") {
} }
if ($type == "npm") { if ($type == "npm") {
$port=$_GET['port']; $command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_modbus_v3.py --dry-run';
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data.py ' . $port;
$output = shell_exec($command); $output = shell_exec($command);
echo $output; echo $output;
} }

View File

@@ -117,54 +117,79 @@
$("#loading_" + port).show(); $("#loading_" + port).show();
$.ajax({ $.ajax({
url: 'launcher.php?type=npm&port=' + port, url: 'launcher.php?type=npm',
dataType: 'json', // Specify that you expect a JSON response dataType: 'json',
method: 'GET', // Use GET or POST depending on your needs method: 'GET',
success: function (response) { success: function (response) {
console.log(response); console.log(response);
const tableBody = document.getElementById("data-table-body_" + port); const tableBody = document.getElementById("data-table-body_" + port);
tableBody.innerHTML = ""; tableBody.innerHTML = "";
$("#loading_" + port).hide(); $("#loading_" + port).hide();
// Create an array of the desired keys
const keysToShow = ["PM1", "PM25", "PM10", "message"]; // PM values
// Error messages mapping const pmKeys = ["PM1", "PM25", "PM10"];
const errorMessages = { pmKeys.forEach(key => {
"notReady": "Sensor is not ready", if (response[key] !== undefined) {
"fanError": "Fan malfunction detected",
"laserError": "Laser malfunction detected",
"heatError": "Heating system error",
"t_rhError": "Temperature/Humidity sensor error",
"memoryError": "Memory failure detected",
"degradedState": "Sensor in degraded state"
};
// Add only the specified elements to the table
keysToShow.forEach(key => {
if (response[key] !== undefined) { // Check if the key exists in the response
const value = response[key];
$("#data-table-body_" + port).append(` $("#data-table-body_" + port).append(`
<tr> <tr>
<td>${key}</td> <td>${key}</td>
<td>${value} µg/m³</td> <td>${response[key]} µg/m³</td>
</tr> </tr>
`); `);
} }
}); });
// Check for errors and add them to the table // Temperature & humidity
Object.keys(errorMessages).forEach(errorKey => { if (response.temperature !== undefined) {
if (response[errorKey] === 1) { $("#data-table-body_" + port).append(`
$("#data-table-body_" + port).append(` <tr><td>Temperature</td><td>${response.temperature} °C</td></tr>
<tr class="error-row"> `);
<td><b>${errorKey}</b></td> }
<td style="color: red;">⚠ ${errorMessages[errorKey]}</td> if (response.humidity !== undefined) {
</tr> $("#data-table-body_" + port).append(`
`); <tr><td>Humidity</td><td>${response.humidity} %</td></tr>
} `);
}); }
// NPM status decoded
if (response.npm_status !== undefined) {
const status = response.npm_status;
const statusText = status === 0 ? "OK" : response.npm_status_hex;
const statusColor = status === 0 ? "green" : "orange";
$("#data-table-body_" + port).append(`
<tr>
<td>Status</td>
<td style="color: ${statusColor}; font-weight: bold;">${statusText}</td>
</tr>
`);
// Decode individual error bits
const statusFlags = {
0x01: "Sleep mode",
0x02: "Degraded mode",
0x04: "Not ready",
0x08: "Heater error",
0x10: "THP sensor error",
0x20: "Fan error",
0x40: "Memory error",
0x80: "Laser error"
};
Object.entries(statusFlags).forEach(([mask, label]) => {
if (status & mask) {
$("#data-table-body_" + port).append(`
<tr class="error-row">
<td></td>
<td style="color: red;">⚠ ${label}</td>
</tr>
`);
}
});
}
}, },
error: function (xhr, status, error) { error: function (xhr, status, error) {
console.error('AJAX request failed:', status, error); console.error('AJAX request failed:', status, error);
$("#loading_" + port).hide();
} }
}); });
} }