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

@@ -164,6 +164,10 @@
<td>Firmware Version</td>
<th id="firmwareVersion"></th>
</tr>
<tr>
<td>LTE Category</td>
<th id="lteCategory"></th>
</tr>
<tr>
<td>Phone Number</td>
<th id="phoneNumber"></th>

View File

@@ -24,6 +24,7 @@
<script src="/js/styles/modal-trigger.js"></script>
<script src="/js/utils/reboot.js"></script>
<script src="/js/utils/restart-connection.js"></script>
<script src="/js/cell-locking/cell-lock.js"></script>
<script defer src="/js/auth/auth.js"></script>
<script>
@@ -156,6 +157,7 @@
<input
class="input"
type="email"
id="earfcn1"
placeholder="EARFCN"
/>
<span class="icon is-left">
@@ -169,7 +171,12 @@
<div class="field">
<label class="label">PCI 1</label>
<div class="control has-icons-left">
<input class="input" type="email" placeholder="PCI" />
<input
class="input"
type="email"
placeholder="PCI"
id="pci1"
/>
<span class="icon is-left">
<i class="fa-solid fa-signal"></i>
</span>
@@ -187,6 +194,7 @@
class="input"
type="email"
placeholder="EARFCN"
id="earfcn2"
/>
<span class="icon is-left">
<i class="fas fa-bolt"></i>
@@ -199,7 +207,12 @@
<div class="field">
<label class="label">PCI 2</label>
<div class="control has-icons-left">
<input class="input" type="email" placeholder="PCI" />
<input
class="input"
type="email"
placeholder="PCI"
id="pci2"
/>
<span class="icon is-left">
<i class="fa-solid fa-signal"></i>
</span>
@@ -217,6 +230,7 @@
class="input"
type="email"
placeholder="EARFCN"
id="earfcn3"
/>
<span class="icon is-left">
<i class="fas fa-bolt"></i>
@@ -229,7 +243,12 @@
<div class="field">
<label class="label">PCI 3</label>
<div class="control has-icons-left">
<input class="input" type="email" placeholder="PCI" />
<input
class="input"
type="email"
placeholder="PCI"
id="pci3"
/>
<span class="icon is-left">
<i class="fa-solid fa-signal"></i>
</span>
@@ -244,6 +263,7 @@
<a
href="#"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
id="saveLTE"
>
Lock LTE Cells
</a>
@@ -251,6 +271,7 @@
<a
href="#"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
id="resetLTE"
>
Reset
</a>
@@ -275,6 +296,7 @@
class="input"
type="email"
placeholder="NR-ARFCN"
id="nr-arfcn"
/>
<span class="icon is-left">
<i class="fas fa-bolt"></i>
@@ -291,6 +313,7 @@
class="input"
type="email"
placeholder="NR PCI"
id="nr-pci"
/>
<span class="icon is-left">
<i class="fa-solid fa-signal"></i>
@@ -306,13 +329,13 @@
<label class="label">SCS</label>
<div class="control has-icons-left">
<span class="select">
<select>
<select id="scs">
<option selected>Select SCS</option>
<option>15 kHz</option>
<option>30 kHz</option>
<option>60 kHz</option>
<option>120 kHz</option>
<option>240 kHz</option>
<option value="15">15 kHz</option>
<option value="30">30 kHz</option>
<option value="60">60 kHz</option>
<option value="120">120 kHz</option>
<option value="240">240 kHz</option>
</select>
</span>
<span class="icon is-left">
@@ -330,6 +353,7 @@
class="input"
type="email"
placeholder="NR Band"
id="nr-band"
/>
<span class="icon is-left">
<i class="fa-solid fa-signal"></i>
@@ -345,6 +369,7 @@
<a
href="#"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
id="saveSA"
>
Lock SA Cells
</a>
@@ -352,6 +377,7 @@
<a
href="#"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
id="resetSA"
>
Reset
</a>
@@ -388,11 +414,12 @@
<span class="icon has-text-info">
<i class="fas fa-info-circle"></i>
</span>
<span>Current Active Locked Cells</span>
<span>NR-5G Cell Locking</span>
</div>
<p class="block has-text-weight-semibold">
150/69, 1350/70, 1350/71
The NR-5G cell locking feature is only available on 5G-NR SA mode. <br />
This will also change the preferred network mode to NR5G.
</p>
</div>
</div>

View File

@@ -24,6 +24,7 @@
<script src="/js/styles/modal-trigger.js"></script>
<script src="/js/utils/reboot.js"></script>
<script src="/js/utils/restart-connection.js"></script>
<script src="/js/cell-scanner/cell-scanner.js"></script>
<script defer src="/js/auth/auth.js"></script>
<script>
@@ -149,7 +150,7 @@
</p>
</div>
<div class="card-content">
<table class="table is-fullwidth">
<!-- <table class="table is-fullwidth">
<thead>
<tr>
<th>Network Provider</th>
@@ -162,7 +163,7 @@
<th class="is-hidden-mobile">SINR</th>
</tr>
</thead>
<tbody>
<tbody id="fullCellScanTableBody">
<tr>
<td>Smart</td>
<td>B1</td>
@@ -171,106 +172,46 @@
<td>623</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -103 </span>
<span class="tag is-danger is-size-6 has-text-white">
Poor
</span>
<span class="tag is-size-6">-103</span>
<span class="tag is-danger is-size-6 has-text-white"
>Poor</span
>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -103 </span>
<span class="tag is-danger is-size-6 has-text-white">
Poor
</span>
<span class="tag is-size-6">-103</span>
<span class="tag is-danger is-size-6 has-text-white"
>Poor</span
>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -5 </span>
<span class="tag is-danger is-size-6 has-text-white">
Poor
</span>
</div>
</td>
</tr>
<tr>
<td>DITO</td>
<td>B2</td>
<td>150</td>
<td>10</td>
<td>623</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -90 </span>
<span class="tag is-warning is-size-6 has-text-white">
Medium
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -90 </span>
<span class="tag is-warning is-size-6 has-text-white">
Medium
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> 10 </span>
<span class="tag is-warning is-size-6 has-text-white">
Medium
</span>
</div>
</td>
</tr>
<tr>
<td>Globe</td>
<td>B3</td>
<td>150</td>
<td>10</td>
<td>623</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -65 </span>
<span class="tag is-success is-size-6 has-text-white">
Strong
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -65 </span>
<span class="tag is-success is-size-6 has-text-white">
Strong
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> 30 </span>
<span class="tag is-success is-size-6 has-text-white">
Strong
</span>
<span class="tag is-size-6">-5</span>
<span class="tag is-danger is-size-6 has-text-white"
>Poor</span
>
</div>
</td>
</tr>
</tbody>
</table>
</table> -->
<p>
Still under development. Please check back later for updates.
</p>
</div>
<div class="card-footer">
<a
href="#"
id="startFullScanBtn"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
>
Start Full Scan
</a>
<a
href="#"
id="resetFullScanBtn"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
>
Reset
@@ -285,124 +226,38 @@
<div class="card-header-title">Full Neighbour Cell Scanner</div>
</div>
<div class="card-content">
<table class="table is-fullwidth">
<table class="table is-fullwidth" id="neighbourCellTable">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>EARFCN</th>
<th>Bandwidth</th>
<th>Physical ID</th>
<th class="is-hidden-mobile">RSRP</th>
<th class="is-hidden-mobile">RSRQ</th>
<th class="is-hidden-mobile">SINR</th>
<th class="is-hidden-mobile">RSSI</th>
</tr>
</thead>
<tbody>
<tr>
<td>B1</td>
<td>150</td>
<td>10</td>
<td>623</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -103 </span>
<span class="tag is-danger is-size-6 has-text-white">
Poor
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -103 </span>
<span class="tag is-danger is-size-6 has-text-white">
Poor
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -5 </span>
<span class="tag is-danger is-size-6 has-text-white">
Poor
</span>
</div>
</td>
</tr>
<tr>
<td>B2</td>
<td>150</td>
<td>10</td>
<td>623</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -90 </span>
<span class="tag is-warning is-size-6 has-text-white">
Medium
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -90 </span>
<span class="tag is-warning is-size-6 has-text-white">
Medium
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> 10 </span>
<span class="tag is-warning is-size-6 has-text-white">
Medium
</span>
</div>
</td>
</tr>
<tr>
<td>B3</td>
<td>150</td>
<td>10</td>
<td>623</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -65 </span>
<span class="tag is-success is-size-6 has-text-white">
Strong
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> -65 </span>
<span class="tag is-success is-size-6 has-text-white">
Strong
</span>
</div>
</td>
<td class="is-hidden-mobile">
<div class="tags has-addons">
<span class="tag is-size-6"> 30 </span>
<span class="tag is-success is-size-6 has-text-white">
Strong
</span>
</div>
</td>
</tr>
</tbody>
<tbody id="neighbourCellTableBody"></tbody>
</table>
</div>
<div class="card-footer">
<a
href="#"
id="startLTEScanBtn"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
>
Start Neighbourcell Scan
Start LTE Neighbourcell Scan
</a>
<a
href="#"
id="startNR5GScanBtn"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
>
Start NR5G Neighbourcell Scan
</a>
<a
href="#"
id="resetScanBtn"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
>
Reset
@@ -417,47 +272,19 @@
<footer class="footer">
<div class="content">
<div class="fixed-grid has-2-cols has-1-cols-mobile">
<div class="grid">
<div class="cell">
<div class="icon-text">
<span class="icon has-text-info">
<i class="fas fa-info-circle"></i>
</span>
<span>Information</span>
</div>
<p class="block has-text-weight-semibold">
Full Network Provider Cell Scanner will scan all of the cells in
your area even from other network providers.
<br />
The NR-5G scan will only display SA bands available in your
area.
<br />
Scanning will take a few minutes so please wait patiently.
<br />
If problem persists after scanning, please reboot the modem.
</p>
</div>
<div class="cell">
<div class="icon-text">
<span class="icon has-text-info">
<i class="fas fa-info-circle"></i>
</span>
<span>Information</span>
</div>
<p class="block has-text-weight-semibold">
Full Neighbour Cell Scanner will only scan the bands of your
active network provider.
<br />
The NR-5G scan result is based on your current active 5G network
mode.
<br />
If problem persists after scanning, please reboot the modem.
</p>
</div>
<div class="icon-text">
<span class="icon has-text-info">
<i class="fas fa-info-circle"></i>
</span>
<span>Full Network Provider Cell Scanner</span>
</div>
<p class="block has-text-weight-semibold">
This command is recommended to be used when there is no (U)SIM card.
<br />
This command does not apply to 5G cells in NSA mode.
<br />
Scanning will take a few minutes so please wait patiently.
</p>
</div>
</div>
</footer>

View File

@@ -233,6 +233,27 @@
</p>
</div>
</div>
<div class="cell">
<div class="field">
<label class="label">U-SIM Slot Configuration</label>
<p class="control has-icons-left">
<span class="select">
<select id="simSlot">
<option value="">Select active SIM slot</option>
<option value="1">SIM Slot 1</option>
<option value="2">SIM Slot 2</option>
</select>
</span>
<span class="icon is-small is-left">
<i class="fas fa-sim-card" id="simCardIcon"></i>
</span>
</p>
<p class="help">
Selecting a mode will apply immediately.
</p>
</div>
</div>
</div>
</div>
</div>

View File

@@ -24,6 +24,7 @@
<script src="/js/styles/modal-trigger.js"></script>
<script src="/js/utils/reboot.js"></script>
<script src="/js/utils/restart-connection.js"></script>
<script src="/js/cell-sms/sms-manager.js"></script>
<script defer src="/js/auth/auth.js"></script>
<script>
@@ -152,150 +153,9 @@
class="fixed-grid has-1-cols"
style="height: 450px; overflow-y: scroll"
>
<div class="grid is-gap-4 is-fullwidth">
<div class="cell">
<div class="is-flex is-align-items-center">
<div class="checkbox mr-6">
<input type="checkbox" />
</div>
<div
class="is-flex is-flex-direction-column is-align-items-start"
>
<p class="has-text-weight-semibold">
Senders Name Here
</p>
<p>2022-11-20 13:30:00</p>
<p>
Message content here. Lorem ipsum dolor sit amet
consectetur adipisicing elit. Corrupti doloremque
voluptatum velit repellendus ipsum delectus
blanditiis quis dolores. Maxime labore esse
laboriosam inventore error molestiae consequuntur
quo, deleniti ea nihil.
</p>
</div>
</div>
</div>
<div class="cell">
<div class="is-flex is-align-items-center">
<div class="checkbox mr-6">
<input type="checkbox" />
</div>
<div
class="is-flex is-flex-direction-column is-align-items-start"
>
<p class="has-text-weight-semibold">
Senders Name Here
</p>
<p>2022-11-20 13:30:00</p>
<p>
Message content here. Lorem ipsum dolor sit amet
consectetur adipisicing elit. Corrupti doloremque
voluptatum velit repellendus ipsum delectus
blanditiis quis dolores. Maxime labore esse
laboriosam inventore error molestiae consequuntur
quo, deleniti ea nihil.
</p>
</div>
</div>
</div>
<div class="cell">
<div class="is-flex is-align-items-center">
<div class="checkbox mr-6">
<input type="checkbox" />
</div>
<div
class="is-flex is-flex-direction-column is-align-items-start"
>
<p class="has-text-weight-semibold">
Senders Name Here
</p>
<p>2022-11-20 13:30:00</p>
<p>
Message content here. Lorem ipsum dolor sit amet
consectetur adipisicing elit. Corrupti doloremque
voluptatum velit repellendus ipsum delectus
blanditiis quis dolores. Maxime labore esse
laboriosam inventore error molestiae consequuntur
quo, deleniti ea nihil.
</p>
</div>
</div>
</div>
<div class="cell">
<div class="is-flex is-align-items-center">
<div class="checkbox mr-6">
<input type="checkbox" />
</div>
<div
class="is-flex is-flex-direction-column is-align-items-start"
>
<p class="has-text-weight-semibold">
Senders Name Here
</p>
<p>2022-11-20 13:30:00</p>
<p>
Message content here. Lorem ipsum dolor sit amet
consectetur adipisicing elit. Corrupti doloremque
voluptatum velit repellendus ipsum delectus
blanditiis quis dolores. Maxime labore esse
laboriosam inventore error molestiae consequuntur
quo, deleniti ea nihil.
</p>
</div>
</div>
</div>
<div class="cell">
<div class="is-flex is-align-items-center">
<div class="checkbox mr-6">
<input type="checkbox" />
</div>
<div
class="is-flex is-flex-direction-column is-align-items-start"
>
<p class="has-text-weight-semibold">
Senders Name Here
</p>
<p>2022-11-20 13:30:00</p>
<p>
Message content here. Lorem ipsum dolor sit amet
consectetur adipisicing elit. Corrupti doloremque
voluptatum velit repellendus ipsum delectus
blanditiis quis dolores. Maxime labore esse
laboriosam inventore error molestiae consequuntur
quo, deleniti ea nihil.
</p>
</div>
</div>
</div>
<div class="cell">
<div class="is-flex is-align-items-center">
<div class="checkbox mr-6">
<input type="checkbox" />
</div>
<div
class="is-flex is-flex-direction-column is-align-items-start"
>
<p class="has-text-weight-semibold">
Senders Name Here
</p>
<p>2022-11-20 13:30:00</p>
<p>
Message content here. Lorem ipsum dolor sit amet
consectetur adipisicing elit. Corrupti doloremque
voluptatum velit repellendus ipsum delectus
blanditiis quis dolores. Maxime labore esse
laboriosam inventore error molestiae consequuntur
quo, deleniti ea nihil.
</p>
</div>
</div>
</div>
<!-- This is where messages will be inserted -->
<div id="sms-container" class="grid is-gap-4 is-fullwidth">
<!-- Messages will be populated here -->
</div>
</div>
</div>
@@ -303,6 +163,7 @@
<div class="card-footer">
<a
href="#"
id="refresh-sms"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
>
Refresh
@@ -310,6 +171,7 @@
<a
href="#"
id="delete-selected-sms"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
>
Delete Selected
@@ -328,9 +190,10 @@
<div class="grid">
<div class="cell is-col-span-1">
<div class="field">
<label class="label">Receipient's Phone Number</label>
<label class="label">Recipient's Phone Number</label>
<div class="control has-icons-left">
<input
id="phone-number-input"
class="input"
type="text"
placeholder="Input Phone Number"
@@ -347,8 +210,9 @@
<label class="label">Message</label>
<div class="control">
<textarea
id="message-input"
class="textarea"
placeholder="Textarea"
placeholder="Enter your message here"
></textarea>
</div>
</div>
@@ -359,6 +223,7 @@
<div class="card-footer">
<a
href="#"
id="send-sms"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
>
Send SMS
@@ -366,6 +231,7 @@
<a
href="#"
id="reset-form"
class="card-footer-item has-text-link has-text-weight-semibold has-text-white"
>
Reset

View File

@@ -29,7 +29,7 @@ escape_json() {
JSON_RESPONSE="["
# List of AT commands to run, one by one
for COMMAND in 'AT+CGMI' 'AT+CGMM' 'AT+CGMR' 'AT+CNUM' 'AT+CIMI' 'AT+ICCID' 'AT+CGSN' 'AT+QMAP="LANIP"' 'AT+QMAP="WWAN"'; do
for COMMAND in 'AT+CGMI' 'AT+CGMM' 'AT+CGMR' 'AT+CNUM' 'AT+CIMI' 'AT+ICCID' 'AT+CGSN' 'AT+QMAP="LANIP"' 'AT+QMAP="WWAN"' 'AT+QGETCAPABILITY'; do
# Write the command to the input file
echo "$COMMAND" > "$INPUT_FILE"

View File

@@ -0,0 +1,258 @@
#!/bin/sh
# Parse POST data
read -r QUERY_STRING
# Function to urldecode
urldecode() {
echo -e "$(echo "$1" | sed 's/+/ /g;s/%\([0-9A-F][0-9A-F]\)/\\x\1/g')"
}
# Function to send AT commands silently
send_at_command() {
echo "$1" | atinout - /dev/smd7 - >/dev/null 2>&1
}
# Extract reset flags
reset_lte=$(echo "$QUERY_STRING" | grep -o 'reset_lte=[^&]*' | cut -d= -f2)
reset_5g=$(echo "$QUERY_STRING" | grep -o 'reset_5g=[^&]*' | cut -d= -f2)
# Extract LTE values from POST data
earfcn1=$(echo "$QUERY_STRING" | grep -o 'earfcn1=[^&]*' | cut -d= -f2)
pci1=$(echo "$QUERY_STRING" | grep -o 'pci1=[^&]*' | cut -d= -f2)
earfcn2=$(echo "$QUERY_STRING" | grep -o 'earfcn2=[^&]*' | cut -d= -f2)
pci2=$(echo "$QUERY_STRING" | grep -o 'pci2=[^&]*' | cut -d= -f2)
earfcn3=$(echo "$QUERY_STRING" | grep -o 'earfcn3=[^&]*' | cut -d= -f2)
pci3=$(echo "$QUERY_STRING" | grep -o 'pci3=[^&]*' | cut -d= -f2)
# Extract 5G-SA values from POST data
nrarfcn=$(echo "$QUERY_STRING" | grep -o 'nrarfcn=[^&]*' | cut -d= -f2)
nrpci=$(echo "$QUERY_STRING" | grep -o 'nrpci=[^&]*' | cut -d= -f2)
scs=$(echo "$QUERY_STRING" | grep -o 'scs=[^&]*' | cut -d= -f2)
band=$(echo "$QUERY_STRING" | grep -o 'band=[^&]*' | cut -d= -f2)
# URL decode all values
reset_lte=$(urldecode "$reset_lte")
reset_5g=$(urldecode "$reset_5g")
earfcn1=$(urldecode "$earfcn1")
pci1=$(urldecode "$pci1")
earfcn2=$(urldecode "$earfcn2")
pci2=$(urldecode "$pci2")
earfcn3=$(urldecode "$earfcn3")
pci3=$(urldecode "$pci3")
nrarfcn=$(urldecode "$nrarfcn")
nrpci=$(urldecode "$nrpci")
scs=$(urldecode "$scs")
band=$(urldecode "$band")
# Send Content-type header before any other output
echo "Content-type: application/json"
echo ""
# Handle reset requests
if [ "$reset_lte" = "1" ] || [ "$reset_5g" = "1" ]; then
# Remove configuration files
rm -f /etc/quecmanager/cell_lock_config.txt
rm -f /etc/quecmanager/apply_cell_lock.sh
# Remove from rc.local if present
sed -i '/\/etc\/quecmanager\/apply_cell_lock.sh/d' /etc/rc.local
if [ "$reset_lte" = "1" ] && [ "$reset_5g" = "1" ]; then
send_at_command "AT+QNWLOCK=\"common/4g\",0"
send_at_command "AT+QNWLOCK=\"common/5g\",0"
send_at_command "AT+QNWPREFCFG=\"mode_pref\",AUTO"
sleep 1
send_at_command "AT+COPS=2"
sleep 1
send_at_command "AT+COPS=0"
echo '{"status": "success", "message": "All cell lock configurations removed"}'
elif [ "$reset_lte" = "1" ]; then
send_at_command "AT+QNWLOCK=\"common/4g\",0"
sleep 1
send_at_command "AT+COPS=2"
sleep 1
send_at_command "AT+COPS=0"
echo '{"status": "success", "message": "LTE cell lock configuration removed"}'
else
send_at_command "AT+QNWLOCK=\"common/5g\",0"
send_at_command "AT+QNWPREFCFG=\"mode_pref\",AUTO"
sleep 1
send_at_command "AT+COPS=2"
sleep 1
send_at_command "AT+COPS=0"
echo '{"status": "success", "message": "5G cell lock configuration removed"}'
fi
exit 0
fi
# Create the directory structure if it doesn't exist
mkdir -p /etc/quecmanager /var/log/quecmanager
# Create a configuration file to store cell locking profiles
cat >/etc/quecmanager/cell_lock_config.txt <<EOF
# LTE Cell Locking Configuration
earfcn1=$earfcn1
pci1=$pci1
earfcn2=$earfcn2
pci2=$pci2
earfcn3=$earfcn3
pci3=$pci3
# 5G-SA Cell Locking Configuration
nrarfcn=$nrarfcn
nrpci=$nrpci
scs=$scs
band=$band
EOF
# Create the apply_cell_lock.sh script
cat >/etc/quecmanager/apply_cell_lock.sh <<'EOF'
#!/bin/sh
# Create log directory if it doesn't exist
LOG_DIR="/var/log/quecmanager"
mkdir -p "$LOG_DIR"
DEBUG_LOG="$LOG_DIR/cell_lock_debug.log"
# Function to log messages
log_message() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "$timestamp - $message" >> "$DEBUG_LOG"
}
# Verify required tools
if ! command -v atinout >/dev/null 2>&1; then
log_message "Error: atinout command not found"
exit 1
fi
if [ ! -c "/dev/smd7" ]; then
log_message "Error: Modem device /dev/smd7 not found"
exit 1
fi
# Function to send AT commands
send_at_command() {
local command="$1"
local description="$2"
local retry_count=0
local max_retries=3
log_message "Attempting to send command: $command ($description)"
while [ $retry_count -lt $max_retries ]; do
echo "$command" | atinout - /dev/smd7 - > /tmp/at_response.txt 2>&1
local result=$(cat /tmp/at_response.txt)
log_message "Attempt $((retry_count + 1)) - Response: $result"
if echo "$result" | grep -q "OK"; then
log_message "Command successful: $description"
return 0
fi
retry_count=$((retry_count + 1))
log_message "Command failed, retry $retry_count of $max_retries"
sleep 2
done
log_message "Command failed after $max_retries retries: $description"
return 1
}
# Function to apply cell lock configuration
apply_cell_lock() {
local config_file="/etc/quecmanager/cell_lock_config.txt"
if [ ! -f "$config_file" ]; then
log_message "Configuration file not found"
return 1
fi
# Read configuration values
. "$config_file"
# Test modem responsiveness
if ! send_at_command "AT" "Testing modem responsiveness"; then
return 1
fi
# Apply LTE configuration if present
if [ -n "$earfcn1" ] && [ -n "$pci1" ]; then
if [ -n "$earfcn2" ] && [ -n "$pci2" ]; then
if [ -n "$earfcn3" ] && [ -n "$pci3" ]; then
send_at_command "AT+QNWLOCK=\"common/4g\",3,$earfcn1,$pci1,$earfcn2,$pci2,$earfcn3,$pci3" "Setting LTE lock (3 cells)"
else
send_at_command "AT+QNWLOCK=\"common/4g\",2,$earfcn1,$pci1,$earfcn2,$pci2" "Setting LTE lock (2 cells)"
fi
else
send_at_command "AT+QNWLOCK=\"common/4g\",1,$earfcn1,$pci1" "Setting LTE lock (1 cell)"
fi
sleep 1
if ! send_at_command "AT+COPS=2" "Network Disconnected"; then
return 1
fi
sleep 1
if ! send_at_command "AT+COPS=0" "Network Reconnected"; then
return 1
fi
fi
# Apply 5G configuration if present
if [ -n "$nrpci" ] && [ -n "$nrarfcn" ] && [ -n "$scs" ] && [ -n "$band" ]; then
if ! send_at_command "AT+QNWPREFCFG=\"mode_pref\",NR5G" "Setting network to SA only"; then
return 1
fi
sleep 1
if ! send_at_command "AT+QNWLOCK=\"common/5g\",$nrpci,$nrarfcn,$scs,$band" "Setting 5G lock"; then
return 1
fi
sleep 1
if ! send_at_command "AT+COPS=2" "Network Disconnected"; then
return 1
fi
sleep 1
if ! send_at_command "AT+COPS=0" "Network Reconnected"; then
return 1
fi
fi
return 0
}
# Main execution
log_message "Starting cell lock configuration"
if apply_cell_lock; then
log_message "Cell lock configuration applied successfully"
exit 0
else
log_message "Failed to apply cell lock configuration"
exit 1
fi
EOF
# Make the script executable
chmod +x /etc/quecmanager/apply_cell_lock.sh
# Add to rc.local if not already present
if ! grep -q "/etc/quecmanager/apply_cell_lock.sh" /etc/rc.local; then
sed -i '/exit 0/i sleep 30\n\/etc\/quecmanager\/apply_cell_lock.sh' /etc/rc.local
fi
# Run the script immediately
/etc/quecmanager/apply_cell_lock.sh
result=$?
if [ $result -eq 0 ]; then
echo '{"status": "success", "message": "Cell lock configurations saved and applied successfully"}'
else
echo '{"status": "error", "message": "Cell lock configurations saved but failed to apply"}'
fi

View File

@@ -0,0 +1,60 @@
#!/bin/sh
echo "Content-type: application/json"
echo ""
CONFIG_FILE="/etc/quecmanager/cell_lock_config.txt"
if [ ! -f "$CONFIG_FILE" ]; then
echo '{"status": "error", "message": "No cell lock configurations found", "configurations": {}}'
exit 0
fi
# Function to read config values
get_config_value() {
local key=$1
local value=$(grep "^$key=" "$CONFIG_FILE" | sed "s/^$key=//")
# Remove any trailing whitespace or comments
value=$(echo "$value" | sed 's/[[:space:]]*#.*$//' | sed 's/[[:space:]]*$//')
echo "$value"
}
# Read LTE configuration values
earfcn1=$(get_config_value "earfcn1")
pci1=$(get_config_value "pci1")
earfcn2=$(get_config_value "earfcn2")
pci2=$(get_config_value "pci2")
earfcn3=$(get_config_value "earfcn3")
pci3=$(get_config_value "pci3")
# Read 5G-SA configuration values
nrarfcn=$(get_config_value "nrarfcn")
nrpci=$(get_config_value "nrpci")
scs=$(get_config_value "scs")
band=$(get_config_value "band")
# Debug output to syslog
logger "fetch-cell-lock: earfcn1=$earfcn1 pci1=$pci1 nrarfcn=$nrarfcn nrpci=$nrpci"
# Construct JSON response
cat << EOF
{
"status": "success",
"configurations": {
"lte": {
"earfcn1": "${earfcn1:-}",
"pci1": "${pci1:-}",
"earfcn2": "${earfcn2:-}",
"pci2": "${pci2:-}",
"earfcn3": "${earfcn3:-}",
"pci3": "${pci3:-}"
},
"sa": {
"nrarfcn": "${nrarfcn:-}",
"nrpci": "${nrpci:-}",
"scs": "${scs:-}",
"band": "${band:-}"
}
}
}
EOF

View File

@@ -0,0 +1,120 @@
#!/bin/sh
# Enable debug logging
# exec 1> >(logger -s -t $(basename $0)) 2>&1
# Create log directory if it doesn't exist
LOG_DIR="/var/log/quecmanager"
mkdir -p "$LOG_DIR"
DEBUG_LOG="$LOG_DIR/cell_lock_debug.log"
# Function to log messages
log_message() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "$timestamp - $message" >> "$DEBUG_LOG"
}
# Verify required tools
if ! command -v atinout >/dev/null 2>&1; then
log_message "Error: atinout command not found"
exit 1
fi
if [ ! -c "/dev/smd11" ]; then
log_message "Error: Modem device /dev/smd11 not found"
exit 1
fi
# Function to send AT commands
send_at_command() {
local command="$1"
local description="$2"
local retry_count=0
local max_retries=3
log_message "Attempting to send command: $command ($description)"
while [ $retry_count -lt $max_retries ]; do
echo "$command" | atinout - /dev/smd11 - > /tmp/at_response.txt 2>&1
local result=$(cat /tmp/at_response.txt)
log_message "Attempt $((retry_count + 1)) - Response: $result"
if echo "$result" | grep -q "OK"; then
log_message "Command successful: $description"
return 0
fi
retry_count=$((retry_count + 1))
log_message "Command failed, retry $retry_count of $max_retries"
sleep 2
done
log_message "Command failed after $max_retries retries: $description"
return 1
}
# Function to apply cell lock configuration
apply_cell_lock() {
local config_file="/etc/quecmanager/cell_lock_config.txt"
if [ ! -f "$config_file" ]; then
log_message "Configuration file not found"
return 1
fi
# Read configuration values
. "$config_file"
# Test modem responsiveness
if ! send_at_command "AT" "Testing modem responsiveness"; then
return 1
fi
# Apply LTE configuration if present
if [ -n "$earfcn1" ] && [ -n "$pci1" ]; then
if ! send_at_command "AT+CFUN=0" "Setting radio off"; then
return 1
fi
sleep 2
if [ -n "$earfcn2" ] && [ -n "$pci2" ]; then
if [ -n "$earfcn3" ] && [ -n "$pci3" ]; then
send_at_command "AT+QNWLOCK=\"common/4g\",3,$earfcn1,$pci1,$earfcn2,$pci2,$earfcn3,$pci3" "Setting LTE lock (3 cells)"
else
send_at_command "AT+QNWLOCK=\"common/4g\",2,$earfcn1,$pci1,$earfcn2,$pci2" "Setting LTE lock (2 cells)"
fi
else
send_at_command "AT+QNWLOCK=\"common/4g\",1,$earfcn1,$pci1" "Setting LTE lock (1 cell)"
fi
sleep 2
if ! send_at_command "AT+CFUN=1" "Setting radio on"; then
return 1
fi
fi
# Apply 5G configuration if present
if [ -n "$nrpci" ] && [ -n "$nrarfcn" ] && [ -n "$scs" ] && [ -n "$band" ]; then
if ! send_at_command "AT+CFUN=0" "Setting radio off"; then
return 1
fi
sleep 2
if ! send_at_command "AT+QNWLOCK=\"common/5g\",$nrpci,$nrarfcn,$scs,$band" "Setting 5G lock"; then
return 1
fi
sleep 2
if ! send_at_command "AT+CFUN=1" "Setting radio on"; then
return 1
fi
fi
}
# Main execution
log_message "Starting cell lock configuration"
if apply_cell_lock; then
log_message "Cell lock configuration applied successfully"
exit 0
else
log_message "Failed to apply cell lock configuration"
exit 1
fi

View File

@@ -29,7 +29,7 @@ escape_json() {
JSON_RESPONSE="["
# List of AT commands to run, one by one
for COMMAND in 'AT+CGDCONT?' 'AT+QNWPREFCFG="mode_pref"' 'AT+QNWPREFCFG="nr5g_disable_mode"'; do
for COMMAND in 'AT+CGDCONT?' 'AT+QNWPREFCFG="mode_pref"' 'AT+QNWPREFCFG="nr5g_disable_mode"' 'AT+QUIMSLOT?'; do
# Write the command to the input file
echo "$COMMAND" > "$INPUT_FILE"

View File

@@ -1,7 +1,7 @@
/* */
/* import Poppins font */
@import url("https://fonts.googleapis.com/css2?family=Euclid+Flex:wght@400;500;600;700&display=swap");
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
/* use Poppins everywhere */
* {
@@ -331,4 +331,24 @@ html.theme-light {
@keyframes spinAround {
from { transform: rotate(0deg); }
to { transform: rotate(359deg); }
}
.loading-container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 1rem;
height: 200px;
width: 100%;
}
.loading-container .icon {
color: #3273dc;
}
.loading-container p {
color: #4a4a4a;
font-size: 1.2rem;
margin-top: 1rem;
}

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) {