Files
nebuleair_pro_4g/html/saraR4.html
2026-04-02 11:29:02 +02:00

1603 lines
69 KiB
HTML
Executable File

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NebuleAir</title>
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<style>
body {
overflow-x: hidden;
}
#sidebar a.nav-link {
position: relative;
display: flex;
align-items: center;
}
#sidebar a.nav-link:hover {
background-color: rgba(0, 0, 0, 0.5);
}
#sidebar a.nav-link svg {
margin-right: 8px; /* Add spacing between icons and text */
}
#sidebar {
transition: transform 0.3s ease-in-out;
}
.offcanvas-backdrop {
z-index: 1040;
}
</style>
</head>
<body>
<!-- Topbar -->
<span id="topbar"></span>
<!-- Sidebar Offcanvas for Mobile -->
<div class="offcanvas offcanvas-start text-white bg-dark" tabindex="-1" id="sidebarOffcanvas" aria-labelledby="sidebarOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="sidebarOffcanvasLabel">NebuleAir</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" id="sidebar_mobile">
</div>
</div>
<div class="container-fluid mt-5">
<div class="row">
<aside class="col-md-2 col-lg-1 d-none d-md-block vh-100 position-fixed bg-dark text-white" id="sidebar">
</aside>
<!-- Main content -->
<main class="col-md-10 ms-sm-auto col-lg-11 offset-md-2 offset-lg-1 px-md-4">
<h1 class="mt-4">Modem 4G</h1>
<h4 id="modem_version"></h4>
<p>Votre capteur est équipé d'un modem 4G et d'une carte SIM afin d'envoyer les mesures sur internet.</p>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" role="switch" id="check_modem_configMode" onchange="update_modem_configMode('modem_config_mode',this.checked)">
<label class="form-check-label" for="check_modem_configMode">Mode configuration</label>
</div>
<div id="configmode_alert" class="alert alert-danger align-items-center py-3 mb-3" role="alert" style="display:none;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-exclamation-triangle-fill me-3 flex-shrink-0" viewBox="0 0 16 16">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
</svg>
<div>
<strong>Mode configuration actif — le capteur n'envoie aucune donnée !</strong><br>
<small>Le script de transmission (SARA) est désactivé tant que ce mode est actif. Pensez à le désactiver une fois la configuration terminée. Ce mode sera automatiquement désactivé au prochain redémarrage du capteur.</small>
</div>
</div>
<button class="btn btn-success mb-3 btn_selfTest" onclick="runSelfTest()">
<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>
Status
<span id="modem-status" class="badge">Loading...</span>
</h3>
-->
<div class="row mb-3">
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<p class="card-text">General information. </p>
<button class="btn btn-primary" onclick="getModemInfo('ttyAMA2', 1)">Get Data</button>
<div id="loading_ttyAMA2_ATI" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="modem_info_alert"></div>
<div class="collapse mt-2" id="modem_info_logs">
<div class="card card-body bg-light">
<small><code id="response_ttyAMA2_ATI"></code></small>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<p class="card-text">SIM card information.</p>
<button class="btn btn-primary me-1" onclick="getSimInfo('ttyAMA2', 1)">Get Data (ICCID)</button>
<div id="loading_ttyAMA2_AT_CCID_" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="sim_info_alert"></div>
<div class="collapse mt-2" id="sim_info_logs">
<div class="card card-body bg-light">
<small><code id="response_ttyAMA2_AT_CCID_"></code></small>
</div>
</div>
<hr>
<button class="btn btn-primary me-1" onclick="getImsiInfo('ttyAMA2', 1)">Get Data (IMSI)</button>
<div id="loading_ttyAMA2_AT_CIMI" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="imsi_info_alert"></div>
<div class="collapse mt-2" id="imsi_info_logs">
<div class="card card-body bg-light">
<small><code id="response_ttyAMA2_AT_CIMI"></code></small>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<p class="card-text">Actual Network connection</p>
<button class="btn btn-primary" onclick="getNetworkInfo('ttyAMA2', 2)">Get Data</button>
<div id="loading_ttyAMA2_AT_COPS_" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="network_info_alert"></div>
<div class="collapse mt-2" id="network_info_logs">
<div class="card card-body bg-light">
<small><code id="response_ttyAMA2_AT_COPS_"></code></small>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<p class="card-text">Signal strength </p>
<button class="btn btn-primary" onclick="getSignalInfo('ttyAMA2', 1)">Get Data</button>
<div id="loading_ttyAMA2_AT_CSQ" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="signal_info_alert"></div>
<div class="collapse mt-2" id="signal_info_logs">
<div class="card card-body bg-light">
<small><code id="response_ttyAMA2_AT_CSQ"></code></small>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<p class="card-text"><strong>Modem Reset</strong></p>
<p class="text-muted small mb-2">Reset software (AT+CFUN=15) : redémarre le firmware du modem.</p>
<button class="btn btn-warning mb-2" onclick="getData_saraR4('ttyAMA2', 'AT+CFUN=15', 1)">Reset Software</button>
<div id="loading_ttyAMA2_AT_CFUN_15" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_AT_CFUN_15"></div>
<hr>
<p class="text-muted small mb-2">Reset hardware (GPIO 16) : coupe et rétablit l'alimentation du modem, puis teste la connexion avec le modem (~20 secondes).</p>
<button class="btn btn-danger mb-2" id="btn_hw_reboot" onclick="hardwareRebootSara()">Reset Hardware</button>
<div id="loading_hw_reboot" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_hw_reboot"></div>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<p class="card-text"><strong>LED status connexion (PCB)</strong></p>
<p class="text-muted small mb-2">Active la LED bleue du PCB qui indique l'état de la connexion réseau (GPIO 16 du modem).</p>
<button class="btn btn-primary mb-2" onclick="getData_saraR4('ttyAMA2', 'AT+UGPIOC=16,2', 5)">Activer LED</button>
<div id="loading_ttyAMA2_AT_UGPIOC_16_2" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_AT_UGPIOC_16_2"></div>
<hr>
<button class="btn btn-secondary mb-2" onclick="getData_saraR4('ttyAMA2', 'AT+UGPIOC=16,255', 5)">Désactiver LED</button>
<div id="loading_ttyAMA2_AT_UGPIOC_16_255" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_AT_UGPIOC_16_255"></div>
</div>
</div>
</div>
</div>
<h3>Connexion 4G Network</h3>
<div class="row mb-3">
<div class="col-sm-6">
<div class="card text-dark bg-light">
<div class="card-body">
<p class="card-text">Network scan. Attention: 2 min scan.</p>
<p class="card-text">Orange FR (20801), SFR (20810), Bouygues (20820)</p>
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+COPS=?', 120)">Scan</button>
<div id="loading_ttyAMA2_AT_COPS__" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_AT_COPS__"></div>
<div id="table-network"></div>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card text-dark bg-light">
<div class="card-body">
<p class="card-text">Network connexion.</p>
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Numeric Operator</span>
<input type="text" id="messageInput_network" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm">
</div>
<button class="btn btn-primary" onclick="connectNetwork_saraR4('ttyAMA2', document.getElementById('messageInput_network').value, 60)">Connect</button>
<div id="loading_ttyAMA2_AT_COPS_Connect" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_AT_COPS_Connect"></div>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card text-dark bg-light">
<div class="card-body">
<p class="card-text">APN</p>
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Address</span>
<input type="text" id="messageInput_APN" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm">
</div>
<button class="btn btn-primary" onclick="connectAPN_saraR4('ttyAMA2', document.getElementById('messageInput_APN').value, 5)">Set APN</button>
<button class="btn btn-secondary" onclick="getData_saraR4('ttyAMA2','AT+CGDCONT?', 5)">Get APN</button>
<div id="loading_ttyAMA2_APN" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_APN"></div>
<div id="loading_ttyAMA2_AT_CGDCONT_" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_AT_CGDCONT_"></div>
</div>
</div>
</div>
</div>
<!--
<h3>MQTT</h3>
<div class="row mb-3">
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<p class="card-text">Get config.</p>
<button class="btn btn-primary" onclick="mqtt_getConfig_saraR4('ttyAMA2', 2)">Get Data</button>
<div id="loading_mqtt_getConfig" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_mqtt_getConfig"></div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<p class="card-text">Login / logout</p>
<button class="btn btn-success" onclick="mqtt_login_logout('ttyAMA2', 1, 6)">Login </button>
<button class="btn btn-danger" onclick="mqtt_login_logout('ttyAMA2', 0, 6)">Logout </button>
<div id="loading_mqtt_login_logout" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_mqtt_login_logout"></div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<p class="card-text">Send message (MQTT publish) .</p>
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Text</span>
<input type="text" id="MQTTmessageInput" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm">
</div>
<button class="btn btn-primary" onclick="mqtt_publish('ttyAMA2', document.getElementById('MQTTmessageInput').value, 2)">Send Message</button>
<div id="loading_mqtt_publish" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_mqtt_publish"></div>
</div>
</div>
</div>
</div>
-->
<h3>Test HTTP server comm.</h3>
<div class="row mb-3">
<!-- SET URL -->
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<p class="card-text">Test communication with the server.</p>
<button class="btn btn-primary" onclick="ping_test()">Test</button>
<div id="loading_ping" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ping"></div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<p class="card-text">Setup PSD connection.</p>
<button class="btn btn-primary" onclick="PSD_setup()">Start</button>
<div id="loading_PSD" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_psd_setup"></div>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<p class="card-text">Setup Server Hostname.</p>
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Server name</span>
<input type="text" id="messageInput_server" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm">
</div>
<button class="btn btn-primary" onclick="setupServerHostname('ttyAMA2', document.getElementById('messageInput_server').value, 0)">Set</button>
<div id="loading_serverHostname" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_serverHostname"></div>
</div>
</div>
</div>
</div>
<h3>Send message (test)</h3>
<div class="row mb-3">
<!-- SET URL -->
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<p class="card-text">Set url (HTTP).</p>
<button class="btn btn-primary" onclick="setURL_saraR4('ttyAMA2', 'data.nebuleair.fr')">Set URL</button>
<div id="loading_ttyAMA2_setURL" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_setURL"></div>
</div>
</div>
</div>
<!-- WRITE MESSAGE to memory -->
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<p class="card-text">Write message (local storage).</p>
<div class="input-group input-group-sm mb-3">
<span class="input-group-text" id="inputGroup-sizing-sm">Text</span>
<input type="text" id="messageInput" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm">
</div>
<button class="btn btn-primary" onclick="writeMessage_saraR4('ttyAMA2', document.getElementById('messageInput').value , 'write')">Write </button>
<button class="btn btn-warning" onclick="writeMessage_saraR4('ttyAMA2', 'Hello', 'read')">Read </button>
<button class="btn btn-danger" onclick="writeMessage_saraR4('ttyAMA2', 'Hello', 'erase')">Empty </button>
<div id="loading_ttyAMA2_message_write" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_message_write"></div>
</div>
</div>
</div>
<!-- Send MESSAGE -->
<div class="col-sm-4">
<div class="card">
<div class="card-body">
<p class="card-text">Send message .</p>
<button class="btn btn-primary" onclick="sendMessage_saraR4('ttyAMA2', '/pro_4G/notif_message.php')">Send Message</button>
<div id="loading_ttyAMA2_message_send" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_message_send"></div>
</div>
</div>
</div>
</div>
<!-- toast -->
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="liveToast" class="toast align-items-center text-bg-primary border-1" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
Hello, world! This is a toast message.
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- JAVASCRIPT -->
<!-- Link Ajax locally -->
<script src="assets/jquery/jquery-3.7.1.min.js"></script>
<!-- Link Bootstrap JS and Popper.js locally -->
<script src="assets/js/bootstrap.bundle.js"></script>
<!-- i18n translation system -->
<script src="assets/js/i18n.js"></script>
<script src="assets/js/topbar-logo.js"></script>
<script src="assets/js/selftest.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const elementsToLoad = [
{ id: 'topbar', file: 'topbar.html' },
{ id: 'sidebar', file: 'sidebar.html' },
{ id: 'sidebar_mobile', file: 'sidebar.html' }
];
elementsToLoad.forEach(({ id, file }) => {
fetch(file)
.then(response => response.text())
.then(data => {
const element = document.getElementById(id);
if (element) {
element.innerHTML = data;
}
})
.catch(error => console.error(`Error loading ${file}:`, error));
});
//OLD way to retreive data from JSON
/*
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
.then(response => response.json()) // Parse response as JSON
.then(data => {
console.log("Getting config file (onload)");
//modem config mode
const check_modem_configMode = document.getElementById("check_modem_configMode");
check_modem_configMode.checked = data.modem_config_mode;
console.log("Modem configuration: " + data.modem_config_mode);
})
*/
//NEW way to get data from SQLITE
$.ajax({
url: 'launcher.php?type=get_config_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config table:");
console.log(response);
//modem_version
const modem_version_html = document.getElementById("modem_version");
modem_version_html.innerText = response.modem_version;
// Set checkbox state based on the response data
const check_modem_configMode = document.getElementById("check_modem_configMode");
if (check_modem_configMode) {
check_modem_configMode.checked = response.modem_config_mode;
console.log("Modem configuration: " + response.modem_config_mode);
// Show/hide config mode alert banner
const alertEl = document.getElementById("configmode_alert");
if (alertEl) alertEl.style.display = response.modem_config_mode ? "flex" : "none";
} else {
console.error("Checkbox element not found");
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
});
window.onload = function() {
getModem_busy_status();
setInterval(getModem_busy_status, 1000);
//NEW way to get config (SQLite)
$.ajax({
url: 'launcher.php?type=get_config_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config table:");
console.log(response);
//device name_side bar
const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => {
element.innerText = response.deviceName;
});
//device name html page title
if (response.deviceName) {
document.title = response.deviceName;
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX
//get local RTC
$.ajax({
url: 'launcher.php?type=RTC_time',
dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Local RTC: " + response);
const RTC_Element = document.getElementById("RTC_time");
RTC_Element.textContent = response;
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function getModemInfo(port, timeout) {
console.log("Getting modem info from port " + port);
$("#loading_ttyAMA2_ATI").show();
$("#modem_info_alert").empty();
$("#response_ttyAMA2_ATI").empty();
$.ajax({
url: 'launcher.php?type=sara&port=' + port + '&command=' + encodeURIComponent('ATI') + '&timeout=' + timeout,
dataType: 'text',
method: 'GET',
success: function(response) {
console.log("ATI response:", response);
$("#loading_ttyAMA2_ATI").hide();
// Store raw logs
const formattedLogs = response.replace(/\n/g, "<br>");
$("#response_ttyAMA2_ATI").html(formattedLogs);
// Parse response to detect modem model
let alertHtml = '';
const responseUpper = response.toUpperCase();
if (response.includes('OK') && (responseUpper.includes('SARA-R5') || responseUpper.includes('SARA-R4'))) {
// Extract model name
let modelName = 'SARA';
const modelMatch = response.match(/SARA-R[45]\d*[A-Z]*-\d+[A-Z]*-\d+/i);
if (modelMatch) {
modelName = modelMatch[0];
} else if (responseUpper.includes('SARA-R5')) {
modelName = 'SARA-R5';
} else if (responseUpper.includes('SARA-R4')) {
modelName = 'SARA-R4';
}
alertHtml = `
<div class="alert alert-success py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>Modem connected</strong><br>
<small>Model: ${modelName}</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#modem_info_logs" aria-expanded="false" aria-controls="modem_info_logs">
<small>+</small>
</button>
</div>`;
} else if (response.includes('ERROR') || response.trim() === '' || !response.includes('OK')) {
alertHtml = `
<div class="alert alert-danger py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>Modem not connected</strong><br>
<small>No response from modem</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#modem_info_logs" aria-expanded="false" aria-controls="modem_info_logs">
<small>+</small>
</button>
</div>`;
} else {
// Unknown response but got something
alertHtml = `
<div class="alert alert-warning py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>Modem detected</strong><br>
<small>Unexpected response</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#modem_info_logs" aria-expanded="false" aria-controls="modem_info_logs">
<small>+</small>
</button>
</div>`;
}
$("#modem_info_alert").html(alertHtml);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
$("#loading_ttyAMA2_ATI").hide();
$("#modem_info_alert").html(`
<div class="alert alert-danger py-2 mb-0 mt-2" role="alert">
<strong>Communication error</strong><br>
<small>${error}</small>
</div>`);
}
});
}
function getSimInfo(port, timeout) {
console.log("Getting SIM info from port " + port);
$("#loading_ttyAMA2_AT_CCID_").show();
$("#sim_info_alert").empty();
$("#response_ttyAMA2_AT_CCID_").empty();
$.ajax({
url: 'launcher.php?type=sara&port=' + port + '&command=' + encodeURIComponent('AT+CCID?') + '&timeout=' + timeout,
dataType: 'text',
method: 'GET',
success: function(response) {
console.log("CCID response:", response);
$("#loading_ttyAMA2_AT_CCID_").hide();
// Store raw logs
const formattedLogs = response.replace(/\n/g, "<br>");
$("#response_ttyAMA2_AT_CCID_").html(formattedLogs);
// Parse response to extract SIM card number
let alertHtml = '';
// Match CCID number (typically 19-20 digits)
const ccidMatch = response.match(/\+CCID:\s*(\d{18,22})/);
if (response.includes('OK') && ccidMatch) {
const simNumber = ccidMatch[1];
alertHtml = `
<div class="alert alert-success py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>SIM card connected</strong><br>
<small>ICCID: ${simNumber}</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#sim_info_logs" aria-expanded="false" aria-controls="sim_info_logs">
<small>+</small>
</button>
</div>`;
} else if (response.includes('ERROR') || response.trim() === '' || !response.includes('OK')) {
alertHtml = `
<div class="alert alert-danger py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>SIM card not detected</strong><br>
<small>No SIM card or read error</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#sim_info_logs" aria-expanded="false" aria-controls="sim_info_logs">
<small>+</small>
</button>
</div>`;
} else {
alertHtml = `
<div class="alert alert-warning py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>SIM card detected</strong><br>
<small>Unable to read ICCID</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#sim_info_logs" aria-expanded="false" aria-controls="sim_info_logs">
<small>+</small>
</button>
</div>`;
}
$("#sim_info_alert").html(alertHtml);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
$("#loading_ttyAMA2_AT_CCID_").hide();
$("#sim_info_alert").html(`
<div class="alert alert-danger py-2 mb-0 mt-2" role="alert">
<strong>Communication error</strong><br>
<small>${error}</small>
</div>`);
}
});
}
function getImsiInfo(port, timeout) {
console.log("Getting IMSI from port " + port);
$("#loading_ttyAMA2_AT_CIMI").show();
$("#imsi_info_alert").empty();
$("#response_ttyAMA2_AT_CIMI").empty();
$.ajax({
url: 'launcher.php?type=sara&port=' + port + '&command=' + encodeURIComponent('AT+CIMI') + '&timeout=' + timeout,
dataType: 'text',
method: 'GET',
success: function(response) {
console.log("IMSI response:", response);
$("#loading_ttyAMA2_AT_CIMI").hide();
// Store raw logs
const formattedLogs = response.replace(/\n/g, "<br>");
$("#response_ttyAMA2_AT_CIMI").html(formattedLogs);
// Parse response to extract IMSI (15-digit number)
let alertHtml = '';
const imsiMatch = response.match(/(\d{15})/);
if (response.includes('OK') && imsiMatch) {
const imsiNumber = imsiMatch[1];
alertHtml = `
<div class="alert alert-success py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>IMSI read successfully</strong><br>
<small>IMSI: ${imsiNumber}</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#imsi_info_logs" aria-expanded="false" aria-controls="imsi_info_logs">
<small>+</small>
</button>
</div>`;
} else if (response.includes('ERROR') || response.trim() === '' || !response.includes('OK')) {
alertHtml = `
<div class="alert alert-danger py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>IMSI not available</strong><br>
<small>No SIM card or read error</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#imsi_info_logs" aria-expanded="false" aria-controls="imsi_info_logs">
<small>+</small>
</button>
</div>`;
} else {
alertHtml = `
<div class="alert alert-warning py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>SIM card detected</strong><br>
<small>Unable to read IMSI</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#imsi_info_logs" aria-expanded="false" aria-controls="imsi_info_logs">
<small>+</small>
</button>
</div>`;
}
$("#imsi_info_alert").html(alertHtml);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
$("#loading_ttyAMA2_AT_CIMI").hide();
$("#imsi_info_alert").html(`
<div class="alert alert-danger py-2 mb-0 mt-2" role="alert">
<strong>Communication error</strong><br>
<small>${error}</small>
</div>`);
}
});
}
// Cache for operators data
let operatorsData = null;
function loadOperatorsData() {
return new Promise((resolve, reject) => {
if (operatorsData) {
resolve(operatorsData);
return;
}
$.ajax({
url: 'assets/data/operators.json',
dataType: 'json',
method: 'GET',
success: function(data) {
operatorsData = data;
resolve(data);
},
error: function(xhr, status, error) {
console.error('Failed to load operators data:', error);
reject(error);
}
});
});
}
function getNetworkInfo(port, timeout) {
console.log("Getting network info from port " + port);
$("#loading_ttyAMA2_AT_COPS_").show();
$("#network_info_alert").empty();
$("#response_ttyAMA2_AT_COPS_").empty();
// Load operators data first, then query modem
loadOperatorsData().then(function(opData) {
$.ajax({
url: 'launcher.php?type=sara&port=' + port + '&command=' + encodeURIComponent('AT+COPS?') + '&timeout=' + timeout,
dataType: 'text',
method: 'GET',
success: function(response) {
console.log("COPS response:", response);
$("#loading_ttyAMA2_AT_COPS_").hide();
// Store raw logs
const formattedLogs = response.replace(/\n/g, "<br>");
$("#response_ttyAMA2_AT_COPS_").html(formattedLogs);
// Parse response: +COPS: <mode>[,<format>,<oper>[,<AcT>]]
let alertHtml = '';
const copsMatch = response.match(/\+COPS:\s*(\d+)(?:,(\d+),"?([^",]+)"?,(\d+))?/);
if (response.includes('OK') && copsMatch) {
const mode = copsMatch[1];
const format = copsMatch[2];
const oper = copsMatch[3];
const act = copsMatch[4];
// Get mode description
const modeDesc = opData.modes[mode] || 'Unknown';
// Get operator name
let operatorName = oper || 'Not registered';
let operatorCountry = '';
if (oper && opData.operators[oper]) {
operatorName = opData.operators[oper].name;
operatorCountry = opData.operators[oper].country;
}
// Get access technology
const actDesc = act ? (opData.accessTechnology[act] || 'Unknown') : 'N/A';
if (oper) {
alertHtml = `
<div class="alert alert-success py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>Connected to network</strong><br>
<small>
Operator: ${operatorName}${operatorCountry ? ' (' + operatorCountry + ')' : ''}<br>
Technology: ${actDesc}<br>
Mode: ${modeDesc}
</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#network_info_logs" aria-expanded="false" aria-controls="network_info_logs">
<small>+</small>
</button>
</div>`;
} else {
alertHtml = `
<div class="alert alert-warning py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>Not registered</strong><br>
<small>Mode: ${modeDesc}</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#network_info_logs" aria-expanded="false" aria-controls="network_info_logs">
<small>+</small>
</button>
</div>`;
}
} else if (response.includes('ERROR') || response.trim() === '' || !response.includes('OK')) {
alertHtml = `
<div class="alert alert-danger py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>Network error</strong><br>
<small>Unable to get network info</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#network_info_logs" aria-expanded="false" aria-controls="network_info_logs">
<small>+</small>
</button>
</div>`;
} else {
alertHtml = `
<div class="alert alert-warning py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>Unknown response</strong><br>
<small>Check logs for details</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#network_info_logs" aria-expanded="false" aria-controls="network_info_logs">
<small>+</small>
</button>
</div>`;
}
$("#network_info_alert").html(alertHtml);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
$("#loading_ttyAMA2_AT_COPS_").hide();
$("#network_info_alert").html(`
<div class="alert alert-danger py-2 mb-0 mt-2" role="alert">
<strong>Communication error</strong><br>
<small>${error}</small>
</div>`);
}
});
}).catch(function(error) {
$("#loading_ttyAMA2_AT_COPS_").hide();
$("#network_info_alert").html(`
<div class="alert alert-danger py-2 mb-0 mt-2" role="alert">
<strong>Configuration error</strong><br>
<small>Failed to load operators data</small>
</div>`);
});
}
function getSignalInfo(port, timeout) {
console.log("Getting signal info from port " + port);
$("#loading_ttyAMA2_AT_CSQ").show();
$("#signal_info_alert").empty();
$("#response_ttyAMA2_AT_CSQ").empty();
$.ajax({
url: 'launcher.php?type=sara&port=' + port + '&command=' + encodeURIComponent('AT+CSQ') + '&timeout=' + timeout,
dataType: 'text',
method: 'GET',
success: function(response) {
console.log("CSQ response:", response);
$("#loading_ttyAMA2_AT_CSQ").hide();
// Store raw logs
const formattedLogs = response.replace(/\n/g, "<br>");
$("#response_ttyAMA2_AT_CSQ").html(formattedLogs);
// Parse response: +CSQ: <signal_power>,<qual>
let alertHtml = '';
const csqMatch = response.match(/\+CSQ:\s*(\d+),(\d+)/);
if (response.includes('OK') && csqMatch) {
const signalPower = parseInt(csqMatch[1]);
const qual = parseInt(csqMatch[2]);
// Determine signal quality and color (matching Python thresholds)
let signalDesc, signalColor, signalIcon, alertClass;
if (signalPower === 99) {
signalDesc = 'No signal';
signalColor = '#333333';
signalIcon = '⚫';
alertClass = 'alert-dark';
} else if (signalPower === 0) {
signalDesc = 'Very poor';
signalColor = '#dc3545';
signalIcon = '🔴';
alertClass = 'alert-danger';
} else if (signalPower <= 24) {
signalDesc = 'Poor';
signalColor = '#fd7e14';
signalIcon = '🟠';
alertClass = 'alert-warning';
} else if (signalPower <= 26) {
signalDesc = 'Good';
signalColor = '#ffc107';
signalIcon = '🟡';
alertClass = 'alert-warning';
} else if (signalPower <= 28) {
signalDesc = 'Very good';
signalColor = '#198754';
signalIcon = '🟢';
alertClass = 'alert-success';
} else if (signalPower <= 30) {
signalDesc = 'Excellent';
signalColor = '#0d6efd';
signalIcon = '🔵';
alertClass = 'alert-primary';
} else {
signalDesc = 'Maximum';
signalColor = '#6f42c1';
signalIcon = '🟣';
alertClass = 'alert-primary';
}
// Calculate approximate dBm (for RSSI: -113 + 2*signalPower)
let rssiDbm = signalPower !== 99 ? (-113 + 2 * signalPower) + ' dBm' : 'N/A';
// Signal bars visualization (1-5 bars based on signal power, matching thresholds)
let bars = 0;
if (signalPower !== 99) {
if (signalPower >= 29) bars = 5; // Excellent / Very Strong
else if (signalPower >= 27) bars = 4; // Very good
else if (signalPower >= 25) bars = 3; // Good
else if (signalPower >= 10) bars = 2; // Poor (mid)
else if (signalPower >= 1) bars = 1; // Poor (low)
}
const barsHtml = `
<span style="font-size: 1.2em; letter-spacing: 2px;">
${[1,2,3,4,5].map(i =>
`<span style="color: ${i <= bars ? signalColor : '#dee2e6'};">▮</span>`
).join('')}
</span>`;
alertHtml = `
<div class="alert ${alertClass} py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<div class="d-flex align-items-center mb-1">
${barsHtml}
<span class="ms-2"><strong>${signalDesc}</strong></span>
</div>
<small>
Signal: ${signalPower}/31 (${rssiDbm})<br>
Quality: ${qual}/7
</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#signal_info_logs" aria-expanded="false" aria-controls="signal_info_logs">
<small>+</small>
</button>
</div>`;
} else if (response.includes('ERROR') || response.trim() === '' || !response.includes('OK')) {
alertHtml = `
<div class="alert alert-danger py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>Signal error</strong><br>
<small>Unable to get signal info</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#signal_info_logs" aria-expanded="false" aria-controls="signal_info_logs">
<small>+</small>
</button>
</div>`;
} else {
alertHtml = `
<div class="alert alert-warning py-2 mb-0 mt-2 d-flex justify-content-between align-items-center" role="alert">
<div>
<strong>Unknown response</strong><br>
<small>Check logs for details</small>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#signal_info_logs" aria-expanded="false" aria-controls="signal_info_logs">
<small>+</small>
</button>
</div>`;
}
$("#signal_info_alert").html(alertHtml);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
$("#loading_ttyAMA2_AT_CSQ").hide();
$("#signal_info_alert").html(`
<div class="alert alert-danger py-2 mb-0 mt-2" role="alert">
<strong>Communication error</strong><br>
<small>${error}</small>
</div>`);
}
});
}
function hardwareRebootSara() {
if (!confirm("Couper l'alimentation du modem SARA via GPIO 16 ?\nLe modem sera éteint pendant ~3 secondes puis redémarré.\nLa connexion avec le modem sera ensuite testée (~20 secondes au total).")) return;
console.log("Hardware reboot SARA via GPIO 16");
$("#btn_hw_reboot").prop("disabled", true);
$("#loading_hw_reboot").show();
$("#response_hw_reboot").html(`
<div class="alert alert-info py-2 mt-2" id="hw_reboot_progress">
<small><strong>Étape 1/3</strong> — Coupure alimentation du modem...</small>
</div>`);
// Progress messages
var step2Timeout = setTimeout(function() {
$("#hw_reboot_progress small").html('<strong>Étape 2/3</strong> — Attente du redémarrage du modem...');
}, 4000);
var step3Timeout = setTimeout(function() {
$("#hw_reboot_progress small").html('<strong>Étape 3/3</strong> — Test de la connexion avec le modem (ATI)...');
}, 9000);
$.ajax({
url: 'launcher.php?type=sara_hardware_reboot',
dataType: 'json',
method: 'GET',
timeout: 45000,
success: function(response) {
console.log(response);
clearTimeout(step2Timeout);
clearTimeout(step3Timeout);
$("#loading_hw_reboot").hide();
$("#btn_hw_reboot").prop("disabled", false);
if (response.reboot) {
$("#response_hw_reboot").html(`
<div class="alert alert-success py-2 mt-2">
<strong>Modem redémarré et connexion vérifiée</strong><br>
<small><code>${response.modem_response}</code></small>
</div>`);
} else {
$("#response_hw_reboot").html(`
<div class="alert alert-danger py-2 mt-2">
<strong>Echec</strong><br>
<small>${response.error || 'Modem ne répond pas après le redémarrage'}</small>
</div>`);
}
},
error: function(xhr, status, error) {
console.error('Hardware reboot failed:', status, error);
clearTimeout(step2Timeout);
clearTimeout(step3Timeout);
$("#loading_hw_reboot").hide();
$("#btn_hw_reboot").prop("disabled", false);
$("#response_hw_reboot").html(`
<div class="alert alert-danger py-2 mt-2">
<strong>Erreur de communication</strong><br>
<small>${error}</small>
</div>`);
}
});
}
function getData_saraR4(port, command, timeout){
console.log("Data from SaraR4");
console.log("Port: " + port );
console.log("Command: " + command );
console.log("Timeout: " + timeout );
const safeCommand = command.replace(/[?+=]/g, "_");
console.log(safeCommand);
$("#loading_"+port+"_"+safeCommand).show();
$("#response_"+port+"_"+safeCommand).empty();
$.ajax({
url: 'launcher.php?type=sara&port='+port+'&command='+encodeURIComponent(command)+'&timeout='+timeout,
dataType:'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
//log response
console.log(response);
//hide spinning wheel
$("#loading_"+port+"_"+safeCommand).hide();
// si on fait le scan de network on veut une liste des réseaux
if (command == "AT+COPS=?") {
// Extract data within parentheses
const matches = response.match(/\(.*?\)/g); // Matches all `(...)` sections
const container = document.getElementById('table-network'); // Bootstrap container
// Check if matches exist
if (matches) {
const table = document.createElement('table');
table.className = 'table table-striped'; // Add Bootstrap table styling
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
// Table header (you can customize this based on your data)
const headerRow = document.createElement('tr');
const header1 = document.createElement('th');
header1.textContent = 'Status';
const header2 = document.createElement('th');
header2.textContent = 'Long oper';
const header3 = document.createElement('th');
header3.textContent = 'Short opeer';
const header4 = document.createElement('th');
header4.textContent = 'Numeric oper';
const header5 = document.createElement('th');
header5.textContent = 'AcT';
headerRow.appendChild(header1);
headerRow.appendChild(header2);
headerRow.appendChild(header3);
headerRow.appendChild(header4);
headerRow.appendChild(header5);
thead.appendChild(headerRow);
table.appendChild(thead);
// Loop through each match and create a row in the table
matches.forEach((item) => {
// Skip empty sections
if (item === "()") return;
const row = document.createElement('tr');
const values = item.slice(1, -1).split(','); // Remove parentheses and split by commas
// Add table cells (td) for each value
values.forEach((value) => {
const cell = document.createElement('td');
cell.textContent = value.trim(); // Remove extra spaces
row.appendChild(cell);
});
tbody.appendChild(row);
});
// Add tbody to table and append the table to the container
table.appendChild(tbody);
container.appendChild(table);
} else {
console.error('No valid data found in response.');
}
} else{
// si c'est une commande AT normale
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>")
.replace(/\b(OK)\b/g, '<span style="color: green; font-weight: bold;">$1</span>');;
$("#response_"+port+"_"+safeCommand).html(formattedResponse);
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function connectNetwork_saraR4(port, networkID, timeout){
console.log(" Connect to network (port "+port+" and network id "+networkID+"):");
$("#loading_"+port+"_AT_COPS_Connect").show();
$.ajax({
url: 'launcher.php?type=sara_connectNetwork&port='+port+'&networkID='+encodeURIComponent(networkID)+'&timeout='+timeout,
dataType:'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_"+port+"_AT_COPS_Connect").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_"+port+"_AT_COPS_Connect").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function setupServerHostname(port, serverName, timeout){
console.log(" Setupt server hostname "+serverName+"):");
$("#loading_serverHostname").show();
$.ajax({
url: 'launcher.php?type=sara_setupHostname&port='+port+'&networkID='+encodeURIComponent(serverName)+'&profileID=0',
dataType:'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_serverHostname").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_serverHostname").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function mqtt_getConfig_saraR4(port, timeout){
console.log("GET MQTT config (port "+port+"):");
$("#loading_mqtt_getConfig").show();
$.ajax({
url: 'launcher.php?type=sara_getMQTT_config&port='+port+'&timeout='+timeout,
dataType:'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_mqtt_getConfig").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_mqtt_getConfig").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function mqtt_login_logout(port, login_logout, timeout){
console.log("GET MQTT login / logout (port "+port+"):");
$("#loading_mqtt_login_logout").show();
$.ajax({
url: 'launcher.php?type=sara_getMQTT_login_logout&port='+port+'&login_logout='+login_logout+'&timeout='+timeout,
dataType:'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_mqtt_login_logout").hide();
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_mqtt_login_logout").html(formattedResponse);
const regex = /^\+UMQTTC:\s*(\d+),(\d+)/m; // Match "+UMQTTC:", followed by two numbers separated by a comma
const match = response.match(regex);
if (match) {
const firstNumber = match[1]; // The first number after ":"
const secondNumber = match[2]; // The second number after the ","
if (firstNumber == 0) {
console.log("MQTT LOGOUT:");
$("#response_mqtt_login_logout").append("<p>logout</p>");
}
if (firstNumber == 1) {
console.log("MQTT LOGIN:");
$("#response_mqtt_login_logout").append("<p>login</p>");
}
if (secondNumber == 0) {
console.log("ERROR");
$("#response_mqtt_login_logout").append("<p>error</p>");
}
if (secondNumber == 1) {
console.log("SUCCESS");
$("#response_mqtt_login_logout").append("<p>success</p>");
}
} else {
console.log("No matching line found");
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function mqtt_publish(port, message, timeout){
console.log(" MQTT publish (port "+port+"):");
$("#loading_mqtt_publish").show();
$.ajax({
url: 'launcher.php?type=sara_MQTT_publish&port='+port+'&timeout='+timeout+'&message='+message,
dataType: 'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_mqtt_publish").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_mqtt_publish").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function setURL_saraR4(port, url){
console.log("Set URL for HTTP (port "+port+" and URL "+url+"):");
$("#loading_"+port+"_setURL").show();
$.ajax({
url: 'launcher.php?type=sara_setURL&port='+port+'&url='+encodeURIComponent(url),
dataType: 'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_"+port+"_setURL").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_"+port+"_setURL").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function ping_test(port, url){
console.log("Test ping to data.nebuleair.fr:");
$("#response_ping").empty();
$("#loading_ping").show();
$.ajax({
url: 'launcher.php?type=sara_ping',
dataType: 'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_ping").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_ping").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function PSD_setup(port, url){
console.log("Setup PSD connection:");
$("#loading_PSD").show();
$.ajax({
url: 'launcher.php?type=sara_psd_setup',
dataType: 'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_PSD").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_psd_setup").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function writeMessage_saraR4(port, message, type){
console.log(type +" message to SARA R4 memory (port "+port+" and message "+message+"):");
$("#loading_"+port+"_message_write").show();
$.ajax({
url: 'launcher.php?type=sara_writeMessage&port='+port+'&message='+encodeURIComponent(message)+'&type2='+type,
dataType: 'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_"+port+"_message_write").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_"+port+"_message_write").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function sendMessage_saraR4(port, endpoint){
console.log("Send message from SaraR4 (port "+port+" and endpoint "+endpoint+"):");
$("#loading_"+port+"_message_send").show();
$.ajax({
url: 'launcher.php?type=sara_sendMessage&port='+port+'&endpoint='+encodeURIComponent(endpoint),
dataType: 'text',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_"+port+"_message_send").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_"+port+"_message_send").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function connectAPN_saraR4(port, APN_address, timeout){
console.log(" Set APN (port "+port+" and adress "+APN_address+"):");
$("#loading_"+port+"_APN").show();
$.ajax({
url: 'launcher.php?type=sara_APN&port='+port+'&APN_address='+encodeURIComponent(APN_address)+'&timeout='+timeout,
//dataType: 'json', // Specify that you expect a JSON response
dataType: 'text',
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
$("#loading_"+port+"_APN").hide();
// Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>");
$("#response_"+port+"_APN").html(formattedResponse);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function getModem_busy_status() {
//console.log("Getting modem busy status");
const SARA_busy_message = document.getElementById("modem_status_message");
$.ajax({
url: 'launcher.php?type=getModem_busy',
dataType: 'json', // Expecting JSON response
method: 'GET',
success: function(response) {
//console.log(response);
if (response.running) {
// Script is running → Red button, "Modem is busy"
SARA_busy_message.innerHTML= ` <div class="alert alert-warning" role="alert">
Le modem 4G est en cours d'utilisation! L'utilisation des boutons ci-dessous peut entrainer des erreurs. Veuillez mettre le modem en mode configuration.
</div>`
} else {
// Script is NOT running → Green button, "Modem is available"
SARA_busy_message.innerHTML= ` <div class="alert alert-primary" role="alert">
Veuillez vous assurer de mettre le modem en mode configuration avant de cliquer sur les boutons ci-dessous. <br>
Une fois terminé veillez à bien désactiver le mode configuration.
</div>`
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
SARA_busy_status.textContent = "Error checking status";
SARA_busy_status.className = "btn text-bg-warning"; // Yellow button for errors
}
});
}
function update_modem_configMode(param, checked){
//change ('modem_config_mode', '0', 'bool') inside SQLITE db
// response type: {"success":true,"message":"Configuration updated successfully","param":"modem_config_mode","value":"0","type":"bool"}
const toastLiveExample = document.getElementById('liveToast')
const toastBody = toastLiveExample.querySelector('.toast-body');
console.log("updating modem config mode to :" + checked);
// Toggle alert banner immediately
const alertEl = document.getElementById("configmode_alert");
if (alertEl) alertEl.style.display = checked ? "flex" : "none";
$.ajax({
url: 'launcher.php?type=update_config_sqlite&param='+param+'&value='+checked,
dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
cache: false, // Prevent AJAX from caching
success: function(response) {
console.log("AJAX success:");
console.log(response);
// Format the response nicely
let formattedMessage = '';
if (response.success) {
// Success message
toastLiveExample.classList.remove('text-bg-danger');
toastLiveExample.classList.add('text-bg-success');
formattedMessage = `
<strong>Success!</strong><br>
Parameter: ${response.param || param}<br>
Value: ${response.value || checked}<br>
${response.message || ''}
`;
} else {
// Error message
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
formattedMessage = `
<strong>Error!</strong><br>
${response.error || 'Unknown error'}<br>
Parameter: ${response.param || param}
`;
}
// Update the toast body with formatted content
toastBody.innerHTML = formattedMessage;
// Show the toast
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
toastBootstrap.show()
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
// Update toast with error message
toastBody.textContent = 'Error: ' + error;
// Set toast to danger color
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
// Show the toast for errors too
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
}
});
}
// Self test functions are now in assets/js/selftest.js
</script>
</body>
</html>