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 <noreply@anthropic.com>
This commit is contained in:
549
html/saraR4.html
549
html/saraR4.html
@@ -1774,256 +1774,6 @@ async function selfTestSequence() {
|
|||||||
|
|
||||||
await delay(300);
|
await delay(300);
|
||||||
|
|
||||||
// Step 0: Check WiFi / Network status (informational, no pass/fail)
|
|
||||||
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 network status...</span>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
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 = `
|
|
||||||
<div class="d-flex align-items-center text-primary">
|
|
||||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
|
||||||
<span>Enabling configuration mode...</span>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
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 = `
|
|
||||||
<div class="d-flex align-items-center text-primary">
|
|
||||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
|
||||||
<span>Testing modem connection...</span>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
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 = `
|
|
||||||
<div class="d-flex align-items-center text-primary">
|
|
||||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
|
||||||
<span>Testing SIM card...</span>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
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 = `
|
|
||||||
<div class="d-flex align-items-center text-primary">
|
|
||||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
|
||||||
<span>Testing signal strength...</span>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
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 = `
|
|
||||||
<div class="d-flex align-items-center text-primary">
|
|
||||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
|
||||||
<span>Testing network connection...</span>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
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
|
// SENSOR TESTS - Test enabled sensors based on config
|
||||||
// ═══════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════
|
||||||
@@ -2049,22 +1799,19 @@ async function selfTestSequence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create sensor test UI entries dynamically
|
// Create sensor test UI entries dynamically
|
||||||
if (sensorTests.length > 0) {
|
const sensorContainer = document.getElementById('sensor_tests_container');
|
||||||
document.getElementById('sensor_tests_separator').style.display = '';
|
sensorContainer.innerHTML = '';
|
||||||
const sensorContainer = document.getElementById('sensor_tests_container');
|
|
||||||
sensorContainer.innerHTML = '';
|
|
||||||
|
|
||||||
sensorTests.forEach(sensor => {
|
sensorTests.forEach(sensor => {
|
||||||
sensorContainer.innerHTML += `
|
sensorContainer.innerHTML += `
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center" id="test_${sensor.id}">
|
<div class="list-group-item d-flex justify-content-between align-items-center" id="test_${sensor.id}">
|
||||||
<div>
|
<div>
|
||||||
<strong>${sensor.name}</strong>
|
<strong>${sensor.name}</strong>
|
||||||
<div class="small text-muted" id="test_${sensor.id}_detail">Waiting...</div>
|
<div class="small text-muted" id="test_${sensor.id}_detail">Waiting...</div>
|
||||||
</div>
|
</div>
|
||||||
<span id="test_${sensor.id}_status" class="badge bg-secondary">Pending</span>
|
<span id="test_${sensor.id}_status" class="badge bg-secondary">Pending</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
addSelfTestLog('');
|
addSelfTestLog('');
|
||||||
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 = `
|
||||||
|
<div class="d-flex align-items-center text-primary">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
|
<span>Checking network status...</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="d-flex align-items-center text-primary">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
|
<span>Enabling configuration mode...</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="d-flex align-items-center text-primary">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
|
<span>Testing modem connection...</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="d-flex align-items-center text-primary">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
|
<span>Testing SIM card...</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="d-flex align-items-center text-primary">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
|
<span>Testing signal strength...</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="d-flex align-items-center text-primary">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
|
<span>Testing network connection...</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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) {
|
} catch (error) {
|
||||||
addSelfTestLog(`Test sequence error: ${error.message}`);
|
addSelfTestLog(`Test sequence error: ${error.message}`);
|
||||||
} finally {
|
} 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 = {
|
const testNames = {
|
||||||
|
npm: 'NextPM (Particles)',
|
||||||
|
bme280: 'BME280 (Temp/Hum)',
|
||||||
|
noise: 'Noise Sensor',
|
||||||
|
envea: 'Envea (Gas Sensors)',
|
||||||
wifi: 'WiFi/Network',
|
wifi: 'WiFi/Network',
|
||||||
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)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user