diff --git a/html/saraR4.html b/html/saraR4.html
index b241061..a1081a1 100755
--- a/html/saraR4.html
+++ b/html/saraR4.html
@@ -58,6 +58,14 @@
Mode configuration
+
+
+
+
+
+ Run Self Test
+
+
+
@@ -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 = `
+
`;
+
+ // 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}
`;
+ // 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 = `
+
+
+
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++;
+ }
+
+ } catch (error) {
+ addSelfTestLog(`Test sequence error: ${error.message}`);
+ } finally {
+ // Always disable config mode at the end
+ document.getElementById('selftest_status').innerHTML = `
+
+
+
Disabling configuration mode...
+
`;
+
+ 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 = `
+
+ ${statusIcon}
+ ${statusText}
+
`;
+
+ document.getElementById('selftest_summary').innerHTML = `
+
${testsPassed} passed
+
${testsFailed} failed `;
+
+ // Enable close buttons
+ document.getElementById('selfTestCloseBtn').disabled = false;
+ document.getElementById('selfTestDoneBtn').disabled = false;
+ document.getElementById('btn_selfTest').disabled = false;
+
+ addSelfTestLog('Self test completed.');
+ }
+}