feat(ui): improve network connection display with operator lookup
Add operators.json with MCC/MNC codes for common operators. Parse AT+COPS? response to show operator name, country, technology, and connection mode in a user-friendly format. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
66
html/assets/data/operators.json
Normal file
66
html/assets/data/operators.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"operators": {
|
||||||
|
"20801": { "name": "Orange", "country": "France" },
|
||||||
|
"20802": { "name": "Orange", "country": "France" },
|
||||||
|
"20810": { "name": "SFR", "country": "France" },
|
||||||
|
"20811": { "name": "SFR", "country": "France" },
|
||||||
|
"20813": { "name": "SFR", "country": "France" },
|
||||||
|
"20815": { "name": "Free Mobile", "country": "France" },
|
||||||
|
"20816": { "name": "Free Mobile", "country": "France" },
|
||||||
|
"20820": { "name": "Bouygues Telecom", "country": "France" },
|
||||||
|
"20821": { "name": "Bouygues Telecom", "country": "France" },
|
||||||
|
"20826": { "name": "NRJ Mobile", "country": "France" },
|
||||||
|
"20888": { "name": "Bouygues Telecom", "country": "France" },
|
||||||
|
"22201": { "name": "TIM", "country": "Italy" },
|
||||||
|
"22210": { "name": "Vodafone", "country": "Italy" },
|
||||||
|
"22288": { "name": "WIND", "country": "Italy" },
|
||||||
|
"22299": { "name": "3 Italia", "country": "Italy" },
|
||||||
|
"23410": { "name": "O2", "country": "UK" },
|
||||||
|
"23415": { "name": "Vodafone", "country": "UK" },
|
||||||
|
"23420": { "name": "3", "country": "UK" },
|
||||||
|
"23430": { "name": "EE", "country": "UK" },
|
||||||
|
"23433": { "name": "EE", "country": "UK" },
|
||||||
|
"26201": { "name": "Telekom", "country": "Germany" },
|
||||||
|
"26202": { "name": "Vodafone", "country": "Germany" },
|
||||||
|
"26203": { "name": "O2", "country": "Germany" },
|
||||||
|
"26207": { "name": "O2", "country": "Germany" },
|
||||||
|
"21401": { "name": "Vodafone", "country": "Spain" },
|
||||||
|
"21403": { "name": "Orange", "country": "Spain" },
|
||||||
|
"21404": { "name": "Yoigo", "country": "Spain" },
|
||||||
|
"21407": { "name": "Movistar", "country": "Spain" },
|
||||||
|
"22801": { "name": "Swisscom", "country": "Switzerland" },
|
||||||
|
"22802": { "name": "Sunrise", "country": "Switzerland" },
|
||||||
|
"22803": { "name": "Salt", "country": "Switzerland" },
|
||||||
|
"20601": { "name": "Proximus", "country": "Belgium" },
|
||||||
|
"20610": { "name": "Orange", "country": "Belgium" },
|
||||||
|
"20620": { "name": "Base", "country": "Belgium" },
|
||||||
|
"20404": { "name": "Vodafone", "country": "Netherlands" },
|
||||||
|
"20408": { "name": "KPN", "country": "Netherlands" },
|
||||||
|
"20412": { "name": "T-Mobile", "country": "Netherlands" },
|
||||||
|
"20416": { "name": "T-Mobile", "country": "Netherlands" },
|
||||||
|
"26801": { "name": "Vodafone", "country": "Portugal" },
|
||||||
|
"26803": { "name": "NOS", "country": "Portugal" },
|
||||||
|
"26806": { "name": "MEO", "country": "Portugal" },
|
||||||
|
"29340": { "name": "SI Mobil", "country": "Slovenia" },
|
||||||
|
"29341": { "name": "Mobitel", "country": "Slovenia" }
|
||||||
|
},
|
||||||
|
"modes": {
|
||||||
|
"0": "Automatic",
|
||||||
|
"1": "Manual",
|
||||||
|
"2": "Deregistered",
|
||||||
|
"3": "Format only",
|
||||||
|
"4": "Manual/Automatic"
|
||||||
|
},
|
||||||
|
"accessTechnology": {
|
||||||
|
"0": "GSM",
|
||||||
|
"1": "GSM Compact",
|
||||||
|
"2": "UTRAN (3G)",
|
||||||
|
"3": "GSM/GPRS with EDGE",
|
||||||
|
"4": "UTRAN with HSDPA",
|
||||||
|
"5": "UTRAN with HSUPA",
|
||||||
|
"6": "UTRAN with HSDPA/HSUPA",
|
||||||
|
"7": "LTE (4G)",
|
||||||
|
"8": "EC-GSM-IoT",
|
||||||
|
"9": "LTE Cat-M / NB-IoT"
|
||||||
|
}
|
||||||
|
}
|
||||||
156
html/saraR4.html
156
html/saraR4.html
@@ -101,16 +101,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">Actual Network connection</p>
|
<p class="card-text">Actual Network connection</p>
|
||||||
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+COPS?', 2)">Get Data</button>
|
<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="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="network_info_alert"></div>
|
||||||
|
<div class="collapse mt-2" id="network_info_logs">
|
||||||
</table>
|
<div class="card card-body bg-light">
|
||||||
|
<small><code id="response_ttyAMA2_AT_COPS_"></code></small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -641,6 +644,149 @@ function getSimInfo(port, timeout) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 getData_saraR4(port, command, timeout){
|
function getData_saraR4(port, command, timeout){
|
||||||
console.log("Data from SaraR4");
|
console.log("Data from SaraR4");
|
||||||
console.log("Port: " + port );
|
console.log("Port: " + port );
|
||||||
|
|||||||
Reference in New Issue
Block a user