feat(ui): add sensor tests to modem self-test

Add dynamic sensor testing (NPM, BME280, Noise, Envea) to the self-test
based on enabled sensors in config. Results are included in the diagnostic report.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
PaulVua
2026-02-10 16:58:45 +01:00
parent 98cb1ea517
commit 6bdaef8c24

View File

@@ -419,6 +419,14 @@
</div> </div>
<span id="test_network_status" class="badge bg-secondary">Pending</span> <span id="test_network_status" class="badge bg-secondary">Pending</span>
</div> </div>
<!-- Separator for sensor tests -->
<div id="sensor_tests_separator" class="list-group-item bg-light text-center py-1" style="display:none;">
<small class="text-muted fw-bold">SENSOR TESTS</small>
</div>
<!-- Dynamic sensor test entries will be added here -->
<div id="sensor_tests_container"></div>
</div> </div>
<!-- Logs section --> <!-- Logs section -->
@@ -1603,6 +1611,10 @@ function resetSelfTestUI() {
document.getElementById('test_network_status').textContent = 'Pending'; document.getElementById('test_network_status').textContent = 'Pending';
document.getElementById('test_network_detail').textContent = 'Waiting...'; document.getElementById('test_network_detail').textContent = 'Waiting...';
// Reset sensor tests
document.getElementById('sensor_tests_separator').style.display = 'none';
document.getElementById('sensor_tests_container').innerHTML = '';
// Reset logs // Reset logs
document.getElementById('selftest_logs').innerHTML = ''; document.getElementById('selftest_logs').innerHTML = '';
@@ -1725,6 +1737,7 @@ async function selfTestSequence() {
selfTestReport.modemVersion = configResponse.modem_version || 'Unknown'; selfTestReport.modemVersion = configResponse.modem_version || 'Unknown';
selfTestReport.latitude = configResponse.latitude_raw || 'N/A'; selfTestReport.latitude = configResponse.latitude_raw || 'N/A';
selfTestReport.longitude = configResponse.longitude_raw || 'N/A'; selfTestReport.longitude = configResponse.longitude_raw || 'N/A';
selfTestReport.config = configResponse;
// Get RTC time // Get RTC time
try { try {
@@ -2011,6 +2024,186 @@ async function selfTestSequence() {
testsFailed++; testsFailed++;
} }
// ═══════════════════════════════════════════════════════
// SENSOR TESTS - Test enabled sensors based on config
// ═══════════════════════════════════════════════════════
const config = selfTestReport.config || {};
const sensorTests = [];
// NPM is always present
sensorTests.push({ id: 'npm', name: 'NextPM (Particles)', type: 'npm', port: 'ttyAMA5' });
// BME280 if enabled
if (config.BME280) {
sensorTests.push({ id: 'bme280', name: 'BME280 (Temp/Hum)', type: 'BME280' });
}
// Noise if enabled
if (config.NOISE) {
sensorTests.push({ id: 'noise', name: 'Noise Sensor', type: 'noise' });
}
// Envea if enabled
if (config.envea) {
sensorTests.push({ id: 'envea', name: 'Envea (Gas Sensors)', type: 'envea' });
}
// Create sensor test UI entries dynamically
if (sensorTests.length > 0) {
document.getElementById('sensor_tests_separator').style.display = '';
const sensorContainer = document.getElementById('sensor_tests_container');
sensorContainer.innerHTML = '';
sensorTests.forEach(sensor => {
sensorContainer.innerHTML += `
<div class="list-group-item d-flex justify-content-between align-items-center" id="test_${sensor.id}">
<div>
<strong>${sensor.name}</strong>
<div class="small text-muted" id="test_${sensor.id}_detail">Waiting...</div>
</div>
<span id="test_${sensor.id}_status" class="badge bg-secondary">Pending</span>
</div>`;
});
}
addSelfTestLog('');
addSelfTestLog('────────────────────────────────────────────────────────');
addSelfTestLog('SENSOR TESTS');
addSelfTestLog('────────────────────────────────────────────────────────');
// Run each sensor test
for (const sensor of sensorTests) {
await delay(500);
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>Testing ${sensor.name}...</span>
</div>`;
updateTestStatus(sensor.id, 'Testing...', 'Reading sensor data...', 'bg-info');
addSelfTestLog(`Testing ${sensor.name}...`);
try {
if (sensor.type === 'npm') {
// NPM sensor test
const npmResult = await new Promise((resolve, reject) => {
$.ajax({
url: 'launcher.php?type=npm&port=' + sensor.port,
dataType: 'json',
method: 'GET',
timeout: 15000,
success: function(data) { resolve(data); },
error: function(xhr, status, error) { reject(new Error(error || status)); }
});
});
selfTestReport.rawResponses['NPM Sensor'] = JSON.stringify(npmResult, null, 2);
addSelfTestLog(`NPM response: PM1=${npmResult.PM1}, PM2.5=${npmResult.PM25}, PM10=${npmResult.PM10}`);
// Check for errors
const npmErrors = ['notReady', 'fanError', 'laserError', 'heatError', 't_rhError', 'memoryError', 'degradedState'];
const activeErrors = npmErrors.filter(e => npmResult[e] === 1);
if (activeErrors.length > 0) {
updateTestStatus(sensor.id, 'Warning', `Errors: ${activeErrors.join(', ')}`, 'bg-warning');
testsFailed++;
} else if (npmResult.PM1 !== undefined && npmResult.PM25 !== undefined && npmResult.PM10 !== undefined) {
updateTestStatus(sensor.id, 'Passed', `PM1: ${npmResult.PM1} | PM2.5: ${npmResult.PM25} | PM10: ${npmResult.PM10} ug/m3`, 'bg-success');
testsPassed++;
} else {
updateTestStatus(sensor.id, 'Warning', 'Incomplete data received', 'bg-warning');
testsFailed++;
}
} else if (sensor.type === 'BME280') {
// BME280 sensor test
const bme280Result = await new Promise((resolve, reject) => {
$.ajax({
url: 'launcher.php?type=BME280',
dataType: 'text',
method: 'GET',
timeout: 15000,
success: function(data) { resolve(data); },
error: function(xhr, status, error) { reject(new Error(error || status)); }
});
});
const bmeData = JSON.parse(bme280Result);
selfTestReport.rawResponses['BME280 Sensor'] = JSON.stringify(bmeData, null, 2);
addSelfTestLog(`BME280 response: temp=${bmeData.temp}, hum=${bmeData.hum}, press=${bmeData.press}`);
if (bmeData.temp !== undefined && bmeData.hum !== undefined && bmeData.press !== undefined) {
updateTestStatus(sensor.id, 'Passed', `${bmeData.temp}°C | ${bmeData.hum}% | ${bmeData.press} hPa`, 'bg-success');
testsPassed++;
} else {
updateTestStatus(sensor.id, 'Warning', 'Incomplete data received', 'bg-warning');
testsFailed++;
}
} else if (sensor.type === 'noise') {
// Noise sensor test
const noiseResult = await new Promise((resolve, reject) => {
$.ajax({
url: 'launcher.php?type=noise',
dataType: 'text',
method: 'GET',
timeout: 15000,
success: function(data) { resolve(data); },
error: function(xhr, status, error) { reject(new Error(error || status)); }
});
});
selfTestReport.rawResponses['Noise Sensor'] = noiseResult;
addSelfTestLog(`Noise response: ${noiseResult.trim()}`);
const noiseValue = parseFloat(noiseResult.trim());
if (!isNaN(noiseValue) && noiseValue > 0) {
updateTestStatus(sensor.id, 'Passed', `${noiseValue} dB`, 'bg-success');
testsPassed++;
} else if (noiseResult.trim() !== '') {
updateTestStatus(sensor.id, 'Warning', `Unexpected value: ${noiseResult.trim()}`, 'bg-warning');
testsFailed++;
} else {
updateTestStatus(sensor.id, 'Failed', 'No data received', 'bg-danger');
testsFailed++;
}
} else if (sensor.type === 'envea') {
// Envea sensor test - use the debug endpoint for all sensors
const enveaResult = await new Promise((resolve, reject) => {
$.ajax({
url: 'launcher.php?type=envea_debug',
dataType: 'text',
method: 'GET',
timeout: 30000,
success: function(data) { resolve(data); },
error: function(xhr, status, error) { reject(new Error(error || status)); }
});
});
selfTestReport.rawResponses['Envea Sensors'] = enveaResult;
addSelfTestLog(`Envea response: ${enveaResult.trim().substring(0, 200)}`);
if (enveaResult.trim() !== '' && !enveaResult.toLowerCase().includes('error')) {
updateTestStatus(sensor.id, 'Passed', 'Sensors responding', 'bg-success');
testsPassed++;
} else if (enveaResult.toLowerCase().includes('error')) {
updateTestStatus(sensor.id, 'Failed', 'Sensor error detected', 'bg-danger');
testsFailed++;
} else {
updateTestStatus(sensor.id, 'Failed', 'No data received', 'bg-danger');
testsFailed++;
}
}
} catch (error) {
addSelfTestLog(`${sensor.name} test error: ${error.message}`);
updateTestStatus(sensor.id, 'Failed', error.message, 'bg-danger');
selfTestReport.rawResponses[`${sensor.name}`] = `ERROR: ${error.message}`;
testsFailed++;
}
}
} catch (error) { } catch (error) {
addSelfTestLog(`Test sequence error: ${error.message}`); addSelfTestLog(`Test sequence error: ${error.message}`);
} finally { } finally {
@@ -2101,7 +2294,11 @@ GPS Location: ${selfTestReport.latitude || 'N/A'}, ${selfTestReport.longitude
modem: 'Modem Connection', modem: 'Modem Connection',
sim: 'SIM Card', sim: 'SIM Card',
signal: 'Signal Strength', signal: 'Signal Strength',
network: 'Network Connection' network: 'Network Connection',
npm: 'NextPM (Particles)',
bme280: 'BME280 (Temp/Hum)',
noise: 'Noise Sensor',
envea: 'Envea (Gas Sensors)'
}; };
for (const [testId, name] of Object.entries(testNames)) { for (const [testId, name] of Object.entries(testNames)) {