Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb93ba49bd | ||
|
|
3804a52fda | ||
|
|
ee0577c504 | ||
|
|
72fbbb82a1 | ||
|
|
5b3769769d | ||
|
|
6be18b5bde | ||
|
|
7619caffc4 | ||
|
|
85596c3882 | ||
|
|
6a00ab85d9 | ||
|
|
2ff47dc877 | ||
|
|
d2a3eafaa1 | ||
|
|
6706b22f21 | ||
|
|
ffe13d3639 |
@@ -72,6 +72,7 @@ channel_4 = 0
|
|||||||
channel_5 = 0
|
channel_5 = 0
|
||||||
relative_humidity = 0
|
relative_humidity = 0
|
||||||
temperature = 0
|
temperature = 0
|
||||||
|
npm_status = 0xFF # Default: all errors (will be overwritten if read succeeds)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Initialize serial port
|
# Initialize serial port
|
||||||
@@ -176,6 +177,29 @@ try:
|
|||||||
#print(f"Internal Relative Humidity: {relative_humidity} %")
|
#print(f"Internal Relative Humidity: {relative_humidity} %")
|
||||||
#print(f"Internal temperature: {temperature} °C")
|
#print(f"Internal temperature: {temperature} °C")
|
||||||
|
|
||||||
|
# Read NPM status register (register 19 = 0x13, 1 register)
|
||||||
|
# Modbus request: slave=0x01, func=0x03, addr=0x0013, qty=0x0001
|
||||||
|
status_request = b'\x01\x03\x00\x13\x00\x01'
|
||||||
|
status_crc = crc16(status_request)
|
||||||
|
status_request += bytes([status_crc & 0xFF, (status_crc >> 8) & 0xFF])
|
||||||
|
|
||||||
|
ser.flushInput()
|
||||||
|
ser.write(status_request)
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# Response: addr(1) + func(1) + byte_count(1) + data(2) + crc(2) = 7 bytes
|
||||||
|
status_response = ser.read(7)
|
||||||
|
if len(status_response) == 7:
|
||||||
|
status_recv_crc = int.from_bytes(status_response[-2:], byteorder='little')
|
||||||
|
status_calc_crc = crc16(status_response[:-2])
|
||||||
|
if status_recv_crc == status_calc_crc:
|
||||||
|
npm_status = int.from_bytes(status_response[3:5], byteorder='big') & 0xFF
|
||||||
|
print(f"NPM status: 0x{npm_status:02X} ({npm_status})")
|
||||||
|
else:
|
||||||
|
print("[WARNING] NPM status CRC check failed, keeping default")
|
||||||
|
else:
|
||||||
|
print(f"[WARNING] NPM status incomplete response ({len(status_response)} bytes)")
|
||||||
|
|
||||||
ser.close()
|
ser.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -189,8 +213,8 @@ finally:
|
|||||||
, (rtc_time_str, channel_1, channel_2, channel_3, channel_4, channel_5))
|
, (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) 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))
|
, (rtc_time_str, pm1_10s, pm25_10s, pm10_10s, temperature, relative_humidity, npm_status))
|
||||||
|
|
||||||
# Commit and close the connection
|
# Commit and close the connection
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
{
|
{
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.5.0",
|
||||||
|
"date": "2026-03-18",
|
||||||
|
"changes": {
|
||||||
|
"features": [
|
||||||
|
"Payload UDP Miotiq: byte 66 error_flags (erreurs systeme RTC/capteurs)",
|
||||||
|
"Payload UDP Miotiq: byte 67 npm_status (registre status NextPM)",
|
||||||
|
"Payload UDP Miotiq: byte 68 device_status (etat general du boitier, specification)",
|
||||||
|
"Methodes SensorPayload: set_error_flags(), set_npm_status(), set_device_status()"
|
||||||
|
],
|
||||||
|
"improvements": [
|
||||||
|
"Initialisation bytes 66-68 a 0x00 au lieu de 0xFF pour eviter faux positifs cote serveur",
|
||||||
|
"Escalade erreur UDP: si PDP reset echoue, notification WiFi + hardware reboot + exit"
|
||||||
|
],
|
||||||
|
"fixes": [],
|
||||||
|
"compatibility": [
|
||||||
|
"Necessite mise a jour du parser Miotiq pour decoder les bytes 66-68 (error_flags, npm_status, device_status)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": "Ajout de registres d'erreur et d'etat dans la payload UDP (bytes 66-68). Les bytes de status sont initialises a 0x00 (aucune erreur) au lieu de 0xFF. Le flag RTC est implemente, les autres flags seront actives progressivement."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "1.4.6",
|
"version": "1.4.6",
|
||||||
"date": "2026-03-17",
|
"date": "2026-03-17",
|
||||||
|
|||||||
@@ -612,8 +612,23 @@ window.onload = function() {
|
|||||||
// Compare RTC time with browser time
|
// Compare RTC time with browser time
|
||||||
const alertContainer = document.getElementById("alert_container");
|
const alertContainer = document.getElementById("alert_container");
|
||||||
alertContainer.innerHTML = "";
|
alertContainer.innerHTML = "";
|
||||||
|
const rtcInput = document.getElementById("RTC_utc_time");
|
||||||
|
|
||||||
if (response.rtc_module_time) {
|
if (response.rtc_module_time === 'not connected' || !response.rtc_module_time) {
|
||||||
|
// RTC module disconnected
|
||||||
|
rtcInput.classList.add('border-danger', 'text-danger');
|
||||||
|
rtcInput.classList.remove('border-primary');
|
||||||
|
alertContainer.innerHTML = `
|
||||||
|
<div class="alert alert-danger d-flex align-items-center" role="alert">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-exclamation-triangle-fill me-2 flex-shrink-0" viewBox="0 0 16 16">
|
||||||
|
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.436-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<strong>Module RTC deconnecte !</strong><br>
|
||||||
|
Verifiez la pile du module DS3231 et les cables I2C.
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
} else {
|
||||||
const rtcDate = new Date(response.rtc_module_time + ' UTC');
|
const rtcDate = new Date(response.rtc_module_time + ' UTC');
|
||||||
const timeDiff = Math.abs(Math.round((browserDate - rtcDate) / 1000));
|
const timeDiff = Math.abs(Math.round((browserDate - rtcDate) / 1000));
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ async function selfTestSequence() {
|
|||||||
addSelfTestLog(`Device ID: ${selfTestReport.deviceId}`);
|
addSelfTestLog(`Device ID: ${selfTestReport.deviceId}`);
|
||||||
addSelfTestLog(`Device Name: ${selfTestReport.deviceName}`);
|
addSelfTestLog(`Device Name: ${selfTestReport.deviceName}`);
|
||||||
addSelfTestLog(`Modem Version: ${selfTestReport.modemVersion}`);
|
addSelfTestLog(`Modem Version: ${selfTestReport.modemVersion}`);
|
||||||
addSelfTestLog(`System Time (RTC): ${selfTestReport.systemTime}`);
|
addSelfTestLog(`RTC Time: ${selfTestReport.systemTime}`);
|
||||||
addSelfTestLog(`Browser Time: ${new Date().toLocaleString()}`);
|
addSelfTestLog(`Browser Time: ${new Date().toLocaleString()}`);
|
||||||
addSelfTestLog(`GPS: ${selfTestReport.latitude}, ${selfTestReport.longitude}`);
|
addSelfTestLog(`GPS: ${selfTestReport.latitude}, ${selfTestReport.longitude}`);
|
||||||
addSelfTestLog('────────────────────────────────────────────────────────');
|
addSelfTestLog('────────────────────────────────────────────────────────');
|
||||||
@@ -449,16 +449,19 @@ async function selfTestSequence() {
|
|||||||
updateTestStatus(sensor.id, 'Failed', 'RTC module not connected', 'bg-danger');
|
updateTestStatus(sensor.id, 'Failed', 'RTC module not connected', 'bg-danger');
|
||||||
testsFailed++;
|
testsFailed++;
|
||||||
} else if (rtcResult.rtc_module_time) {
|
} else if (rtcResult.rtc_module_time) {
|
||||||
const timeDiff = rtcResult.time_difference_seconds;
|
// Compare RTC with browser time (more reliable than system time)
|
||||||
if (typeof timeDiff === 'number' && Math.abs(timeDiff) <= 60) {
|
const rtcDate = new Date(rtcResult.rtc_module_time + ' UTC');
|
||||||
updateTestStatus(sensor.id, 'Passed', `${rtcResult.rtc_module_time} (sync OK, diff: ${timeDiff}s)`, 'bg-success');
|
const browserDate = new Date();
|
||||||
|
const timeDiff = Math.abs(Math.round((browserDate - rtcDate) / 1000));
|
||||||
|
|
||||||
|
if (timeDiff <= 60) {
|
||||||
|
updateTestStatus(sensor.id, 'Passed', `${rtcResult.rtc_module_time} (sync OK vs navigateur, ecart: ${timeDiff}s)`, 'bg-success');
|
||||||
testsPassed++;
|
testsPassed++;
|
||||||
} else if (typeof timeDiff === 'number') {
|
|
||||||
updateTestStatus(sensor.id, 'Warning', `${rtcResult.rtc_module_time} (out of sync: ${timeDiff}s)`, 'bg-warning');
|
|
||||||
testsFailed++;
|
|
||||||
} else {
|
} else {
|
||||||
updateTestStatus(sensor.id, 'Passed', `${rtcResult.rtc_module_time}`, 'bg-success');
|
const minutes = Math.floor(timeDiff / 60);
|
||||||
testsPassed++;
|
const label = minutes > 0 ? `${minutes}min ${timeDiff % 60}s` : `${timeDiff}s`;
|
||||||
|
updateTestStatus(sensor.id, 'Warning', `${rtcResult.rtc_module_time} (desync vs navigateur: ${label})`, 'bg-warning');
|
||||||
|
testsFailed++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateTestStatus(sensor.id, 'Warning', 'Unexpected response', 'bg-warning');
|
updateTestStatus(sensor.id, 'Warning', 'Unexpected response', 'bg-warning');
|
||||||
|
|||||||
@@ -327,6 +327,7 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
|||||||
<th>PM10</th>
|
<th>PM10</th>
|
||||||
<th>Temperature (°C)</th>
|
<th>Temperature (°C)</th>
|
||||||
<th>Humidity (%)</th>
|
<th>Humidity (%)</th>
|
||||||
|
<th>Status</th>
|
||||||
`;
|
`;
|
||||||
} else if (table === "data_BME280") {
|
} else if (table === "data_BME280") {
|
||||||
tableHTML += `
|
tableHTML += `
|
||||||
@@ -400,6 +401,10 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
|||||||
tableHTML += `<tr${rowClass}>`;
|
tableHTML += `<tr${rowClass}>`;
|
||||||
|
|
||||||
if (table === "data_NPM") {
|
if (table === "data_NPM") {
|
||||||
|
const statusVal = parseInt(columns[6]) || 0;
|
||||||
|
const statusBadge = statusVal === 0
|
||||||
|
? '<span class="badge text-bg-success">OK</span>'
|
||||||
|
: `<span class="badge text-bg-warning">0x${statusVal.toString(16).toUpperCase().padStart(2,'0')}</span>`;
|
||||||
tableHTML += `
|
tableHTML += `
|
||||||
<td>${columns[0]}</td>
|
<td>${columns[0]}</td>
|
||||||
<td>${columns[1]}</td>
|
<td>${columns[1]}</td>
|
||||||
@@ -407,6 +412,7 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
|||||||
<td>${columns[3]}</td>
|
<td>${columns[3]}</td>
|
||||||
<td>${columns[4]}</td>
|
<td>${columns[4]}</td>
|
||||||
<td>${columns[5]}</td>
|
<td>${columns[5]}</td>
|
||||||
|
<td>${statusBadge}</td>
|
||||||
`;
|
`;
|
||||||
} else if (table === "data_BME280") {
|
} else if (table === "data_BME280") {
|
||||||
tableHTML += `
|
tableHTML += `
|
||||||
@@ -519,7 +525,7 @@ 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 += "TimestampUTC,PM1,PM2.5,PM10,Temperature_sensor,Humidity_sensor\n";
|
csvContent += "TimestampUTC,PM1,PM2.5,PM10,Temperature_sensor,Humidity_sensor,npm_status\n";
|
||||||
} else if (table === "data_BME280") {
|
} else if (table === "data_BME280") {
|
||||||
csvContent += "TimestampUTC,Temperature (°C),Humidity (%),Pressure (hPa)\n";
|
csvContent += "TimestampUTC,Temperature (°C),Humidity (%),Pressure (hPa)\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,8 +419,9 @@ if ($type == "upload_firmware") {
|
|||||||
|
|
||||||
// Check file upload
|
// Check file upload
|
||||||
if (!isset($_FILES['firmware_file']) || $_FILES['firmware_file']['error'] !== UPLOAD_ERR_OK) {
|
if (!isset($_FILES['firmware_file']) || $_FILES['firmware_file']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$max_upload = ini_get('upload_max_filesize');
|
||||||
$upload_errors = [
|
$upload_errors = [
|
||||||
UPLOAD_ERR_INI_SIZE => 'File exceeds server upload limit',
|
UPLOAD_ERR_INI_SIZE => "Le fichier depasse la limite serveur (actuellement $max_upload). Effectuez d'abord une mise a jour via WiFi (bouton Update firmware) pour debloquer l'upload hors-ligne.",
|
||||||
UPLOAD_ERR_FORM_SIZE => 'File exceeds form upload limit',
|
UPLOAD_ERR_FORM_SIZE => 'File exceeds form upload limit',
|
||||||
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
|
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
|
||||||
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
|
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
|
||||||
@@ -1108,7 +1109,7 @@ if ($type == "wifi_connect") {
|
|||||||
$SSID=$_GET['SSID'];
|
$SSID=$_GET['SSID'];
|
||||||
$PASS=$_GET['pass'];
|
$PASS=$_GET['pass'];
|
||||||
|
|
||||||
// Get device name from database for instructions
|
// Get device name and hostname for instructions
|
||||||
try {
|
try {
|
||||||
$db = new PDO("sqlite:$database_path");
|
$db = new PDO("sqlite:$database_path");
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
@@ -1120,6 +1121,7 @@ if ($type == "wifi_connect") {
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$deviceName = 'NebuleAir';
|
$deviceName = 'NebuleAir';
|
||||||
}
|
}
|
||||||
|
$hostname = trim(shell_exec('hostname 2>/dev/null')) ?: 'aircarto';
|
||||||
|
|
||||||
// Launch connection script in background
|
// Launch connection script in background
|
||||||
$script_path = '/var/www/nebuleair_pro_4g/connexion.sh';
|
$script_path = '/var/www/nebuleair_pro_4g/connexion.sh';
|
||||||
@@ -1132,6 +1134,7 @@ if ($type == "wifi_connect") {
|
|||||||
'success' => true,
|
'success' => true,
|
||||||
'ssid' => $SSID,
|
'ssid' => $SSID,
|
||||||
'deviceName' => $deviceName,
|
'deviceName' => $deviceName,
|
||||||
|
'hostname' => $hostname,
|
||||||
'message' => 'Connection attempt started',
|
'message' => 'Connection attempt started',
|
||||||
'instructions' => [
|
'instructions' => [
|
||||||
'fr' => [
|
'fr' => [
|
||||||
@@ -1139,7 +1142,7 @@ if ($type == "wifi_connect") {
|
|||||||
'step1' => "Le capteur tente de se connecter au réseau « $SSID »",
|
'step1' => "Le capteur tente de se connecter au réseau « $SSID »",
|
||||||
'step2' => "Vous allez être déconnecté du hotspot dans quelques secondes",
|
'step2' => "Vous allez être déconnecté du hotspot dans quelques secondes",
|
||||||
'step3' => "Reconnectez-vous au WiFi « $SSID » sur votre appareil",
|
'step3' => "Reconnectez-vous au WiFi « $SSID » sur votre appareil",
|
||||||
'step4' => "Accédez au capteur via http://$deviceName.local ou cherchez son IP dans votre routeur",
|
'step4' => "Accédez au capteur via http://$hostname.local/html/ ou cherchez son IP dans votre routeur",
|
||||||
'warning' => "Si la connexion échoue, le capteur recréera automatiquement le hotspot"
|
'warning' => "Si la connexion échoue, le capteur recréera automatiquement le hotspot"
|
||||||
],
|
],
|
||||||
'en' => [
|
'en' => [
|
||||||
@@ -1147,7 +1150,7 @@ if ($type == "wifi_connect") {
|
|||||||
'step1' => "The sensor is attempting to connect to network « $SSID »",
|
'step1' => "The sensor is attempting to connect to network « $SSID »",
|
||||||
'step2' => "You will be disconnected from the hotspot in a few seconds",
|
'step2' => "You will be disconnected from the hotspot in a few seconds",
|
||||||
'step3' => "Reconnect your device to WiFi « $SSID »",
|
'step3' => "Reconnect your device to WiFi « $SSID »",
|
||||||
'step4' => "Access the sensor via http://$deviceName.local or find its IP in your router",
|
'step4' => "Access the sensor via http://$hostname.local/html/ or find its IP in your router",
|
||||||
'warning' => "If connection fails, the sensor will automatically recreate the hotspot"
|
'warning' => "If connection fails, the sensor will automatically recreate the hotspot"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -90,13 +90,33 @@ fi
|
|||||||
info "Configuring Apache..."
|
info "Configuring Apache..."
|
||||||
APACHE_CONF="/etc/apache2/sites-available/000-default.conf"
|
APACHE_CONF="/etc/apache2/sites-available/000-default.conf"
|
||||||
if grep -q "DocumentRoot /var/www/nebuleair_pro_4g" "$APACHE_CONF"; then
|
if grep -q "DocumentRoot /var/www/nebuleair_pro_4g" "$APACHE_CONF"; then
|
||||||
warning "Apache configuration already set. Skipping."
|
warning "Apache DocumentRoot already set. Skipping."
|
||||||
else
|
else
|
||||||
sudo sed -i 's|DocumentRoot /var/www/html|DocumentRoot /var/www/nebuleair_pro_4g|' "$APACHE_CONF"
|
sudo sed -i 's|DocumentRoot /var/www/html|DocumentRoot /var/www/nebuleair_pro_4g|' "$APACHE_CONF"
|
||||||
sudo systemctl reload apache2
|
success "Apache DocumentRoot updated."
|
||||||
success "Apache configuration updated and reloaded."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Enable AllowOverride for .htaccess (needed for PHP upload limits)
|
||||||
|
APACHE_MAIN_CONF="/etc/apache2/apache2.conf"
|
||||||
|
if grep -q "AllowOverride All" "$APACHE_MAIN_CONF"; then
|
||||||
|
warning "AllowOverride already configured. Skipping."
|
||||||
|
else
|
||||||
|
# Replace AllowOverride None with AllowOverride All for /var/www/
|
||||||
|
sudo sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' "$APACHE_MAIN_CONF"
|
||||||
|
success "AllowOverride All enabled for /var/www/."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Also increase PHP limits directly in php.ini as fallback
|
||||||
|
PHP_INI=$(php -r "echo php_ini_loaded_file();" 2>/dev/null)
|
||||||
|
if [[ -n "$PHP_INI" ]]; then
|
||||||
|
sudo sed -i 's/upload_max_filesize = .*/upload_max_filesize = 50M/' "$PHP_INI"
|
||||||
|
sudo sed -i 's/post_max_size = .*/post_max_size = 55M/' "$PHP_INI"
|
||||||
|
success "PHP upload limits set to 50M in $PHP_INI"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo systemctl reload apache2
|
||||||
|
success "Apache configuration updated and reloaded."
|
||||||
|
|
||||||
# Add sudo authorization (prevent duplicate entries)
|
# Add sudo authorization (prevent duplicate entries)
|
||||||
info "Setting up sudo authorization..."
|
info "Setting up sudo authorization..."
|
||||||
SUDOERS_FILE="/etc/sudoers"
|
SUDOERS_FILE="/etc/sudoers"
|
||||||
|
|||||||
@@ -151,6 +151,16 @@ payload_json = {
|
|||||||
aircarto_profile_id = 0
|
aircarto_profile_id = 0
|
||||||
uSpot_profile_id = 1
|
uSpot_profile_id = 1
|
||||||
|
|
||||||
|
# Error flags constants (byte 66)
|
||||||
|
ERR_RTC_DISCONNECTED = 0x01
|
||||||
|
ERR_RTC_RESET = 0x02
|
||||||
|
ERR_BME280 = 0x04
|
||||||
|
ERR_NPM = 0x08
|
||||||
|
ERR_ENVEA = 0x10
|
||||||
|
ERR_NOISE = 0x20
|
||||||
|
ERR_MPPT = 0x40
|
||||||
|
ERR_WIND = 0x80
|
||||||
|
|
||||||
# database connection
|
# database connection
|
||||||
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()
|
||||||
@@ -271,7 +281,13 @@ class SensorPayload:
|
|||||||
#device_id_bytes = bytes.fromhex(device_id)[:8].ljust(8, b'\x00')
|
#device_id_bytes = bytes.fromhex(device_id)[:8].ljust(8, b'\x00')
|
||||||
|
|
||||||
self.payload[0:8] = device_id_bytes
|
self.payload[0:8] = device_id_bytes
|
||||||
|
|
||||||
|
# Status/error bytes default to 0x00 (no error)
|
||||||
|
# 0xFF = "no data" for sensor values, but for status bytes
|
||||||
|
# 0x00 = "no error/no flag" is the safe default
|
||||||
|
self.payload[66] = 0x00 # error_flags
|
||||||
|
self.payload[67] = 0x00 # npm_status
|
||||||
|
self.payload[68] = 0x00 # device_status
|
||||||
# Set protocol version (byte 9)
|
# Set protocol version (byte 9)
|
||||||
self.payload[9] = 0x01
|
self.payload[9] = 0x01
|
||||||
|
|
||||||
@@ -358,6 +374,18 @@ class SensorPayload:
|
|||||||
if direction is not None:
|
if direction is not None:
|
||||||
self.payload[64:66] = struct.pack('>H', int(direction))
|
self.payload[64:66] = struct.pack('>H', int(direction))
|
||||||
|
|
||||||
|
def set_error_flags(self, flags):
|
||||||
|
"""Set system error flags (byte 66)"""
|
||||||
|
self.payload[66] = flags & 0xFF
|
||||||
|
|
||||||
|
def set_npm_status(self, status):
|
||||||
|
"""Set NextPM status register (byte 67)"""
|
||||||
|
self.payload[67] = status & 0xFF
|
||||||
|
|
||||||
|
def set_device_status(self, status):
|
||||||
|
"""Set device status flags (byte 68)"""
|
||||||
|
self.payload[68] = status & 0xFF
|
||||||
|
|
||||||
def get_bytes(self):
|
def get_bytes(self):
|
||||||
"""Get the complete 100-byte payload"""
|
"""Get the complete 100-byte payload"""
|
||||||
return bytes(self.payload)
|
return bytes(self.payload)
|
||||||
@@ -1090,6 +1118,14 @@ try:
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# ---- Build error_flags (byte 66) ----
|
||||||
|
error_flags = 0x00
|
||||||
|
if rtc_status == "disconnected":
|
||||||
|
error_flags |= ERR_RTC_DISCONNECTED
|
||||||
|
if rtc_status == "reset":
|
||||||
|
error_flags |= ERR_RTC_RESET
|
||||||
|
payload.set_error_flags(error_flags)
|
||||||
|
|
||||||
if send_miotiq:
|
if send_miotiq:
|
||||||
print('<p class="fw-bold">➡️SEND TO MIOTIQ</p>', end="")
|
print('<p class="fw-bold">➡️SEND TO MIOTIQ</p>', end="")
|
||||||
|
|
||||||
@@ -1125,9 +1161,20 @@ try:
|
|||||||
print(response_SARA_1)
|
print(response_SARA_1)
|
||||||
else:
|
else:
|
||||||
print("⛔There were issues with the modem CSD PSD reinitialize process")
|
print("⛔There were issues with the modem CSD PSD reinitialize process")
|
||||||
|
print("🔄 PDP reset failed → escalating to hardware reboot")
|
||||||
# Clignotement LED rouge en cas d'erreur
|
# Clignotement LED rouge en cas d'erreur
|
||||||
led_thread = Thread(target=blink_led, args=(24, 5, 0.5))
|
led_thread = Thread(target=blink_led, args=(24, 5, 0.5))
|
||||||
led_thread.start()
|
led_thread.start()
|
||||||
|
#Send notification (WIFI)
|
||||||
|
send_error_notification(device_id, "UDP socket creation failed + PDP reset failed -> hardware reboot")
|
||||||
|
#Hardware Reboot
|
||||||
|
hardware_reboot_success = modem_hardware_reboot()
|
||||||
|
if hardware_reboot_success:
|
||||||
|
print("✅Modem successfully rebooted and reinitialized")
|
||||||
|
else:
|
||||||
|
print("⛔There were issues with the modem reboot/reinitialize process")
|
||||||
|
#end loop
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
#Retreive Socket ID
|
#Retreive Socket ID
|
||||||
socket_id = None
|
socket_id = None
|
||||||
|
|||||||
319
loop/error_flags.md
Normal file
319
loop/error_flags.md
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
# Error Flags — UDP Payload Miotiq (Bytes 66-68)
|
||||||
|
|
||||||
|
## Principe
|
||||||
|
|
||||||
|
Les bytes 66, 67 et 68 de la payload UDP (100 bytes) sont utilises comme registres d'erreurs
|
||||||
|
et d'etat. Chaque bit represente un etat independant. Plusieurs flags peuvent
|
||||||
|
etre actifs simultanement.
|
||||||
|
|
||||||
|
- **Byte 66** : erreurs systeme (RTC, capteurs)
|
||||||
|
- **Byte 67** : status NextPM (registre interne du capteur)
|
||||||
|
- **Byte 68** : status device (etat general du boitier)
|
||||||
|
|
||||||
|
## Position dans la payload
|
||||||
|
|
||||||
|
```
|
||||||
|
Bytes 0-65 : donnees capteurs (existant)
|
||||||
|
Byte 66 : error_flags (erreurs systeme)
|
||||||
|
Byte 67 : npm_status (status NextPM)
|
||||||
|
Byte 68 : device_status (etat general du boitier)
|
||||||
|
Bytes 69-99 : reserved (initialises a 0xFF)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Byte 66 — Error Flags (erreurs systeme)
|
||||||
|
|
||||||
|
Chaque bit represente une erreur detectee par le script d'envoi (`SARA_send_data_v2.py`).
|
||||||
|
|
||||||
|
| Bit | Masque | Nom | Description | Source |
|
||||||
|
|-----|--------|-------------------|--------------------------------------------------|-------------------------------|
|
||||||
|
| 0 | 0x01 | RTC_DISCONNECTED | Module RTC DS3231 non detecte sur le bus I2C | timestamp_table → 'not connected' |
|
||||||
|
| 1 | 0x02 | RTC_RESET | RTC en date par defaut (annee 2000) | timestamp_table → annee 2000 |
|
||||||
|
| 2 | 0x04 | BME280_ERROR | Capteur BME280 non detecte ou erreur de lecture | data_BME280 → valeurs a 0 |
|
||||||
|
| 3 | 0x08 | NPM_ERROR | Capteur NextPM non detecte ou erreur communication| data_NPM → toutes valeurs a 0 |
|
||||||
|
| 4 | 0x10 | ENVEA_ERROR | Capteurs Envea non detectes ou erreur serie | data_envea → valeurs a 0 |
|
||||||
|
| 5 | 0x20 | NOISE_ERROR | Capteur bruit NSRT MK4 non detecte ou erreur | data_noise → valeurs a 0 |
|
||||||
|
| 6 | 0x40 | MPPT_ERROR | Chargeur solaire MPPT non detecte ou erreur | data_MPPT → valeurs a 0 |
|
||||||
|
| 7 | 0x80 | WIND_ERROR | Capteur vent non detecte ou erreur | data_windMeter → valeurs a 0 |
|
||||||
|
|
||||||
|
### Detection des erreurs
|
||||||
|
|
||||||
|
Les scripts de collecte (`get_data_modbus_v3.py`, `get_data_v2.py`, etc.) ecrivent des **0**
|
||||||
|
en base SQLite quand un capteur ne repond pas. Le script d'envoi (`SARA_send_data_v2.py`)
|
||||||
|
lit ces valeurs et peut detecter l'erreur quand toutes les valeurs d'un capteur sont a 0.
|
||||||
|
|
||||||
|
Pour le RTC, le champ `timestamp_table` contient directement `'not connected'` ou une date
|
||||||
|
en annee 2000 quand le module est deconnecte ou reinitialise.
|
||||||
|
|
||||||
|
### Exemples de valeurs
|
||||||
|
|
||||||
|
| Valeur dec | Hex | Signification |
|
||||||
|
|------------|------|---------------------------------------|
|
||||||
|
| 0 | 0x00 | Aucune erreur |
|
||||||
|
| 1 | 0x01 | RTC deconnecte |
|
||||||
|
| 2 | 0x02 | RTC reset (annee 2000) |
|
||||||
|
| 5 | 0x05 | RTC deconnecte + BME280 erreur |
|
||||||
|
| 9 | 0x09 | RTC deconnecte + NPM erreur |
|
||||||
|
| 255 | 0xFF | Toutes les erreurs (cas extreme) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Byte 67 — NPM Status (registre interne NextPM)
|
||||||
|
|
||||||
|
Le capteur NextPM possede un registre de status sur 9 bits (registre Modbus).
|
||||||
|
On stocke les 8 bits bas dans le byte 67. Ce registre est lu directement depuis
|
||||||
|
le capteur via Modbus, pas depuis SQLite.
|
||||||
|
|
||||||
|
| Bit | Masque | Nom | Description | Severite |
|
||||||
|
|-----|--------|-----------------|-----------------------------------------------------------------------|----------------|
|
||||||
|
| 0 | 0x01 | SLEEP_STATE | Capteur en veille (commande sleep). Seule la lecture status autorisee | Info |
|
||||||
|
| 1 | 0x02 | DEGRADED_STATE | Erreur mineure confirmee. Mesures possibles mais precision reduite | Warning |
|
||||||
|
| 2 | 0x04 | NOT_READY | Demarrage en cours (15s apres mise sous tension). Mesures non fiables | Info |
|
||||||
|
| 3 | 0x08 | HEAT_ERROR | Humidite relative > 60% pendant > 10 minutes | Warning |
|
||||||
|
| 4 | 0x10 | TRH_ERROR | Capteur T/RH interne hors specification | Warning |
|
||||||
|
| 5 | 0x20 | FAN_ERROR | Vitesse ventilateur hors plage (tourne encore) | Warning |
|
||||||
|
| 6 | 0x40 | MEMORY_ERROR | Acces memoire impossible, fonctions internes limitees | Warning |
|
||||||
|
| 7 | 0x80 | LASER_ERROR | Aucune particule detectee pendant > 240s, possible erreur laser | Warning |
|
||||||
|
|
||||||
|
Note : le bit 8 du registre NextPM (DEFAULT_STATE — ventilateur arrete apres 3 tentatives)
|
||||||
|
ne tient pas dans un byte. Si necessaire, il peut etre combine avec le bit 0 (SLEEP_STATE)
|
||||||
|
car les deux indiquent un capteur inactif.
|
||||||
|
|
||||||
|
### Exemples de valeurs
|
||||||
|
|
||||||
|
| Valeur dec | Hex | Signification |
|
||||||
|
|------------|------|--------------------------------------------|
|
||||||
|
| 0 | 0x00 | Capteur OK, mesures fiables |
|
||||||
|
| 4 | 0x04 | Demarrage en cours (NOT_READY) |
|
||||||
|
| 8 | 0x08 | Erreur humidite (HEAT_ERROR) |
|
||||||
|
| 32 | 0x20 | Erreur ventilateur (FAN_ERROR) |
|
||||||
|
| 128 | 0x80 | Possible erreur laser (LASER_ERROR) |
|
||||||
|
| 40 | 0x28 | HEAT_ERROR + FAN_ERROR |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Byte 68 — Device Status (etat general du boitier)
|
||||||
|
|
||||||
|
Flags d'etat du device, determines par le script d'envoi (`SARA_send_data_v2.py`).
|
||||||
|
Ces flags donnent du contexte sur l'etat general du boitier pour le diagnostic a distance.
|
||||||
|
|
||||||
|
| Bit | Masque | Nom | Description | Source |
|
||||||
|
|-----|--------|----------------------|----------------------------------------------------------------|-------------------------------------|
|
||||||
|
| 0 | 0x01 | SARA_REBOOTED | Le modem a ete reboot hardware au cycle precedent | flag fichier ou SQLite |
|
||||||
|
| 1 | 0x02 | WIFI_CONNECTED | Le device est connecte en WiFi (atelier/maintenance) | nmcli device status |
|
||||||
|
| 2 | 0x04 | HOTSPOT_ACTIVE | Le hotspot WiFi est actif (configuration en cours) | nmcli device status |
|
||||||
|
| 3 | 0x08 | GPS_NO_FIX | Pas de position GPS valide | config_table latitude/longitude |
|
||||||
|
| 4 | 0x10 | BATTERY_LOW | Tension batterie sous seuil critique | data_MPPT → battery_voltage |
|
||||||
|
| 5 | 0x20 | DISK_FULL | Espace disque critique sur la Pi (< 5%) | os.statvfs ou shutil.disk_usage |
|
||||||
|
| 6 | 0x40 | DB_ERROR | Erreur d'acces a la base SQLite | try/except sur connexion SQLite |
|
||||||
|
| 7 | 0x80 | BOOT_RECENT | Le device a redemarre recemment (uptime < 5 min) | /proc/uptime |
|
||||||
|
|
||||||
|
### Exemples de valeurs
|
||||||
|
|
||||||
|
| Valeur dec | Hex | Signification |
|
||||||
|
|------------|------|--------------------------------------------------|
|
||||||
|
| 0 | 0x00 | Tout est normal |
|
||||||
|
| 1 | 0x01 | Modem reboot au cycle precedent |
|
||||||
|
| 2 | 0x02 | WiFi connecte (probablement en atelier) |
|
||||||
|
| 6 | 0x06 | WiFi + hotspot actifs (configuration en cours) |
|
||||||
|
| 128 | 0x80 | Boot recent (uptime < 5 min) |
|
||||||
|
| 145 | 0x91 | Modem reboot + batterie faible + boot recent |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### Etape 1 : Lire le status NPM depuis le capteur
|
||||||
|
|
||||||
|
Le script `NPM/get_data_modbus_v3.py` doit etre modifie pour :
|
||||||
|
1. Lire le registre de status du NextPM (adresse Modbus a determiner)
|
||||||
|
2. Stocker le status byte dans une nouvelle colonne SQLite (ex: `npm_status` dans `data_NPM`)
|
||||||
|
|
||||||
|
### Etape 2 : Construire les flags dans SARA_send_data_v2.py
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Constantes error_flags (byte 66)
|
||||||
|
ERR_RTC_DISCONNECTED = 0x01
|
||||||
|
ERR_RTC_RESET = 0x02
|
||||||
|
ERR_BME280 = 0x04
|
||||||
|
ERR_NPM = 0x08
|
||||||
|
ERR_ENVEA = 0x10
|
||||||
|
ERR_NOISE = 0x20
|
||||||
|
ERR_MPPT = 0x40
|
||||||
|
ERR_WIND = 0x80
|
||||||
|
|
||||||
|
# Constantes device_status (byte 68)
|
||||||
|
DEV_SARA_REBOOTED = 0x01
|
||||||
|
DEV_WIFI_CONNECTED = 0x02
|
||||||
|
DEV_HOTSPOT_ACTIVE = 0x04
|
||||||
|
DEV_GPS_NO_FIX = 0x08
|
||||||
|
DEV_BATTERY_LOW = 0x10
|
||||||
|
DEV_DISK_FULL = 0x20
|
||||||
|
DEV_DB_ERROR = 0x40
|
||||||
|
DEV_BOOT_RECENT = 0x80
|
||||||
|
|
||||||
|
# Construction byte 66
|
||||||
|
error_flags = 0x00
|
||||||
|
|
||||||
|
if rtc_status == "disconnected":
|
||||||
|
error_flags |= ERR_RTC_DISCONNECTED
|
||||||
|
if rtc_status == "reset":
|
||||||
|
error_flags |= ERR_RTC_RESET
|
||||||
|
if PM1 == 0 and PM25 == 0 and PM10 == 0:
|
||||||
|
error_flags |= ERR_NPM
|
||||||
|
# ... autres capteurs
|
||||||
|
|
||||||
|
payload.set_error_flags(error_flags)
|
||||||
|
|
||||||
|
# Construction byte 67 (lu depuis SQLite, ecrit par get_data_modbus_v3.py)
|
||||||
|
npm_status = get_npm_status_from_db() # 0-255
|
||||||
|
payload.set_npm_status(npm_status)
|
||||||
|
|
||||||
|
# Construction byte 68
|
||||||
|
device_status = 0x00
|
||||||
|
|
||||||
|
if sara_was_rebooted(): # flag fichier persistant
|
||||||
|
device_status |= DEV_SARA_REBOOTED
|
||||||
|
if check_wifi_connected(): # nmcli device status
|
||||||
|
device_status |= DEV_WIFI_CONNECTED
|
||||||
|
if check_hotspot_active(): # nmcli device status
|
||||||
|
device_status |= DEV_HOTSPOT_ACTIVE
|
||||||
|
if latitude == 0.0 and longitude == 0.0: # config_table
|
||||||
|
device_status |= DEV_GPS_NO_FIX
|
||||||
|
if battery_voltage < 11.0: # data_MPPT seuil a ajuster
|
||||||
|
device_status |= DEV_BATTERY_LOW
|
||||||
|
if check_disk_usage() > 95: # shutil.disk_usage
|
||||||
|
device_status |= DEV_DISK_FULL
|
||||||
|
# DEV_DB_ERROR: set dans le try/except de la connexion SQLite
|
||||||
|
if get_uptime_seconds() < 300: # /proc/uptime
|
||||||
|
device_status |= DEV_BOOT_RECENT
|
||||||
|
|
||||||
|
payload.set_device_status(device_status)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Etape 3 : Ajouter les methodes dans SensorPayload
|
||||||
|
|
||||||
|
```python
|
||||||
|
def set_error_flags(self, flags):
|
||||||
|
"""Set system error flags (byte 66)"""
|
||||||
|
self.payload[66] = flags & 0xFF
|
||||||
|
|
||||||
|
def set_npm_status(self, status):
|
||||||
|
"""Set NextPM status register (byte 67)"""
|
||||||
|
self.payload[67] = status & 0xFF
|
||||||
|
|
||||||
|
def set_device_status(self, status):
|
||||||
|
"""Set device status flags (byte 68)"""
|
||||||
|
self.payload[68] = status & 0xFF
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parser Miotiq
|
||||||
|
|
||||||
|
```
|
||||||
|
16|device_id|string|||W
|
||||||
|
2|signal_quality|hex2dec|dB||
|
||||||
|
2|version|hex2dec|||W
|
||||||
|
4|ISO_68|hex2dec|ugm3|x/10|
|
||||||
|
4|ISO_39|hex2dec|ugm3|x/10|
|
||||||
|
4|ISO_24|hex2dec|ugm3|x/10|
|
||||||
|
4|ISO_54|hex2dec|degC|x/100|
|
||||||
|
4|ISO_55|hex2dec|%|x/100|
|
||||||
|
4|ISO_53|hex2dec|hPa||
|
||||||
|
4|noise_cur_leq|hex2dec|dB|x/10|
|
||||||
|
4|noise_cur_level|hex2dec|dB|x/10|
|
||||||
|
4|max_noise|hex2dec|dB|x/10|
|
||||||
|
4|ISO_03|hex2dec|ppb||
|
||||||
|
4|ISO_05|hex2dec|ppb||
|
||||||
|
4|ISO_21|hex2dec|ppb||
|
||||||
|
4|ISO_04|hex2dec|ppb||
|
||||||
|
4|ISO_08|hex2dec|ppb||
|
||||||
|
4|npm_ch1|hex2dec|count||
|
||||||
|
4|npm_ch2|hex2dec|count||
|
||||||
|
4|npm_ch3|hex2dec|count||
|
||||||
|
4|npm_ch4|hex2dec|count||
|
||||||
|
4|npm_ch5|hex2dec|count||
|
||||||
|
4|npm_temp|hex2dec|°C|x/10|
|
||||||
|
4|npm_humidity|hex2dec|%|x/10|
|
||||||
|
4|battery_voltage|hex2dec|V|x/100|
|
||||||
|
4|battery_current|hex2dec|A|x/100|
|
||||||
|
4|solar_voltage|hex2dec|V|x/100|
|
||||||
|
4|solar_power|hex2dec|W||
|
||||||
|
4|charger_status|hex2dec|||
|
||||||
|
4|wind_speed|hex2dec|m/s|x/10|
|
||||||
|
4|wind_direction|hex2dec|degrees||
|
||||||
|
2|error_flags|hex2dec|||
|
||||||
|
2|npm_status|hex2dec|||
|
||||||
|
2|device_status|hex2dec|||
|
||||||
|
28|reserved|skip|||
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lecture cote serveur (exemple Python)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Byte 66 — erreurs systeme
|
||||||
|
error_flags = int(parsed_error_flags)
|
||||||
|
|
||||||
|
rtc_disconnected = bool(error_flags & 0x01)
|
||||||
|
rtc_reset = bool(error_flags & 0x02)
|
||||||
|
bme280_error = bool(error_flags & 0x04)
|
||||||
|
npm_error = bool(error_flags & 0x08)
|
||||||
|
envea_error = bool(error_flags & 0x10)
|
||||||
|
noise_error = bool(error_flags & 0x20)
|
||||||
|
mppt_error = bool(error_flags & 0x40)
|
||||||
|
wind_error = bool(error_flags & 0x80)
|
||||||
|
|
||||||
|
# Byte 67 — status NextPM
|
||||||
|
npm_status = int(parsed_npm_status)
|
||||||
|
|
||||||
|
npm_sleep = bool(npm_status & 0x01)
|
||||||
|
npm_degraded = bool(npm_status & 0x02)
|
||||||
|
npm_not_ready = bool(npm_status & 0x04)
|
||||||
|
npm_heat_err = bool(npm_status & 0x08)
|
||||||
|
npm_trh_err = bool(npm_status & 0x10)
|
||||||
|
npm_fan_err = bool(npm_status & 0x20)
|
||||||
|
npm_mem_err = bool(npm_status & 0x40)
|
||||||
|
npm_laser_err = bool(npm_status & 0x80)
|
||||||
|
|
||||||
|
# Byte 68 — status device
|
||||||
|
device_status = int(parsed_device_status)
|
||||||
|
|
||||||
|
sara_rebooted = bool(device_status & 0x01)
|
||||||
|
wifi_connected = bool(device_status & 0x02)
|
||||||
|
hotspot_active = bool(device_status & 0x04)
|
||||||
|
gps_no_fix = bool(device_status & 0x08)
|
||||||
|
battery_low = bool(device_status & 0x10)
|
||||||
|
disk_full = bool(device_status & 0x20)
|
||||||
|
db_error = bool(device_status & 0x40)
|
||||||
|
boot_recent = bool(device_status & 0x80)
|
||||||
|
|
||||||
|
# Alertes
|
||||||
|
if rtc_disconnected:
|
||||||
|
alert("RTC module deconnecte — verifier pile/cables I2C")
|
||||||
|
if npm_fan_err:
|
||||||
|
alert("NextPM: ventilateur hors plage — maintenance requise")
|
||||||
|
if npm_laser_err:
|
||||||
|
alert("NextPM: possible erreur laser — verifier le capteur")
|
||||||
|
if battery_low:
|
||||||
|
alert("Batterie faible — verifier alimentation solaire")
|
||||||
|
if disk_full:
|
||||||
|
alert("Espace disque critique — verifier logs/DB")
|
||||||
|
if sara_rebooted:
|
||||||
|
alert("Modem reboot hardware au cycle precedent — instabilite reseau")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- La payload est initialisee a 0xFF (tous bytes a 255). Le script doit explicitement
|
||||||
|
ecrire 0x00 dans les bytes 66-67 quand tout va bien, sinon Miotiq interpretera
|
||||||
|
255 = toutes les erreurs.
|
||||||
|
- Le NPM status n'est pas encore lu par `get_data_modbus_v3.py`. Il faut d'abord
|
||||||
|
ajouter la lecture du registre de status Modbus et le stocker en SQLite.
|
||||||
|
- Les flags du byte 66 sont determines par le script d'envoi en analysant les
|
||||||
|
valeurs lues depuis SQLite (toutes a 0 = capteur en erreur).
|
||||||
@@ -67,10 +67,18 @@ CREATE TABLE IF NOT EXISTS data_NPM (
|
|||||||
PM25 REAL,
|
PM25 REAL,
|
||||||
PM10 REAL,
|
PM10 REAL,
|
||||||
temp_npm REAL,
|
temp_npm REAL,
|
||||||
hum_npm REAL
|
hum_npm REAL,
|
||||||
|
npm_status INTEGER DEFAULT 0
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
# Add npm_status column to existing databases (migration)
|
||||||
|
try:
|
||||||
|
cursor.execute("ALTER TABLE data_NPM ADD COLUMN npm_status INTEGER DEFAULT 0")
|
||||||
|
print("Added npm_status column to data_NPM")
|
||||||
|
except:
|
||||||
|
pass # Column already exists
|
||||||
|
|
||||||
# Create a table BME280
|
# Create a table BME280
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS data_BME280 (
|
CREATE TABLE IF NOT EXISTS data_BME280 (
|
||||||
|
|||||||
@@ -107,6 +107,18 @@ for connected, port, name, coefficient in envea_sondes:
|
|||||||
print(f"Envea sonde '{name}' already exists, skipping")
|
print(f"Envea sonde '{name}' already exists, skipping")
|
||||||
|
|
||||||
|
|
||||||
|
# Database migrations (add columns to existing tables)
|
||||||
|
migrations = [
|
||||||
|
("data_NPM", "npm_status", "INTEGER DEFAULT 0"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for table, column, col_type in migrations:
|
||||||
|
try:
|
||||||
|
cursor.execute(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}")
|
||||||
|
print(f"Migration: added column '{column}' to {table}")
|
||||||
|
except:
|
||||||
|
pass # Column already exists
|
||||||
|
|
||||||
# Commit and close the connection
|
# Commit and close the connection
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -81,6 +81,32 @@ sudo chmod 755 /var/www/nebuleair_pro_4g/MPPT/*.py 2>/dev/null
|
|||||||
sudo chmod 755 /var/www/nebuleair_pro_4g/wifi/*.py 2>/dev/null
|
sudo chmod 755 /var/www/nebuleair_pro_4g/wifi/*.py 2>/dev/null
|
||||||
check_status "File permissions update"
|
check_status "File permissions update"
|
||||||
|
|
||||||
|
# Step 3b: Ensure Apache/PHP config allows file uploads
|
||||||
|
APACHE_MAIN_CONF="/etc/apache2/apache2.conf"
|
||||||
|
if grep -q '<Directory /var/www/>' "$APACHE_MAIN_CONF"; then
|
||||||
|
if ! sed -n '/<Directory \/var\/www\/>/,/<\/Directory>/p' "$APACHE_MAIN_CONF" | grep -q "AllowOverride All"; then
|
||||||
|
sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' "$APACHE_MAIN_CONF"
|
||||||
|
print_status "✓ AllowOverride All enabled for Apache"
|
||||||
|
APACHE_CHANGED=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
PHP_INI=$(php -r "echo php_ini_loaded_file();" 2>/dev/null)
|
||||||
|
if [ -n "$PHP_INI" ]; then
|
||||||
|
CURRENT_UPLOAD=$(grep -oP 'upload_max_filesize = \K\d+' "$PHP_INI" 2>/dev/null)
|
||||||
|
if [ -n "$CURRENT_UPLOAD" ] && [ "$CURRENT_UPLOAD" -lt 50 ]; then
|
||||||
|
sed -i 's/upload_max_filesize = .*/upload_max_filesize = 50M/' "$PHP_INI"
|
||||||
|
sed -i 's/post_max_size = .*/post_max_size = 55M/' "$PHP_INI"
|
||||||
|
print_status "✓ PHP upload limits set to 50M"
|
||||||
|
APACHE_CHANGED=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${APACHE_CHANGED:-false}" = true ]; then
|
||||||
|
systemctl reload apache2 2>/dev/null
|
||||||
|
print_status "✓ Apache reloaded"
|
||||||
|
fi
|
||||||
|
|
||||||
# Step 4: Restart critical services if they exist
|
# Step 4: Restart critical services if they exist
|
||||||
print_status ""
|
print_status ""
|
||||||
print_status "Step 4: Managing system services..."
|
print_status "Step 4: Managing system services..."
|
||||||
|
|||||||
@@ -118,6 +118,32 @@ chmod 755 "$TARGET_DIR/MPPT/"*.py 2>/dev/null
|
|||||||
chmod 755 "$TARGET_DIR/wifi/"*.py 2>/dev/null
|
chmod 755 "$TARGET_DIR/wifi/"*.py 2>/dev/null
|
||||||
check_status "File permissions update"
|
check_status "File permissions update"
|
||||||
|
|
||||||
|
# Step 4b: Ensure Apache/PHP config allows file uploads (.htaccess + php.ini)
|
||||||
|
APACHE_MAIN_CONF="/etc/apache2/apache2.conf"
|
||||||
|
if grep -q '<Directory /var/www/>' "$APACHE_MAIN_CONF"; then
|
||||||
|
if ! sed -n '/<Directory \/var\/www\/>/,/<\/Directory>/p' "$APACHE_MAIN_CONF" | grep -q "AllowOverride All"; then
|
||||||
|
sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' "$APACHE_MAIN_CONF"
|
||||||
|
print_status "✓ AllowOverride All enabled for Apache"
|
||||||
|
APACHE_CHANGED=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
PHP_INI=$(php -r "echo php_ini_loaded_file();" 2>/dev/null)
|
||||||
|
if [ -n "$PHP_INI" ]; then
|
||||||
|
CURRENT_UPLOAD=$(grep -oP 'upload_max_filesize = \K\d+' "$PHP_INI" 2>/dev/null)
|
||||||
|
if [ -n "$CURRENT_UPLOAD" ] && [ "$CURRENT_UPLOAD" -lt 50 ]; then
|
||||||
|
sed -i 's/upload_max_filesize = .*/upload_max_filesize = 50M/' "$PHP_INI"
|
||||||
|
sed -i 's/post_max_size = .*/post_max_size = 55M/' "$PHP_INI"
|
||||||
|
print_status "✓ PHP upload limits set to 50M"
|
||||||
|
APACHE_CHANGED=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${APACHE_CHANGED:-false}" = true ]; then
|
||||||
|
systemctl reload apache2 2>/dev/null
|
||||||
|
print_status "✓ Apache reloaded"
|
||||||
|
fi
|
||||||
|
|
||||||
# Step 5: Restart critical services
|
# Step 5: Restart critical services
|
||||||
print_status ""
|
print_status ""
|
||||||
print_status "Step 5: Managing system services..."
|
print_status "Step 5: Managing system services..."
|
||||||
|
|||||||
Reference in New Issue
Block a user