From dc1739e033fa3363b1ebe4cd3c18c2852c8c656e Mon Sep 17 00:00:00 2001 From: PaulVua Date: Tue, 10 Feb 2026 17:05:35 +0100 Subject: [PATCH] feat(ui): reorder self-test to run sensor tests before communication Sensor tests (NPM, BME280, Noise, Envea) now run first, followed by communication tests (WiFi, Modem, SIM, Signal, Network) with a visual separator between the two sections. Co-Authored-By: Claude Opus 4.6 --- html/saraR4.html | 549 ++++++++++++++++++++++++----------------------- 1 file changed, 278 insertions(+), 271 deletions(-) diff --git a/html/saraR4.html b/html/saraR4.html index d1f2107..dbfba94 100755 --- a/html/saraR4.html +++ b/html/saraR4.html @@ -1774,256 +1774,6 @@ async function selfTestSequence() { await delay(300); - // Step 0: Check WiFi / Network status (informational, no pass/fail) - document.getElementById('selftest_status').innerHTML = ` -
-
- Checking network status... -
`; - - updateTestStatus('wifi', 'Checking...', 'Getting network info...', 'bg-info'); - - try { - const wifiResponse = await new Promise((resolve, reject) => { - $.ajax({ - url: 'launcher.php?type=wifi_status', - dataType: 'json', - method: 'GET', - success: function(data) { - addSelfTestLog(`WiFi status received`); - // Store raw response - selfTestReport.rawResponses['WiFi Status'] = JSON.stringify(data, null, 2); - resolve(data); - }, - error: function(xhr, status, error) { - addSelfTestLog(`WiFi status error: ${error}`); - selfTestReport.rawResponses['WiFi Status'] = `ERROR: ${error}`; - reject(new Error(error)); - } - }); - }); - - // Log detailed WiFi info - addSelfTestLog(`Mode: ${wifiResponse.mode}, SSID: ${wifiResponse.ssid}, IP: ${wifiResponse.ip}, Hostname: ${wifiResponse.hostname}`); - - if (wifiResponse.connected) { - let modeIcon = ''; - let modeLabel = ''; - let badgeClass = 'bg-info'; - - if (wifiResponse.mode === 'hotspot') { - modeIcon = '📡'; - modeLabel = 'Hotspot'; - badgeClass = 'bg-warning text-dark'; - } else if (wifiResponse.mode === 'wifi') { - modeIcon = '📶'; - modeLabel = 'WiFi'; - badgeClass = 'bg-info'; - } else if (wifiResponse.mode === 'ethernet') { - modeIcon = '🔌'; - modeLabel = 'Ethernet'; - badgeClass = 'bg-info'; - } - - const detailText = `${wifiResponse.ssid} | ${wifiResponse.ip} | ${wifiResponse.hostname}.local`; - updateTestStatus('wifi', modeLabel, detailText, badgeClass); - } else { - updateTestStatus('wifi', 'Disconnected', 'No network connection', 'bg-secondary'); - } - } catch (error) { - updateTestStatus('wifi', 'Error', error.message, 'bg-secondary'); - } - - await delay(500); - - // Step 1: Enable config mode - document.getElementById('selftest_status').innerHTML = ` -
-
- Enabling configuration mode... -
`; - - await setConfigMode(true); - - // Wait for SARA script to release the port (2 seconds should be enough) - addSelfTestLog('Waiting for modem port to be available...'); - await delay(2000); - - // Step 2: Test Modem Connection (ATI) - document.getElementById('selftest_status').innerHTML = ` -
-
- Testing modem connection... -
`; - - updateTestStatus('modem', 'Testing...', 'Sending ATI command...', 'bg-info'); - - try { - const modemResponse = await sendATCommand('ATI', 5); - - if (modemResponse.includes('OK') && (modemResponse.toUpperCase().includes('SARA-R5') || modemResponse.toUpperCase().includes('SARA-R4'))) { - // Extract model - const modelMatch = modemResponse.match(/SARA-R[45]\d*[A-Z]*-\d+[A-Z]*-\d+/i); - const model = modelMatch ? modelMatch[0] : 'SARA module'; - updateTestStatus('modem', 'Passed', `Model: ${model}`, 'bg-success'); - testsPassed++; - } else if (modemResponse.includes('OK')) { - updateTestStatus('modem', 'Passed', 'Modem responding', 'bg-success'); - testsPassed++; - } else { - updateTestStatus('modem', 'Failed', 'No valid response', 'bg-danger'); - testsFailed++; - } - } catch (error) { - updateTestStatus('modem', 'Failed', error.message, 'bg-danger'); - testsFailed++; - } - - // Delay between AT commands - await delay(1000); - - // Step 3: Test SIM Card (AT+CCID?) - document.getElementById('selftest_status').innerHTML = ` -
-
- Testing SIM card... -
`; - - updateTestStatus('sim', 'Testing...', 'Sending AT+CCID? command...', 'bg-info'); - - try { - const simResponse = await sendATCommand('AT+CCID?', 5); - - const ccidMatch = simResponse.match(/\+CCID:\s*(\d{18,22})/); - if (simResponse.includes('OK') && ccidMatch) { - const iccid = ccidMatch[1]; - // Show last 4 digits only for privacy - const maskedIccid = '****' + iccid.slice(-4); - updateTestStatus('sim', 'Passed', `ICCID: ...${maskedIccid}`, 'bg-success'); - testsPassed++; - } else if (simResponse.includes('ERROR')) { - updateTestStatus('sim', 'Failed', 'SIM card not detected', 'bg-danger'); - testsFailed++; - } else { - updateTestStatus('sim', 'Warning', 'Unable to read ICCID', 'bg-warning'); - testsFailed++; - } - } catch (error) { - updateTestStatus('sim', 'Failed', error.message, 'bg-danger'); - testsFailed++; - } - - // Delay between AT commands - await delay(1000); - - // Step 4: Test Signal Strength (AT+CSQ) - document.getElementById('selftest_status').innerHTML = ` -
-
- Testing signal strength... -
`; - - updateTestStatus('signal', 'Testing...', 'Sending AT+CSQ command...', 'bg-info'); - - try { - const signalResponse = await sendATCommand('AT+CSQ', 5); - - const csqMatch = signalResponse.match(/\+CSQ:\s*(\d+),(\d+)/); - if (signalResponse.includes('OK') && csqMatch) { - const signalPower = parseInt(csqMatch[1]); - const qual = parseInt(csqMatch[2]); - - if (signalPower === 99) { - updateTestStatus('signal', 'Failed', 'No signal detected', 'bg-danger'); - testsFailed++; - } else if (signalPower === 0) { - updateTestStatus('signal', 'Warning', 'Very poor signal (0/31)', 'bg-warning'); - testsFailed++; - } else if (signalPower <= 24) { - updateTestStatus('signal', 'Passed', `Poor signal (${signalPower}/31)`, 'bg-success'); - testsPassed++; - } else if (signalPower <= 26) { - updateTestStatus('signal', 'Passed', `Good signal (${signalPower}/31)`, 'bg-success'); - testsPassed++; - } else if (signalPower <= 28) { - updateTestStatus('signal', 'Passed', `Very good signal (${signalPower}/31)`, 'bg-success'); - testsPassed++; - } else { - updateTestStatus('signal', 'Passed', `Excellent signal (${signalPower}/31)`, 'bg-success'); - testsPassed++; - } - } else if (signalResponse.includes('ERROR')) { - updateTestStatus('signal', 'Failed', 'Unable to read signal', 'bg-danger'); - testsFailed++; - } else { - updateTestStatus('signal', 'Warning', 'Unexpected response', 'bg-warning'); - testsFailed++; - } - } catch (error) { - updateTestStatus('signal', 'Failed', error.message, 'bg-danger'); - testsFailed++; - } - - // Delay between AT commands - await delay(1000); - - // Step 5: Test Network Connection (AT+COPS?) - document.getElementById('selftest_status').innerHTML = ` -
-
- Testing network connection... -
`; - - updateTestStatus('network', 'Testing...', 'Sending AT+COPS? command...', 'bg-info'); - - try { - // Load operators data for network name lookup - let opData = null; - try { - opData = await loadOperatorsData(); - } catch (e) { - addSelfTestLog('Warning: Could not load operators data'); - } - - const networkResponse = await sendATCommand('AT+COPS?', 5); - - const copsMatch = networkResponse.match(/\+COPS:\s*(\d+)(?:,(\d+),"?([^",]+)"?,(\d+))?/); - if (networkResponse.includes('OK') && copsMatch) { - const mode = copsMatch[1]; - const oper = copsMatch[3]; - const act = copsMatch[4]; - - if (oper) { - // Get operator name from lookup table - let operatorName = oper; - if (opData && opData.operators && opData.operators[oper]) { - operatorName = opData.operators[oper].name; - } - - // Get access technology - let actDesc = 'Unknown'; - if (opData && opData.accessTechnology && opData.accessTechnology[act]) { - actDesc = opData.accessTechnology[act]; - } - - updateTestStatus('network', 'Passed', `${operatorName} (${actDesc})`, 'bg-success'); - testsPassed++; - } else { - updateTestStatus('network', 'Warning', 'Not registered to network', 'bg-warning'); - testsFailed++; - } - } else if (networkResponse.includes('ERROR')) { - updateTestStatus('network', 'Failed', 'Unable to get network info', 'bg-danger'); - testsFailed++; - } else { - updateTestStatus('network', 'Warning', 'Unexpected response', 'bg-warning'); - testsFailed++; - } - } catch (error) { - updateTestStatus('network', 'Failed', error.message, 'bg-danger'); - testsFailed++; - } - // ═══════════════════════════════════════════════════════ // SENSOR TESTS - Test enabled sensors based on config // ═══════════════════════════════════════════════════════ @@ -2049,22 +1799,19 @@ async function selfTestSequence() { } // 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 = ''; + const sensorContainer = document.getElementById('sensor_tests_container'); + sensorContainer.innerHTML = ''; - sensorTests.forEach(sensor => { - sensorContainer.innerHTML += ` -
-
- ${sensor.name} -
Waiting...
-
- Pending -
`; - }); - } + sensorTests.forEach(sensor => { + sensorContainer.innerHTML += ` +
+
+ ${sensor.name} +
Waiting...
+
+ Pending +
`; + }); addSelfTestLog(''); addSelfTestLog('────────────────────────────────────────────────────────'); @@ -2204,6 +1951,266 @@ async function selfTestSequence() { } } + // ═══════════════════════════════════════════════════════ + // COMMUNICATION TESTS - WiFi, Modem, SIM, Signal, Network + // ═══════════════════════════════════════════════════════ + addSelfTestLog(''); + addSelfTestLog('────────────────────────────────────────────────────────'); + addSelfTestLog('COMMUNICATION TESTS'); + addSelfTestLog('────────────────────────────────────────────────────────'); + + document.getElementById('comm_tests_separator').style.display = ''; + + // Check WiFi / Network status (informational, no pass/fail) + document.getElementById('selftest_status').innerHTML = ` +
+
+ Checking network status... +
`; + + updateTestStatus('wifi', 'Checking...', 'Getting network info...', 'bg-info'); + + try { + const wifiResponse = await new Promise((resolve, reject) => { + $.ajax({ + url: 'launcher.php?type=wifi_status', + dataType: 'json', + method: 'GET', + success: function(data) { + addSelfTestLog(`WiFi status received`); + // Store raw response + selfTestReport.rawResponses['WiFi Status'] = JSON.stringify(data, null, 2); + resolve(data); + }, + error: function(xhr, status, error) { + addSelfTestLog(`WiFi status error: ${error}`); + selfTestReport.rawResponses['WiFi Status'] = `ERROR: ${error}`; + reject(new Error(error)); + } + }); + }); + + // Log detailed WiFi info + addSelfTestLog(`Mode: ${wifiResponse.mode}, SSID: ${wifiResponse.ssid}, IP: ${wifiResponse.ip}, Hostname: ${wifiResponse.hostname}`); + + if (wifiResponse.connected) { + let modeIcon = ''; + let modeLabel = ''; + let badgeClass = 'bg-info'; + + if (wifiResponse.mode === 'hotspot') { + modeIcon = '📡'; + modeLabel = 'Hotspot'; + badgeClass = 'bg-warning text-dark'; + } else if (wifiResponse.mode === 'wifi') { + modeIcon = '📶'; + modeLabel = 'WiFi'; + badgeClass = 'bg-info'; + } else if (wifiResponse.mode === 'ethernet') { + modeIcon = '🔌'; + modeLabel = 'Ethernet'; + badgeClass = 'bg-info'; + } + + const detailText = `${wifiResponse.ssid} | ${wifiResponse.ip} | ${wifiResponse.hostname}.local`; + updateTestStatus('wifi', modeLabel, detailText, badgeClass); + } else { + updateTestStatus('wifi', 'Disconnected', 'No network connection', 'bg-secondary'); + } + } catch (error) { + updateTestStatus('wifi', 'Error', error.message, 'bg-secondary'); + } + + await delay(500); + + // Enable config mode + document.getElementById('selftest_status').innerHTML = ` +
+
+ Enabling configuration mode... +
`; + + await setConfigMode(true); + + // Wait for SARA script to release the port (2 seconds should be enough) + addSelfTestLog('Waiting for modem port to be available...'); + await delay(2000); + + // Test Modem Connection (ATI) + document.getElementById('selftest_status').innerHTML = ` +
+
+ Testing modem connection... +
`; + + updateTestStatus('modem', 'Testing...', 'Sending ATI command...', 'bg-info'); + + try { + const modemResponse = await sendATCommand('ATI', 5); + + if (modemResponse.includes('OK') && (modemResponse.toUpperCase().includes('SARA-R5') || modemResponse.toUpperCase().includes('SARA-R4'))) { + // Extract model + const modelMatch = modemResponse.match(/SARA-R[45]\d*[A-Z]*-\d+[A-Z]*-\d+/i); + const model = modelMatch ? modelMatch[0] : 'SARA module'; + updateTestStatus('modem', 'Passed', `Model: ${model}`, 'bg-success'); + testsPassed++; + } else if (modemResponse.includes('OK')) { + updateTestStatus('modem', 'Passed', 'Modem responding', 'bg-success'); + testsPassed++; + } else { + updateTestStatus('modem', 'Failed', 'No valid response', 'bg-danger'); + testsFailed++; + } + } catch (error) { + updateTestStatus('modem', 'Failed', error.message, 'bg-danger'); + testsFailed++; + } + + // Delay between AT commands + await delay(1000); + + // Test SIM Card (AT+CCID?) + document.getElementById('selftest_status').innerHTML = ` +
+
+ Testing SIM card... +
`; + + updateTestStatus('sim', 'Testing...', 'Sending AT+CCID? command...', 'bg-info'); + + try { + const simResponse = await sendATCommand('AT+CCID?', 5); + + const ccidMatch = simResponse.match(/\+CCID:\s*(\d{18,22})/); + if (simResponse.includes('OK') && ccidMatch) { + const iccid = ccidMatch[1]; + // Show last 4 digits only for privacy + const maskedIccid = '****' + iccid.slice(-4); + updateTestStatus('sim', 'Passed', `ICCID: ...${maskedIccid}`, 'bg-success'); + testsPassed++; + } else if (simResponse.includes('ERROR')) { + updateTestStatus('sim', 'Failed', 'SIM card not detected', 'bg-danger'); + testsFailed++; + } else { + updateTestStatus('sim', 'Warning', 'Unable to read ICCID', 'bg-warning'); + testsFailed++; + } + } catch (error) { + updateTestStatus('sim', 'Failed', error.message, 'bg-danger'); + testsFailed++; + } + + // Delay between AT commands + await delay(1000); + + // Test Signal Strength (AT+CSQ) + document.getElementById('selftest_status').innerHTML = ` +
+
+ Testing signal strength... +
`; + + updateTestStatus('signal', 'Testing...', 'Sending AT+CSQ command...', 'bg-info'); + + try { + const signalResponse = await sendATCommand('AT+CSQ', 5); + + const csqMatch = signalResponse.match(/\+CSQ:\s*(\d+),(\d+)/); + if (signalResponse.includes('OK') && csqMatch) { + const signalPower = parseInt(csqMatch[1]); + const qual = parseInt(csqMatch[2]); + + if (signalPower === 99) { + updateTestStatus('signal', 'Failed', 'No signal detected', 'bg-danger'); + testsFailed++; + } else if (signalPower === 0) { + updateTestStatus('signal', 'Warning', 'Very poor signal (0/31)', 'bg-warning'); + testsFailed++; + } else if (signalPower <= 24) { + updateTestStatus('signal', 'Passed', `Poor signal (${signalPower}/31)`, 'bg-success'); + testsPassed++; + } else if (signalPower <= 26) { + updateTestStatus('signal', 'Passed', `Good signal (${signalPower}/31)`, 'bg-success'); + testsPassed++; + } else if (signalPower <= 28) { + updateTestStatus('signal', 'Passed', `Very good signal (${signalPower}/31)`, 'bg-success'); + testsPassed++; + } else { + updateTestStatus('signal', 'Passed', `Excellent signal (${signalPower}/31)`, 'bg-success'); + testsPassed++; + } + } else if (signalResponse.includes('ERROR')) { + updateTestStatus('signal', 'Failed', 'Unable to read signal', 'bg-danger'); + testsFailed++; + } else { + updateTestStatus('signal', 'Warning', 'Unexpected response', 'bg-warning'); + testsFailed++; + } + } catch (error) { + updateTestStatus('signal', 'Failed', error.message, 'bg-danger'); + testsFailed++; + } + + // Delay between AT commands + await delay(1000); + + // Test Network Connection (AT+COPS?) + document.getElementById('selftest_status').innerHTML = ` +
+
+ Testing network connection... +
`; + + updateTestStatus('network', 'Testing...', 'Sending AT+COPS? command...', 'bg-info'); + + try { + // Load operators data for network name lookup + let opData = null; + try { + opData = await loadOperatorsData(); + } catch (e) { + addSelfTestLog('Warning: Could not load operators data'); + } + + const networkResponse = await sendATCommand('AT+COPS?', 5); + + const copsMatch = networkResponse.match(/\+COPS:\s*(\d+)(?:,(\d+),"?([^",]+)"?,(\d+))?/); + if (networkResponse.includes('OK') && copsMatch) { + const mode = copsMatch[1]; + const oper = copsMatch[3]; + const act = copsMatch[4]; + + if (oper) { + // Get operator name from lookup table + let operatorName = oper; + if (opData && opData.operators && opData.operators[oper]) { + operatorName = opData.operators[oper].name; + } + + // Get access technology + let actDesc = 'Unknown'; + if (opData && opData.accessTechnology && opData.accessTechnology[act]) { + actDesc = opData.accessTechnology[act]; + } + + updateTestStatus('network', 'Passed', `${operatorName} (${actDesc})`, 'bg-success'); + testsPassed++; + } else { + updateTestStatus('network', 'Warning', 'Not registered to network', 'bg-warning'); + testsFailed++; + } + } else if (networkResponse.includes('ERROR')) { + updateTestStatus('network', 'Failed', 'Unable to get network info', 'bg-danger'); + testsFailed++; + } else { + updateTestStatus('network', 'Warning', 'Unexpected response', 'bg-warning'); + testsFailed++; + } + } catch (error) { + updateTestStatus('network', 'Failed', error.message, 'bg-danger'); + testsFailed++; + } + } catch (error) { addSelfTestLog(`Test sequence error: ${error.message}`); } finally { @@ -2288,17 +2295,17 @@ GPS Location: ${selfTestReport.latitude || 'N/A'}, ${selfTestReport.longitude `; - // Add test results + // Add test results (sensors first, then communication) const testNames = { + npm: 'NextPM (Particles)', + bme280: 'BME280 (Temp/Hum)', + noise: 'Noise Sensor', + envea: 'Envea (Gas Sensors)', wifi: 'WiFi/Network', modem: 'Modem Connection', sim: 'SIM Card', signal: 'Signal Strength', - network: 'Network Connection', - npm: 'NextPM (Particles)', - bme280: 'BME280 (Temp/Hum)', - noise: 'Noise Sensor', - envea: 'Envea (Gas Sensors)' + network: 'Network Connection' }; for (const [testId, name] of Object.entries(testNames)) {