diff --git a/html/saraR4.html b/html/saraR4.html
index e8b42bd..df0009e 100755
--- a/html/saraR4.html
+++ b/html/saraR4.html
@@ -419,6 +419,14 @@
Pending
+
+
+
+ SENSOR TESTS
+
+
+
+
@@ -1603,6 +1611,10 @@ function resetSelfTestUI() {
document.getElementById('test_network_status').textContent = 'Pending';
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
document.getElementById('selftest_logs').innerHTML = '';
@@ -1725,6 +1737,7 @@ async function selfTestSequence() {
selfTestReport.modemVersion = configResponse.modem_version || 'Unknown';
selfTestReport.latitude = configResponse.latitude_raw || 'N/A';
selfTestReport.longitude = configResponse.longitude_raw || 'N/A';
+ selfTestReport.config = configResponse;
// Get RTC time
try {
@@ -2011,6 +2024,186 @@ async function selfTestSequence() {
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 += `
+
+
+
${sensor.name}
+
Waiting...
+
+
Pending
+
`;
+ });
+ }
+
+ addSelfTestLog('');
+ addSelfTestLog('────────────────────────────────────────────────────────');
+ addSelfTestLog('SENSOR TESTS');
+ addSelfTestLog('────────────────────────────────────────────────────────');
+
+ // Run each sensor test
+ for (const sensor of sensorTests) {
+ await delay(500);
+
+ document.getElementById('selftest_status').innerHTML = `
+
+
+
Testing ${sensor.name}...
+
`;
+
+ 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) {
addSelfTestLog(`Test sequence error: ${error.message}`);
} finally {
@@ -2101,7 +2294,11 @@ GPS Location: ${selfTestReport.latitude || 'N/A'}, ${selfTestReport.longitude
modem: 'Modem Connection',
sim: 'SIM Card',
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)) {