v1.9.10: Self Test - check sous-tension (vcgencmd get_throttled)
Ajoute un test 'Power Supply' au Self Test pour détecter une sous-tension du Pi (cause fréquente de capteurs USB instables, corruptions SD, reboots). Endpoint launcher.php?type=throttled + script power/get_throttled.py (lancé via sudo python3, déjà whitelisté — pas de modif sudoers). Affiché en tête des résultats et dans le rapport copiable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,20 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.9.10",
|
||||
"date": "2026-05-28",
|
||||
"changes": {
|
||||
"features": [
|
||||
"Self Test: nouveau check 'Power Supply' qui lit 'vcgencmd get_throttled' et détecte la sous-tension du Pi. Passed = alim OK, Warning = sous-tension survenue depuis le boot, Failed = sous-tension active. Apparaît en tête des résultats et dans le rapport copiable. La sous-tension est une cause fréquente de capteurs USB instables, corruptions SD et reboots."
|
||||
],
|
||||
"improvements": [],
|
||||
"fixes": [],
|
||||
"compatibility": [
|
||||
"Backend: nouvel endpoint launcher.php?type=throttled + script power/get_throttled.py (lancé via sudo python3, déjà whitelisté dans sudoers — aucun changement /etc/sudoers requis)."
|
||||
]
|
||||
},
|
||||
"notes": "Complément de la v1.9.9 (retry sonde bruit): permet de diagnostiquer à distance la cause racine (alimentation 5V insuffisante / câble) depuis l'interface admin, sur n'importe quel boîtier de la flotte."
|
||||
},
|
||||
{
|
||||
"version": "1.9.9",
|
||||
"date": "2026-05-28",
|
||||
|
||||
@@ -74,6 +74,10 @@ function resetSelfTestUI() {
|
||||
</div>`;
|
||||
|
||||
// Reset test items
|
||||
document.getElementById('test_power_status').className = 'badge bg-secondary';
|
||||
document.getElementById('test_power_status').textContent = 'Pending';
|
||||
document.getElementById('test_power_detail').textContent = 'Waiting...';
|
||||
|
||||
document.getElementById('test_wifi_status').className = 'badge bg-secondary';
|
||||
document.getElementById('test_wifi_status').textContent = 'Pending';
|
||||
document.getElementById('test_wifi_detail').textContent = 'Waiting...';
|
||||
@@ -260,6 +264,59 @@ async function selfTestSequence() {
|
||||
|
||||
await delaySelfTest(300);
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// SYSTEM TEST - Power supply (under-voltage detection)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
addSelfTestLog('');
|
||||
addSelfTestLog('────────────────────────────────────────────────────────');
|
||||
addSelfTestLog('SYSTEM TEST');
|
||||
addSelfTestLog('────────────────────────────────────────────────────────');
|
||||
|
||||
document.getElementById('selftest_status').innerHTML = `
|
||||
<div class="d-flex align-items-center text-primary">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<span>Checking power supply...</span>
|
||||
</div>`;
|
||||
|
||||
updateTestStatus('power', 'Testing...', 'Reading vcgencmd get_throttled...', 'bg-info');
|
||||
addSelfTestLog('Checking power supply (under-voltage)...');
|
||||
|
||||
try {
|
||||
const powerResult = await new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
url: 'launcher.php?type=throttled',
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
cache: false,
|
||||
timeout: 10000,
|
||||
success: function(data) { resolve(data); },
|
||||
error: function(xhr, status, error) { reject(new Error(error || status)); }
|
||||
});
|
||||
});
|
||||
|
||||
selfTestReport.rawResponses['Power Supply'] = JSON.stringify(powerResult, null, 2);
|
||||
addSelfTestLog(`Power response: ${JSON.stringify(powerResult)}`);
|
||||
|
||||
if (!powerResult.available) {
|
||||
updateTestStatus('power', 'Warning', powerResult.error || 'vcgencmd indisponible', 'bg-warning');
|
||||
testsFailed++;
|
||||
} else if (powerResult.status === 'critical') {
|
||||
updateTestStatus('power', 'Failed', powerResult.message, 'bg-danger');
|
||||
testsFailed++;
|
||||
} else if (powerResult.status === 'warning') {
|
||||
updateTestStatus('power', 'Warning', powerResult.message, 'bg-warning');
|
||||
testsFailed++;
|
||||
} else {
|
||||
updateTestStatus('power', 'Passed', powerResult.message || 'Alimentation OK', 'bg-success');
|
||||
testsPassed++;
|
||||
}
|
||||
} catch (error) {
|
||||
addSelfTestLog(`Power supply test error: ${error.message}`);
|
||||
updateTestStatus('power', 'Failed', error.message, 'bg-danger');
|
||||
selfTestReport.rawResponses['Power Supply'] = `ERROR: ${error.message}`;
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// SENSOR TESTS - Test enabled sensors based on config
|
||||
// ═══════════════════════════════════════════════════════
|
||||
@@ -856,6 +913,7 @@ GPS Location: ${selfTestReport.latitude || 'N/A'}, ${selfTestReport.longitud
|
||||
|
||||
// Add test results (sensors first, then communication)
|
||||
const testNames = {
|
||||
power: 'Power Supply',
|
||||
npm: 'NextPM (Particles)',
|
||||
bme280: 'BME280 (Temp/Hum)',
|
||||
noise: 'Noise Sensor',
|
||||
|
||||
@@ -990,6 +990,13 @@ if ($type == "noise") {
|
||||
echo $output;
|
||||
}
|
||||
|
||||
if ($type == "throttled") {
|
||||
header('Content-Type: application/json');
|
||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/power/get_throttled.py';
|
||||
$output = shell_exec($command);
|
||||
echo $output;
|
||||
}
|
||||
|
||||
if ($type == "BME280") {
|
||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/BME280/read.py';
|
||||
$output = shell_exec($command);
|
||||
|
||||
@@ -15,6 +15,15 @@
|
||||
</div>
|
||||
|
||||
<div class="list-group" id="selftest_results">
|
||||
<!-- System: Power supply (under-voltage detection) -->
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center" id="test_power">
|
||||
<div>
|
||||
<strong>Power Supply</strong>
|
||||
<div class="small text-muted" id="test_power_detail">Waiting...</div>
|
||||
</div>
|
||||
<span id="test_power_status" class="badge bg-secondary">Pending</span>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic sensor test entries will be added here -->
|
||||
<div id="sensor_tests_container"></div>
|
||||
|
||||
|
||||
82
power/get_throttled.py
Normal file
82
power/get_throttled.py
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
python3 /var/www/nebuleair_pro_4g/power/get_throttled.py
|
||||
|
||||
Lit l'état d'alimentation du Raspberry Pi via `vcgencmd get_throttled` et
|
||||
renvoie un JSON décodé. Utilisé par le Self Test (launcher.php?type=throttled)
|
||||
pour détecter une sous-tension (cause fréquente de capteurs USB instables,
|
||||
corruptions SD, reboots).
|
||||
|
||||
Doit tourner en root (vcgencmd a besoin de /dev/vcio) : appelé via
|
||||
`sudo /usr/bin/python3 ...` depuis launcher.php.
|
||||
|
||||
Bits de get_throttled (cf. doc Raspberry Pi) :
|
||||
0 : sous-tension active maintenant
|
||||
1 : freq ARM bridée maintenant
|
||||
2 : throttling actif maintenant
|
||||
3 : limite temperature douce active maintenant
|
||||
16 : sous-tension survenue depuis le boot
|
||||
17 : bridage freq ARM survenu depuis le boot
|
||||
18 : throttling survenu depuis le boot
|
||||
19 : limite temperature douce survenue depuis le boot
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
raw = subprocess.check_output(
|
||||
['/usr/bin/vcgencmd', 'get_throttled'],
|
||||
stderr=subprocess.STDOUT,
|
||||
timeout=5,
|
||||
).decode('utf-8', errors='ignore').strip()
|
||||
except FileNotFoundError:
|
||||
print(json.dumps({"available": False, "error": "vcgencmd introuvable (pas un Raspberry Pi ?)"}))
|
||||
return
|
||||
except Exception as e:
|
||||
print(json.dumps({"available": False, "error": str(e)}))
|
||||
return
|
||||
|
||||
# Sortie attendue : "throttled=0x50000"
|
||||
if '=' not in raw:
|
||||
print(json.dumps({"available": False, "error": f"sortie inattendue: {raw}"}))
|
||||
return
|
||||
|
||||
hex_str = raw.split('=', 1)[1].strip()
|
||||
try:
|
||||
value = int(hex_str, 16)
|
||||
except ValueError:
|
||||
print(json.dumps({"available": False, "error": f"valeur illisible: {raw}"}))
|
||||
return
|
||||
|
||||
flags = {
|
||||
"under_voltage_now": bool(value & 0x1),
|
||||
"arm_freq_capped_now": bool(value & 0x2),
|
||||
"throttled_now": bool(value & 0x4),
|
||||
"soft_temp_limit_now": bool(value & 0x8),
|
||||
"under_voltage_occurred": bool(value & 0x10000),
|
||||
"arm_freq_capped_occurred": bool(value & 0x20000),
|
||||
"throttling_occurred": bool(value & 0x40000),
|
||||
"soft_temp_limit_occurred": bool(value & 0x80000),
|
||||
}
|
||||
|
||||
# Niveau de gravite pour le Self Test
|
||||
if flags["under_voltage_now"] or flags["throttled_now"]:
|
||||
status = "critical"
|
||||
message = "Sous-tension ACTIVE — alimentation 5V insuffisante (alim/cable a remplacer)"
|
||||
elif flags["under_voltage_occurred"] or flags["throttling_occurred"]:
|
||||
status = "warning"
|
||||
message = "Sous-tension survenue depuis le demarrage — verifier alim 5V / cable USB"
|
||||
else:
|
||||
status = "ok"
|
||||
message = "Alimentation OK"
|
||||
|
||||
result = {"available": True, "raw": hex_str, "value": value, "status": status, "message": message}
|
||||
result.update(flags)
|
||||
print(json.dumps(result))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user