Last active
June 3, 2024 21:42
-
-
Save andrewmackrodt/71aab86065773abd05a22f8dcf07a74e to your computer and use it in GitHub Desktop.
GL-X3000 AT Command Tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width,initial-scale=1"> | |
<title>GL-X3000 AT Command Tool</title> | |
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css"> | |
<style type="text/css"> | |
body { | |
max-width: 800px; | |
margin: 0 auto; | |
padding: 2rem; | |
} | |
code { | |
white-space: normal; | |
} | |
pre { | |
padding: 2rem; | |
} | |
</style> | |
<script> | |
window.config = { | |
auth: { | |
password: 'PUT_YOUR_PASSWORD_HERE', | |
sid: null, | |
username: 'root', | |
}, | |
modem: { | |
bus: '0001:01:00.0', | |
port: '/dev/mhi_DUN', | |
}, | |
router: { | |
ip: '192.168.8.1', | |
}, | |
} | |
window.module = { | |
exports: { }, | |
} | |
</script> | |
</head> | |
<body> | |
<h1>GL-X3000 AT Command Tool</h1> | |
<p> | |
This page must be run in a browser where CORS has been disabled or requests to the router will fail. A temporary | |
browser profile can be used with CORS disabled, e.g. for Google Chrome run: | |
<code>'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' --user-data-dir="$(mktemp -d)" --disable-web-security</code> | |
</p> | |
<!-- | |
<h3 class="title">Router</h3> | |
<fieldset> | |
<div> | |
<label for="shortcut_select"><span><b>IP Address</b></span></label> | |
<input type="text" name="ipaddr" id="ipaddr_input" value="192.168.8.1"> | |
</div> | |
<div> | |
<label for="shortcut_select"><span><b>Username</b></span></label> | |
<input type="text" name="username" id="username_input" value="root"> | |
</div> | |
<div> | |
<label for="shortcut_select"><span><b>Password</b></span></label> | |
<input type="password" name="password" id="password_input"> | |
</div> | |
</fieldset> | |
<h3 class="title">Command</h3> | |
--> | |
<form> | |
<fieldset> | |
<div> | |
<label for="shortcut_select"><span><b>Shortcut</b></span></label> | |
<select name="shortcut_select" id="shortcut_select"> | |
<!-- Default commands --> | |
<option value='AT+GSN' selected>Request IMEI</option> | |
<option value='AT+QCCID'>Request QCCID</option> | |
<option value='AT+CIMI'>Request IMSI</option> | |
<option value='AT+CSQ'>Check Signal Quality</option> | |
<option value='AT&F0'>Reset modem</option> | |
<option value='AT+COPS?'>Operator Names</option> | |
<option value='AT+CPIN?'>Request SIM card status</option> | |
<!-- Custom commands: AT+QENG --> | |
<option value='AT+QENG="servingcell"'>Get Serving Cell</option> | |
<option value='AT+QENG="neighbourcell"'>Get Neighbour Cell</option> | |
<!-- Custom commands: AT+QNWLOCK --> | |
<option value='AT+QNWLOCK="common/4g"'>Get 4G Cell Lock</option> | |
<option value='AT+QNWLOCK="common/5g"'>Get 5G Cell Lock</option> | |
<!-- Custom commands: AT+QNWPREFCFG --> | |
<option value='AT+QNWPREFCFG="mode_pref"'>Get mode pref</option> | |
<option value='AT+QNWPREFCFG="nr5g_disable_mode"'>Get NR5G disable mode</option> | |
</select> | |
<label for="command_input"><span><b>AT Command</b></span></label> | |
<input type="text" name="command_input" id="command_input"> | |
</div> | |
<div> | |
<button type="submit" id="command_submit"><span>Send Command</span></button> | |
</div> | |
</fieldset> | |
</form> | |
<pre id="command_output"></pre> | |
<script type="module"> | |
import 'https://cdn.jsdelivr.net/npm/[email protected]/md5.min.js' | |
const { crypt, fromBytes } = window.module.exports | |
let id = 1 | |
async function rpc(method, params) { | |
return await new Promise((resolve, reject) => { | |
fetch(`http://${window.config.router.ip}/rpc`, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
jsonrpc: '2.0', | |
id: id++, | |
method, | |
params, | |
}) | |
}) | |
.then(async res => { | |
const text = await res.text() | |
const json = JSON.parse(text) | |
resolve(json.result) | |
}) | |
.catch(err => reject(err)) | |
}) | |
} | |
async function getChallenge() { | |
return await rpc('challenge', { username: 'root' }) | |
} | |
async function doLogin() { | |
const challenge = await getChallenge() | |
const login = await rpc('login', { | |
hash: fromBytes( | |
[ | |
window.config.auth.username, | |
crypt(window.config.auth.password, challenge.salt), | |
challenge.nonce, | |
].join(':') | |
).toHex(), | |
username: window.config.auth.username, | |
}) | |
window.config.auth.sid = login.sid | |
console.log(`logged in as username ${window.config.auth.username} sid ${login.sid}`) | |
} | |
async function doLoginCached() { | |
if ( ! window.config.auth.sid) { | |
await doLogin() | |
} | |
} | |
async function doSendAtCommand(command) { | |
await doLoginCached() | |
const res = await rpc('call', [ | |
window.config.auth.sid, | |
'modem', | |
'send_at_command', | |
{ | |
bus: window.config.modem.bus, | |
port: window.config.modem.port, | |
command, | |
} | |
]) | |
let response = res | |
let status = '-' | |
if (response !== null && typeof response === 'object' && 'response' in response) { | |
response = res.response | |
} | |
if (typeof response === 'string') { | |
response = res.response.trim().split('\r\n') | |
status = response.pop() | |
response = response.join('\r\n').trim() | |
} | |
if (status !== 'OK') { | |
throw new Error(`AT command failed: ${status} - ${response}`) | |
} | |
return response | |
} | |
const shortcutSelectElement = document.getElementById('shortcut_select') | |
const commandInputElement = document.getElementById('command_input') | |
const commandSubmitElement = document.getElementById('command_submit') | |
const outputElement = document.getElementById('command_output') | |
shortcutSelectElement.addEventListener('change', e => commandInputElement.value = e.target.value) | |
shortcutSelectElement.dispatchEvent(new Event('change')) | |
commandSubmitElement.addEventListener('click', async e => { | |
e.preventDefault() | |
const command = commandInputElement.value.trim() | |
if ( ! command || command.match(/^AT\+[A-Z]/i) === null) { | |
alert('ERROR: command must begin AT+ and contain at least one character') | |
return | |
} | |
try { | |
outputElement.innerText = await doSendAtCommand(command) | |
} catch (e) { | |
alert(`ERROR: ${e}`) | |
} | |
}) | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
cd "$(cd "$(dirname "$(readlink -f "$0")")" && pwd -P)" | |
declare -a chromeArgs=( | |
--disable-back-forward-cache | |
--disable-background-networking | |
--disable-background-timer-throttling | |
--disable-backgrounding-occluded-windows | |
--disable-breakpad | |
--disable-client-side-phishing-detection | |
--disable-component-extensions-with-background-pages | |
--disable-component-update | |
--disable-default-apps | |
--disable-dev-shm-usage | |
--disable-extensions | |
--disable-features="ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,HttpsUpgrades,PaintHolding" | |
--disable-field-trial-config | |
--disable-hang-monitor | |
--disable-ipc-flooding-protection | |
--disable-popup-blocking | |
--disable-prompt-on-repost | |
--disable-renderer-backgrounding | |
--disable-search-engine-choice-screen | |
--disable-web-security | |
--new-window | |
--no-default-browser-check | |
--no-first-run | |
--no-service-autorun | |
--password-store="basic" | |
--test-type | |
--unsafely-disable-devtools-self-xss-warnings | |
--use-mock-keychain ) | |
userDataDir=$(mktemp -d) | |
if [[ "$userDataDir" == "" ]] || [[ ! -d "$userDataDir" ]]; then | |
echo "ERROR unable to create temporary directory" >&2 | |
exit 1 | |
fi | |
cleanup() { | |
rm -rf "$userDataDir" | |
} | |
trap cleanup exit | |
chromeArgs+=( --user-data-dir="$userDataDir" ) | |
chromeArgs+=( --app="file://$PWD/glx3000-at-tool.html" ) | |
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' "${chromeArgs[@]}" >/dev/null 2>&1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment