feat(ui): add modem self-test functionality
Add "Run Self Test" button that opens a modal and runs diagnostic tests: - Automatically enables modem_config_mode before tests - Test 1: Modem connection (ATI command) - Test 2: SIM card detection (AT+CCID? command) - Respects delays between AT commands - Always disables modem_config_mode after tests (even on failure) - Shows real-time progress and detailed logs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
311
html/saraR4.html
311
html/saraR4.html
@@ -58,6 +58,14 @@
|
||||
<label class="form-check-label" for="check_modem_configMode">Mode configuration</label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-success mb-3" onclick="runSelfTest()" id="btn_selfTest">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2-circle me-1" viewBox="0 0 16 16">
|
||||
<path d="M2.5 8a5.5 5.5 0 1 1 11 0 5.5 5.5 0 0 1-11 0z"/>
|
||||
<path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l7-7z"/>
|
||||
</svg>
|
||||
Run Self Test
|
||||
</button>
|
||||
|
||||
<span id="modem_status_message"></span>
|
||||
<!--
|
||||
<h3>
|
||||
@@ -350,6 +358,62 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Self Test Modal -->
|
||||
<div class="modal fade" id="selfTestModal" tabindex="-1" aria-labelledby="selfTestModalLabel" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="selfTestModalLabel">Modem Self Test</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" id="selfTestCloseBtn" disabled></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="selftest_status" class="mb-3">
|
||||
<div class="d-flex align-items-center text-muted">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<span>Preparing test...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-group" id="selftest_results">
|
||||
<!-- Test 1: Modem Connection -->
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center" id="test_modem">
|
||||
<div>
|
||||
<strong>Modem Connection</strong>
|
||||
<div class="small text-muted" id="test_modem_detail">Waiting...</div>
|
||||
</div>
|
||||
<span id="test_modem_status" class="badge bg-secondary">Pending</span>
|
||||
</div>
|
||||
|
||||
<!-- Test 2: SIM Card -->
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center" id="test_sim">
|
||||
<div>
|
||||
<strong>SIM Card</strong>
|
||||
<div class="small text-muted" id="test_sim_detail">Waiting...</div>
|
||||
</div>
|
||||
<span id="test_sim_status" class="badge bg-secondary">Pending</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs section -->
|
||||
<div class="mt-3">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#selftest_logs_collapse">
|
||||
Show logs
|
||||
</button>
|
||||
<div class="collapse mt-2" id="selftest_logs_collapse">
|
||||
<div class="card card-body bg-light" style="max-height: 200px; overflow-y: auto;">
|
||||
<small><code id="selftest_logs"></code></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div id="selftest_summary" class="me-auto"></div>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="selfTestDoneBtn" disabled>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- toast -->
|
||||
|
||||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||||
@@ -1397,7 +1461,254 @@ function update_modem_configMode(param, checked){
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// SELF TEST FUNCTIONS
|
||||
// ============================================
|
||||
|
||||
function runSelfTest() {
|
||||
console.log("Starting Self Test...");
|
||||
|
||||
// Reset UI
|
||||
resetSelfTestUI();
|
||||
|
||||
// Show modal
|
||||
const modal = new bootstrap.Modal(document.getElementById('selfTestModal'));
|
||||
modal.show();
|
||||
|
||||
// Disable close buttons during test
|
||||
document.getElementById('selfTestCloseBtn').disabled = true;
|
||||
document.getElementById('selfTestDoneBtn').disabled = true;
|
||||
document.getElementById('btn_selfTest').disabled = true;
|
||||
|
||||
// Start test sequence
|
||||
selfTestSequence();
|
||||
}
|
||||
|
||||
function resetSelfTestUI() {
|
||||
// Reset status
|
||||
document.getElementById('selftest_status').innerHTML = `
|
||||
<div class="d-flex align-items-center text-muted">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<span>Preparing test...</span>
|
||||
</div>`;
|
||||
|
||||
// Reset test items
|
||||
document.getElementById('test_modem_status').className = 'badge bg-secondary';
|
||||
document.getElementById('test_modem_status').textContent = 'Pending';
|
||||
document.getElementById('test_modem_detail').textContent = 'Waiting...';
|
||||
|
||||
document.getElementById('test_sim_status').className = 'badge bg-secondary';
|
||||
document.getElementById('test_sim_status').textContent = 'Pending';
|
||||
document.getElementById('test_sim_detail').textContent = 'Waiting...';
|
||||
|
||||
// Reset logs
|
||||
document.getElementById('selftest_logs').innerHTML = '';
|
||||
|
||||
// Reset summary
|
||||
document.getElementById('selftest_summary').innerHTML = '';
|
||||
}
|
||||
|
||||
function addSelfTestLog(message) {
|
||||
const logsEl = document.getElementById('selftest_logs');
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
logsEl.innerHTML += `[${timestamp}] ${message}<br>`;
|
||||
// Auto-scroll to bottom
|
||||
logsEl.parentElement.scrollTop = logsEl.parentElement.scrollHeight;
|
||||
}
|
||||
|
||||
function updateTestStatus(testId, status, detail, badge) {
|
||||
document.getElementById(`test_${testId}_status`).className = `badge ${badge}`;
|
||||
document.getElementById(`test_${testId}_status`).textContent = status;
|
||||
document.getElementById(`test_${testId}_detail`).textContent = detail;
|
||||
}
|
||||
|
||||
function setConfigMode(enabled) {
|
||||
return new Promise((resolve, reject) => {
|
||||
addSelfTestLog(`Setting modem_config_mode to ${enabled}...`);
|
||||
|
||||
$.ajax({
|
||||
url: `launcher.php?type=update_config_sqlite¶m=modem_config_mode&value=${enabled}`,
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
cache: false,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
addSelfTestLog(`modem_config_mode set to ${enabled}`);
|
||||
// Update checkbox state
|
||||
document.getElementById('check_modem_configMode').checked = enabled;
|
||||
resolve(true);
|
||||
} else {
|
||||
addSelfTestLog(`Failed to set modem_config_mode: ${response.error || 'Unknown error'}`);
|
||||
reject(new Error(response.error || 'Failed to set config mode'));
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
addSelfTestLog(`AJAX error setting config mode: ${error}`);
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendATCommand(command, timeout) {
|
||||
return new Promise((resolve, reject) => {
|
||||
addSelfTestLog(`Sending AT command: ${command}`);
|
||||
|
||||
$.ajax({
|
||||
url: `launcher.php?type=sara&port=ttyAMA2&command=${encodeURIComponent(command)}&timeout=${timeout}`,
|
||||
dataType: 'text',
|
||||
method: 'GET',
|
||||
success: function(response) {
|
||||
addSelfTestLog(`Response: ${response.replace(/\n/g, ' | ')}`);
|
||||
resolve(response);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
addSelfTestLog(`AT command error: ${error}`);
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function selfTestSequence() {
|
||||
let testsPassed = 0;
|
||||
let testsFailed = 0;
|
||||
|
||||
try {
|
||||
// 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++;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
addSelfTestLog(`Test sequence error: ${error.message}`);
|
||||
} finally {
|
||||
// Always disable config mode at the end
|
||||
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>Disabling configuration mode...</span>
|
||||
</div>`;
|
||||
|
||||
try {
|
||||
await delay(500);
|
||||
await setConfigMode(false);
|
||||
} catch (error) {
|
||||
addSelfTestLog(`Warning: Failed to disable config mode: ${error.message}`);
|
||||
}
|
||||
|
||||
// Show final status
|
||||
const totalTests = testsPassed + testsFailed;
|
||||
let statusClass, statusIcon, statusText;
|
||||
|
||||
if (testsFailed === 0) {
|
||||
statusClass = 'text-success';
|
||||
statusIcon = '✓';
|
||||
statusText = 'All tests passed';
|
||||
} else if (testsPassed === 0) {
|
||||
statusClass = 'text-danger';
|
||||
statusIcon = '✗';
|
||||
statusText = 'All tests failed';
|
||||
} else {
|
||||
statusClass = 'text-warning';
|
||||
statusIcon = '!';
|
||||
statusText = 'Some tests failed';
|
||||
}
|
||||
|
||||
document.getElementById('selftest_status').innerHTML = `
|
||||
<div class="d-flex align-items-center ${statusClass}">
|
||||
<span class="fs-4 me-2">${statusIcon}</span>
|
||||
<span><strong>${statusText}</strong></span>
|
||||
</div>`;
|
||||
|
||||
document.getElementById('selftest_summary').innerHTML = `
|
||||
<span class="badge bg-success me-1">${testsPassed} passed</span>
|
||||
<span class="badge bg-danger">${testsFailed} failed</span>`;
|
||||
|
||||
// Enable close buttons
|
||||
document.getElementById('selfTestCloseBtn').disabled = false;
|
||||
document.getElementById('selfTestDoneBtn').disabled = false;
|
||||
document.getElementById('btn_selfTest').disabled = false;
|
||||
|
||||
addSelfTestLog('Self test completed.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user