Sync Changes with development-SDXPINN

-Updated QuecManager to 0.0.2 from PR #87

- Change-log:

https: //github.com/iamromulan/quectel-rgmii-toolkit/pull/87
Co-Authored-By: Russel Yasol <73575327+dr-dolomite@users.noreply.github.com>
This commit is contained in:
iamromulan
2024-10-11 14:35:17 -04:00
parent 28c39a4829
commit be9ee7def5
27 changed files with 3156 additions and 436 deletions

View File

@@ -66,6 +66,19 @@ const DATA_MAP = {
}),
elementIds: ["IPv4", "IPv6"],
},
QGETCAPABILITY: {
// Changed from LTECATERGORY to match the actual command
parse: (response) => {
const lines = response.split("\n");
for (const line of lines) {
if (line.includes("LTE-CATEGORY")) {
return `CAT-${line.split(":").pop().trim()}`;
}
}
return "";
},
elementId: "lteCategory",
},
};
// DOM Element Selectors
@@ -240,39 +253,6 @@ async function saveIMEISetting() {
}
}
// Data Parsing Functions
function parseDeviceData(response, key) {
const dataMap = {
CGMI: (response) => response.split("\n")[1].trim(),
CGMM: (response) => response.split("\n")[1].trim(),
CGMR: (response) => response.split("\n")[1].trim(),
CNUM: (response) =>
response
.split("\n")[1]
.split(":")[1]
.split(",")[1]
.replace(/"/g, "")
.trim(),
CIMI: (response) => response.split("\n")[1].trim(),
ICCID: (response) => response.split("\n")[1].split(":")[1].trim(),
CGSN: (response) => response.split("\n")[1].trim(),
LANIP: (response) =>
response.split("\n")[1].split(":")[1].split(",")[3].trim(),
WWAN: (response) => ({
IPv4: response
.split("\n")[1]
.split(":")[1]
.split(",")[4]
.replace(/"/g, "")
.trim(),
IPv6: response.split("\n")[2].split(",")[4].replace(/"/g, "").trim(),
}),
};
return dataMap[key]?.(response);
}
// Data Fetching and Display
// Data Parsing and Update Functions
function updateDeviceInfo(key, value) {
const mapping = DATA_MAP[key];
@@ -317,6 +297,7 @@ async function fetchAboutData() {
Object.keys(DATA_MAP).forEach((key) => {
if (item.response.includes(key)) {
const value = DATA_MAP[key].parse(item.response);
console.log("Parsed value:", value);
updateDeviceInfo(key, value);
}
});

View File

@@ -274,7 +274,7 @@ const eventHandlers = {
if (selectedMode !== currentMode) {
const commands = {
"Disabled": 'AT+QMAP="MPDN_rule",0;+QPOWD=1',
"Disabled": 'AT+QMAP="MPDN_rule",0;+QMAPWAC=1;+QPOWD=1',
"ETH Only": `AT+QMAP="MPDN_rule",0,1,0,1,1,"${selectedDeviceMAC}";+QPOWD=1`,
"USB Only": `AT+QMAP="MPDN_rule",0,1,0,3,1,"${selectedDeviceMAC}";+QPOWD=1`
};
@@ -299,10 +299,10 @@ const eventHandlers = {
if (selectedProtocol !== currentProtocol) {
const commands = {
"RMNET": 'AT+QCFG="usbnet",0;+CFUN=1,1',
"ECM (Recommended)": 'AT+QCFG="usbnet",1;+CFUN=1,1',
"MBIM": 'AT+QCFG="usbnet",2;+CFUN=1,1',
"RNDIS": 'AT+QCFG="usbnet",3;+CFUN=1,1'
"RMNET": 'AT+QCFG="usbnet",0;+QPOWD=1',
"ECM (Recommended)": 'AT+QCFG="usbnet",1;+QPOWD=1',
"MBIM": 'AT+QCFG="usbnet",2;+QPOWD=1',
"RNDIS": 'AT+QCFG="usbnet",3;+QPOWD=1'
};
const command = commands[selectedProtocol];

View File

@@ -0,0 +1,363 @@
document.addEventListener("DOMContentLoaded", function () {
// State management
const state = {
isLTECellLockEnabled: false,
is5GCellLockEnabled: false
};
// Constants
const CONSTANTS = {
NOTIFICATION_TIMEOUT: 4000,
SCS_DEFAULT: 'Select SCS',
ENDPOINTS: {
CELL_LOCK: '/cgi-bin/cell-locking/cell-lock.sh',
FETCH_CONFIG: '/cgi-bin/cell-locking/fetch-cell-lock.sh'
}
};
// DOM Elements
const elements = {
lteFields: ['earfcn1', 'pci1', 'earfcn2', 'pci2', 'earfcn3', 'pci3'],
saFields: ['nr-arfcn', 'nr-pci', 'nr-band'],
buttons: {
saveLTE: document.getElementById('saveLTE'),
saveSA: document.getElementById('saveSA'),
resetLTE: document.getElementById('resetLTE'),
resetSA: document.getElementById('resetSA'),
refresh: document.getElementById('refreshConfig')
}
};
// UI Utilities
const UI = {
showNotification: (message, isError = false) => {
const existingNotification = document.querySelector('.notification');
if (existingNotification) {
existingNotification.remove();
}
const notification = document.createElement('div');
notification.className = `notification ${isError ? 'is-danger' : 'is-success'} is-light`;
notification.innerHTML = `
<button class="delete"></button>
${message}
`;
document.querySelector('.column-margin').insertAdjacentElement('beforebegin', notification);
const deleteButton = notification.querySelector('.delete');
deleteButton.addEventListener('click', () => notification.remove());
setTimeout(() => notification.remove(), CONSTANTS.NOTIFICATION_TIMEOUT);
},
setButtonLoading: (buttonId, isLoading, text = '') => {
const button = document.getElementById(buttonId);
if (!button) return;
button.disabled = isLoading;
button.innerHTML = isLoading ? `
<span class="icon is-small">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<span class="ml-2">Processing...</span>
` : text;
},
toggleInputs: (disabled) => {
document.querySelectorAll('input, select').forEach(input => {
input.disabled = disabled;
});
},
clearInputs: (fields) => {
fields.forEach(fieldId => {
const element = document.getElementById(fieldId);
if (element) {
if (element.tagName === 'SELECT') {
element.selectedIndex = 0;
} else {
element.value = '';
}
}
});
}
};
// Validation Utilities
const Validator = {
validateNumeric: (value, fieldName) => {
if (value && !/^\d+$/.test(value)) {
UI.showNotification(`${fieldName} must be a numeric value`, true);
return false;
}
return true;
},
validateLTEInputs: () => {
const earfcn1 = document.getElementById('earfcn1').value;
const pci1 = document.getElementById('pci1').value;
if (!earfcn1 && !pci1) return true;
if (!Validator.validateNumeric(earfcn1, 'EARFCN 1')) return false;
if (!Validator.validateNumeric(pci1, 'PCI 1')) return false;
if ((earfcn1 && !pci1) || (!earfcn1 && pci1)) {
UI.showNotification('Both EARFCN and PCI must be provided for each pair', true);
return false;
}
return true;
},
validate5GInputs: () => {
const nrArfcn = document.getElementById('nr-arfcn').value;
const nrPci = document.getElementById('nr-pci').value;
const scs = document.getElementById('scs').value;
const nrBand = document.getElementById('nr-band').value;
if (!nrArfcn && !nrPci && scs === CONSTANTS.SCS_DEFAULT && !nrBand) return true;
if (!Validator.validateNumeric(nrArfcn, 'NR ARFCN')) return false;
if (!Validator.validateNumeric(nrPci, 'NR PCI')) return false;
if (!Validator.validateNumeric(nrBand, 'NR Band')) return false;
if (scs === CONSTANTS.SCS_DEFAULT) {
UI.showNotification('Please select an SCS value', true);
return false;
}
return true;
}
};
// Data Utilities
const DataUtils = {
hasValues: (fields) => {
return fields.some(field => {
const element = document.getElementById(field);
if (element.tagName === 'SELECT') {
return element.value !== CONSTANTS.SCS_DEFAULT;
}
return element.value.trim() !== '';
});
},
getFormData: (fields) => {
return fields.reduce((acc, field) => {
acc[field] = document.getElementById(field).value;
return acc;
}, {});
}
};
// API Handlers
const API = {
async makeRequest(endpoint, method = 'GET', body = null) {
try {
const response = await fetch(endpoint, {
method,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
...(body && { body: new URLSearchParams(body).toString() })
});
return await response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
},
async saveLTEConfiguration(formData) {
return API.makeRequest(CONSTANTS.ENDPOINTS.CELL_LOCK, 'POST', formData);
},
async save5GConfiguration(formData) {
return API.makeRequest(CONSTANTS.ENDPOINTS.CELL_LOCK, 'POST', formData);
},
async resetConfiguration(type) {
return API.makeRequest(CONSTANTS.ENDPOINTS.CELL_LOCK, 'POST', {
[`reset_${type}`]: '1'
});
},
async fetchConfigurations() {
return API.makeRequest(CONSTANTS.ENDPOINTS.FETCH_CONFIG);
}
};
// Event Handlers
const EventHandlers = {
async handleLTESave(e) {
e.preventDefault();
if (!Validator.validateLTEInputs()) return;
if (state.is5GCellLockEnabled || DataUtils.hasValues(elements.saFields)) {
UI.showNotification('LTE cell lock cannot be configured when 5G-SA cell lock is enabled', true);
return;
}
try {
UI.toggleInputs(true);
UI.setButtonLoading('saveLTE', true);
const formData = DataUtils.getFormData(elements.lteFields);
const response = await API.saveLTEConfiguration(formData);
if (response.status === 'success') {
state.isLTECellLockEnabled = true;
state.is5GCellLockEnabled = false;
UI.showNotification('LTE cell lock configured successfully');
} else {
UI.showNotification(response.message || 'Error configuring LTE cell lock', true);
}
} catch (error) {
UI.showNotification(`Error configuring LTE cell lock: ${error.message}`, true);
} finally {
UI.toggleInputs(false);
UI.setButtonLoading('saveLTE', false, 'Lock LTE Cells');
}
},
async handle5GSave(e) {
e.preventDefault();
if (!Validator.validate5GInputs()) return;
if (state.isLTECellLockEnabled || DataUtils.hasValues(elements.lteFields)) {
UI.showNotification('5G-SA cell lock cannot be configured when LTE cell lock is enabled', true);
return;
}
try {
UI.toggleInputs(true);
UI.setButtonLoading('saveSA', true);
const scsValue = document.getElementById('scs').value;
const formData = {
nrarfcn: document.getElementById('nr-arfcn').value,
nrpci: document.getElementById('nr-pci').value,
scs: scsValue === CONSTANTS.SCS_DEFAULT ? '' : scsValue.split(' ')[0],
band: document.getElementById('nr-band').value
};
const response = await API.save5GConfiguration(formData);
if (response.status === 'success') {
state.is5GCellLockEnabled = true;
state.isLTECellLockEnabled = false;
UI.showNotification('5G-SA cell lock configured successfully');
} else {
UI.showNotification(response.message || 'Error configuring 5G-SA cell lock', true);
}
} catch (error) {
UI.showNotification(`Error configuring 5G-SA cell lock: ${error.message}`, true);
} finally {
UI.toggleInputs(false);
UI.setButtonLoading('saveSA', false, 'Lock 5G-SA Cells');
}
},
async handleLTEReset(e) {
e.preventDefault();
try {
UI.setButtonLoading('resetLTE', true);
const response = await API.resetConfiguration('lte');
if (response.status === 'success') {
UI.clearInputs(elements.lteFields);
state.isLTECellLockEnabled = false;
UI.showNotification('LTE cell lock reset successfully');
} else {
UI.showNotification(response.message || 'Error resetting LTE cell lock', true);
}
} catch (error) {
UI.showNotification(`Error resetting LTE cell lock: ${error.message}`, true);
} finally {
UI.setButtonLoading('resetLTE', false, 'Reset LTE Cells');
}
},
async handle5GReset(e) {
e.preventDefault();
try {
UI.setButtonLoading('resetSA', true);
const response = await API.resetConfiguration('5g');
if (response.status === 'success') {
UI.clearInputs([...elements.saFields, 'scs']);
state.is5GCellLockEnabled = false;
UI.showNotification('5G-SA cell lock reset successfully');
} else {
UI.showNotification(response.message || 'Error resetting 5G-SA cell lock', true);
}
} catch (error) {
UI.showNotification(`Error resetting 5G-SA cell lock: ${error.message}`, true);
} finally {
UI.setButtonLoading('resetSA', false, 'Reset 5G-SA Cells');
}
},
async handleRefresh(e) {
e?.preventDefault();
try {
const data = await API.fetchConfigurations();
if (data.status === 'success' && data.configurations) {
const { lte, sa } = data.configurations;
if (lte) {
state.isLTECellLockEnabled = true;
state.is5GCellLockEnabled = false;
elements.lteFields.forEach(field => {
if (lte[field]) document.getElementById(field).value = lte[field];
});
}
if (sa) {
state.is5GCellLockEnabled = true;
state.isLTECellLockEnabled = false;
elements.saFields.forEach(field => {
if (sa[field.replace('-', '')]) {
document.getElementById(field).value = sa[field.replace('-', '')];
}
});
if (sa.scs) {
const scsSelect = document.getElementById('scs');
Array.from(scsSelect.options).some((option, index) => {
if (option.value === sa.scs) {
scsSelect.selectedIndex = index;
return true;
}
return false;
});
}
}
}
} catch (error) {
console.error('Error fetching configurations:', error);
UI.showNotification(`Error fetching configurations: ${error.message}`, true);
}
}
};
// Initialize event listeners
function initializeEventListeners() {
elements.buttons.saveLTE?.addEventListener('click', EventHandlers.handleLTESave);
elements.buttons.saveSA?.addEventListener('click', EventHandlers.handle5GSave);
elements.buttons.resetLTE?.addEventListener('click', EventHandlers.handleLTEReset);
elements.buttons.resetSA?.addEventListener('click', EventHandlers.handle5GReset);
elements.buttons.refresh?.addEventListener('click', EventHandlers.handleRefresh);
}
// Initialize the application
function initialize() {
initializeEventListeners();
EventHandlers.handleRefresh();
}
initialize();
});

View File

@@ -0,0 +1,246 @@
class NeighbourCellScanner {
constructor() {
this.tableBody = document.getElementById("neighbourCellTableBody");
this.tableHeaders = document.querySelector("#neighbourCellTable thead tr");
this.lteScanBtn = document.getElementById("startLTEScanBtn");
this.nr5gScanBtn = document.getElementById("startNR5GScanBtn");
this.resetBtn = document.getElementById("resetScanBtn");
this.bindEvents();
}
bindEvents() {
this.lteScanBtn.addEventListener("click", () => this.startLTEScan());
this.nr5gScanBtn.addEventListener("click", () => this.startNR5GScan());
this.resetBtn.addEventListener("click", () => this.resetTable());
}
updateTableHeaders(mode) {
if (mode === "LTE") {
this.tableHeaders.innerHTML = `
<th>Type</th>
<th>EARFCN</th>
<th>Physical ID</th>
<th class="is-hidden-mobile">RSRP</th>
<th class="is-hidden-mobile">RSRQ</th>
<th class="is-hidden-mobile">RSSI</th>
`;
} else if (mode === "NR5G") {
this.tableHeaders.innerHTML = `
<th>Type</th>
<th>ARFCN</th>
<th>Physical ID</th>
<th class="is-hidden-mobile">RSRP</th>
<th class="is-hidden-mobile">RSSI</th>
<th class="is-hidden-mobile">--</th>
`;
}
}
async sendCommand(command) {
try {
const response = await fetch("/cgi-bin/atinout_handler.sh", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: `command=${encodeURIComponent(command)}`,
});
// remove the initial table row
this.tableBody.innerHTML = "";
return await response.json();
} catch (error) {
console.error("API Error:", error);
// add the initial table row again
this.addPlaceholderRow();
throw error;
}
}
getSignalQuality(value) {
if (value > -90) return "is-success";
if (value > -100) return "is-warning";
return "is-danger";
}
getSignalText(value) {
if (value > -90) return "Good";
if (value > -100) return "Fair";
return "Poor";
}
createSignalTag(value) {
const quality = this.getSignalQuality(value);
const text = this.getSignalText(value);
return `
<div class="tags has-addons">
<span class="tag is-size-6">${value}</span>
<span class="tag ${quality} is-size-6 has-text-white">${text}</span>
</div>
`;
}
parseLTEResponse(response) {
const output = response.output;
const lines = output.split("\n");
const results = [];
for (const line of lines) {
if (line.startsWith("+QENG:")) {
const match = line.match(
/"([^"]+)","LTE",(\d+),(\d+),(-?\d+),(-?\d+),(-?\d+)/
);
if (match) {
// Extract just 'intra' or 'inter' from the type
const fullType = match[1];
const type = fullType.includes("intra") ? "intra" : "inter";
results.push({
type: type,
earfcn: match[2],
pci: match[3],
rsrq: parseInt(match[4]),
rsrp: parseInt(match[5]),
rssi: parseInt(match[6]),
});
}
}
}
return results;
}
parseNR5GResponse(response) {
const output = response.output;
const lines = output.split("\n");
const results = [];
for (const line of lines) {
if (line.startsWith("+QNWCFG:")) {
const match = line.match(/\d+,(\d+),(\d+),(-?\d+),(-?\d+)/);
if (match) {
results.push({
arfcn: match[1],
pci: match[2],
rsrp: parseInt(match[3]),
rssi: parseInt(match[4]),
});
}
}
}
return results;
}
addPlaceholderRow() {
const row = document.createElement("tr");
row.innerHTML = `
<td>--</td>
<td>--</td>
<td>--</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6">--</span>
<span class="tag is-light is-size-6">No Data</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6">--</span>
<span class="tag is-light is-size-6">No Data</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6">--</span>
<span class="tag is-light is-size-6">No Data</span>
</div>
</td>
`;
this.tableBody.appendChild(row);
}
async startLTEScan() {
try {
const response = await this.sendCommand('AT+QENG="neighbourcell"');
const results = this.parseLTEResponse(response);
// Clear the table and update headers first
this.tableBody.innerHTML = "";
this.updateTableHeaders("LTE");
if (results.length === 0) {
this.addPlaceholderRow();
} else {
results.forEach((result) => {
const row = document.createElement("tr");
row.innerHTML = `
<td>${result.type}</td>
<td>${result.earfcn}</td>
<td>${result.pci}</td>
<td class="is-hidden-mobile">${this.createSignalTag(
result.rsrp
)}</td>
<td class="is-hidden-mobile">${this.createSignalTag(
result.rsrq
)}</td>
<td class="is-hidden-mobile">${this.createSignalTag(
result.rssi
)}</td>
`;
this.tableBody.appendChild(row);
});
}
} catch (error) {
console.error("LTE Scan failed:", error);
this.resetTable();
}
}
async startNR5GScan() {
try {
const response = await this.sendCommand(
'AT+QNWCFG="nr5g_meas_info",1;+QNWCFG="nr5g_meas_info"'
);
const results = this.parseNR5GResponse(response);
// Clear the table and update headers first
this.tableBody.innerHTML = "";
this.updateTableHeaders("NR5G");
if (results.length === 0) {
this.addPlaceholderRow();
} else {
results.forEach((result) => {
const row = document.createElement("tr");
row.innerHTML = `
<td>NR5G</td>
<td>${result.arfcn}</td>
<td>${result.pci}</td>
<td class="is-hidden-mobile">${this.createSignalTag(
result.rsrp
)}</td>
<td class="is-hidden-mobile">${this.createSignalTag(
result.rssi
)}</td>
<td class="is-hidden-mobile">--</td>
`;
this.tableBody.appendChild(row);
});
}
} catch (error) {
console.error("NR5G Scan failed:", error);
this.resetTable();
}
}
resetTable() {
this.tableBody.innerHTML = "";
this.updateTableHeaders("LTE"); // Reset to default LTE headers
this.addPlaceholderRow(); // Add placeholder row after reset
}
}
// Initialize the scanner when the document is ready
document.addEventListener("DOMContentLoaded", () => {
const scanner = new NeighbourCellScanner();
scanner.resetTable(); // Show initial placeholder row
});

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@ document.addEventListener('DOMContentLoaded', function() {
form.insertAdjacentElement('beforebegin', notification);
// Remove notification after 5 seconds
setTimeout(() => notification.remove(), 5000);
setTimeout(() => notification.remove(), 4000);
// Allow manual close
notification.querySelector('.delete').addEventListener('click', () => notification.remove());

View File

@@ -15,6 +15,9 @@ let currentNetworkMode = "";
let currentNr5GModeControl = "";
let updatedNr5GModeControl = "";
let updatedSlot = "";
let currentSlot = "";
// Function to check if settings have changed
function haveSettingsChanged() {
return (
@@ -37,6 +40,13 @@ function haveNr5GModeControlChanged() {
return currentNr5GModeControl !== updatedNr5GModeControl;
}
// Function to check if SIM slot has changed
function haveSimSlotChanged() {
console.log("Current SIM slot:", currentSlot);
console.log("Updated SIM slot:", updatedSlot);
return currentSlot !== updatedSlot;
}
// Function to apply network mode changes immediately
async function applyNetworkModeChange() {
if (!haveNetworkModeChanged()) {
@@ -75,6 +85,40 @@ async function applyNr5GModeControlChange() {
}
}
// Function to apply SIM slot changes immediately
async function applySimSlotChange() {
if (!haveSimSlotChanged()) {
alert("No changes detected in the SIM slot.");
return;
}
try {
const atCommand = `AT+QUIMSLOT=${updatedSlot}`;
console.log("Sending AT command for SIM slot change:", atCommand);
const response = await sendATCommand(atCommand);
console.log("AT command response:", response);
// Disable the select input while the SIM slot is being applied
const simSlotSelect = document.getElementById("simSlot");
simSlotSelect.disabled = true;
// Send network deregistration command to apply SIM slot changes after 1 second
await new Promise((resolve) => setTimeout(resolve, 1000));
await sendATCommand("AT+COPS=2");
// Wait for 2 seconds before turning on the modem
await new Promise((resolve) => setTimeout(resolve, 2000));
await sendATCommand("AT+COPS=0");
// re-enable the select input after the SIM slot is applied
simSlotSelect.disabled = false;
alert("SIM slot applied successfully!");
} catch (error) {
console.error("Error applying SIM slot:", error);
alert("Error applying SIM slot. Please try again.");
}
}
// Function to send settings to the modem
async function saveSettings() {
if (!haveSettingsChanged()) {
@@ -281,6 +325,35 @@ async function fetchCellSettings() {
});
}
}
} else if (item.response.includes("QUIMSLOT")) {
const slot = item.response
.split("\n")[1]
.split(":")[1]
.split(",")[0]
.trim();
console.log("Slot:", slot);
currentSlot = slot;
updatedSlot = slot;
const slotInput = document.getElementById("simSlot");
if (slotInput) {
// Explicitly set the value and update the selected option
slotInput.value = slot;
// Add event listener for slot changes if not already added
if (!slotInput.hasListener) {
slotInput.hasListener = true;
slotInput.addEventListener("change", (e) => {
updatedSlot = e.target.value;
if (updatedSlot) {
// Only apply if a valid slot is selected
applySimSlotChange();
}
});
}
}
}
});
} catch (error) {

View File

@@ -0,0 +1,251 @@
class SMSManager {
constructor() {
this.initializeElements();
this.bindEvents();
this.init();
}
initializeElements() {
this.smsContainer = document.getElementById("sms-container");
this.refreshButton = document.getElementById("refresh-sms");
this.deleteSelectedButton = document.getElementById("delete-selected-sms");
this.phoneNumberInput = document.getElementById("phone-number-input");
this.messageTextarea = document.getElementById("message-input");
this.sendSMSButton = document.getElementById("send-sms");
this.resetButton = document.getElementById("reset-form");
const elements = {
"SMS Container": this.smsContainer,
"Refresh Button": this.refreshButton,
"Delete Selected Button": this.deleteSelectedButton,
"Phone Number Input": this.phoneNumberInput,
"Message Textarea": this.messageTextarea,
"Send SMS Button": this.sendSMSButton,
"Reset Button": this.resetButton,
};
for (const [name, element] of Object.entries(elements)) {
if (!element) {
console.error(`${name} element not found!`);
}
}
}
bindEvents() {
if (this.refreshButton) {
this.refreshButton.addEventListener("click", (e) => {
e.preventDefault();
this.refreshSMS();
});
}
if (this.deleteSelectedButton) {
this.deleteSelectedButton.addEventListener("click", (e) => {
e.preventDefault();
this.deleteSelectedSMS();
});
}
if (this.sendSMSButton) {
this.sendSMSButton.addEventListener("click", (e) => {
e.preventDefault();
this.sendSMS();
});
}
if (this.resetButton) {
this.resetButton.addEventListener("click", (e) => {
e.preventDefault();
this.resetForm();
});
}
}
async sendCommand(command) {
try {
const response = await fetch("/cgi-bin/atinout_handler.sh", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: `command=${encodeURIComponent(command)}`,
});
return await response.json();
} catch (error) {
console.error("AT command failed:", error);
throw error;
}
}
async init() {
try {
await this.sendCommand("AT+CMGF=1");
await this.refreshSMS();
} catch (error) {
console.error("Initialization failed:", error);
}
}
showLoadingState() {
this.smsContainer.innerHTML = `
<div class="loading-container">
<span class="icon is-large">
<i class="fas fa-spinner fa-pulse fa-2x"></i>
</span>
<p class="mt-2">Fetching SMS...</p>
</div>
`;
}
async refreshSMS() {
this.showLoadingState();
try {
const response = await this.sendCommand('AT+CMGL="ALL"');
let rawData;
if (typeof response === "string") {
rawData = response;
} else if (response && response.result) {
rawData = response.result;
} else if (response && response.output) {
rawData = response.output;
}
if (!rawData) {
console.error("No valid data received from AT command");
this.displayMessages([]);
return;
}
const messages = this.parseSMSData(rawData);
this.displayMessages(messages);
} catch (error) {
console.error("Failed to refresh SMS:", error);
this.displayMessages([]);
}
}
parseSMSData(data) {
const messages = [];
const lines = data.split("\n");
let currentMessage = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (!line || line === "OK" || line === 'AT+CMGL="ALL"') continue;
if (line.startsWith("+CMGL:")) {
if (currentMessage && currentMessage.message) {
messages.push(currentMessage);
}
const headerMatch = line.match(
/\+CMGL:\s*(\d+),"([^"]*?)","([^"]*?)",,"([^"]*?)"/
);
if (headerMatch) {
currentMessage = {
index: headerMatch[1],
status: headerMatch[2],
sender: headerMatch[3],
date: headerMatch[4].replace("+32", ""),
message: "",
};
}
} else if (currentMessage) {
currentMessage.message += (currentMessage.message ? "\n" : "") + line;
}
}
if (currentMessage && currentMessage.message) {
messages.push(currentMessage);
}
return messages;
}
createMessageElement(message, index) {
const formattedDate = message.date.replace(
/(\d{2})\/(\d{2})\/(\d{2}),(\d{2}:\d{2}:\d{2})/,
"20$3-$2-$1 $4"
);
return `
<div class="cell" id="sms-message-${index}">
<div class="is-flex is-align-items-center">
<div class="checkbox mr-6">
<input type="checkbox"
id="sms-checkbox-${index}"
data-index="${message.index}" />
</div>
<div class="is-flex is-flex-direction-column is-align-items-start">
<p class="has-text-weight-semibold" id="sms-sender-${index}">${message.sender}</p>
<p id="sms-date-${index}">${formattedDate}</p>
<p id="sms-content-${index}">${message.message}</p>
</div>
</div>
</div>
`;
}
displayMessages(messages) {
if (!this.smsContainer) {
console.error("SMS container not found!");
return;
}
this.smsContainer.innerHTML =
messages.length === 0
? '<div class="cell" id="no-messages">No messages found</div>'
: messages
.map((msg, index) => this.createMessageElement(msg, index))
.join("");
}
async deleteSelectedSMS() {
const selectedCheckboxes = document.querySelectorAll(
'input[type="checkbox"]:checked'
);
const indices = Array.from(selectedCheckboxes).map(
(cb) => cb.dataset.index
);
if (indices.length === 0) return;
try {
for (const index of indices) {
await this.sendCommand(`AT+CMGD=${index}`);
}
await this.refreshSMS();
} catch (error) {
console.error("Failed to delete messages:", error);
}
}
async sendSMS() {
const phoneNumber = this.phoneNumberInput.value.trim();
const message = this.messageTextarea.value.trim();
if (!phoneNumber || !message) {
alert("Please enter both phone number and message");
return;
}
try {
await this.sendCommand(`AT+CMGS="${phoneNumber}"`);
await this.sendCommand(`${message}\x1A`);
this.resetForm();
await this.refreshSMS();
} catch (error) {
console.error("Failed to send SMS:", error);
}
}
resetForm() {
if (this.phoneNumberInput) this.phoneNumberInput.value = "";
if (this.messageTextarea) this.messageTextarea.value = "";
}
}
document.addEventListener("DOMContentLoaded", () => {
window.smsManager = new SMSManager();
});

View File

@@ -54,7 +54,7 @@ let atCommandInterval;
let connectionStatusInterval;
let trafficStatsInterval;
const DEFAULT_REFRESH_RATE = 5000; // 5 seconds
const TRAFFIC_STATS_REFRESH_RATE = 1000; // 1 second
const TRAFFIC_STATS_REFRESH_RATE = 5000; // 5 seconds
const CONNECTION_CHECK_MULTIPLIER = 6; // Will make connection check 6 times slower
const STORAGE_KEY = "modemRefreshRate";
@@ -958,26 +958,31 @@ function processWANIPData(jsonData) {
async function fetchTrafficStats() {
try {
const response = await fetch("/cgi-bin/home/traffic_stats.sh", {
method: "GET",
// Send the AT command to the CGI handler
const response = await fetch("/cgi-bin/atinout_handler.sh", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
body: `command=${encodeURIComponent("AT+QGDNRCNT?")}`,
});
// Get the raw text response
const rawData = await response.text();
if (!rawData || rawData.trim() === "") {
throw new Error("Empty or malformed response");
}
const jsonData = JSON.parse(rawData);
// Extract the upload and download values using regex
const match = rawData.match(/\+QGDNRCNT: (\d+),(\d+)/);
if (!match || match.length < 3) {
throw new Error("Unexpected response format");
}
console.log("Traffic stats fetched successfully");
// Parse rx (download) and tx (upload) values
const download = jsonData.download;
const upload = jsonData.upload;
// Parse the upload and download values
const upload = parseInt(match[1], 10);
const download = parseInt(match[2], 10);
// Convert to human-readable format
const downloadFormatted = formatBytes(download);

View File

@@ -75,7 +75,7 @@ document.addEventListener('DOMContentLoaded', function() {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'command=' + encodeURIComponent('AT+QPOWD=1')
body: 'command=' + encodeURIComponent('AT+CFUN=1,1')
});
if (!response.ok) {