- Python 84.3%
- Roff 12.9%
- Makefile 2.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The new device (firmware MX1SW1.0_10F_FB_DANENG_SL_V01.01.02P42U28_06) serves a ZTE-style web UI with a JSON API under /reqproc instead of Marvell duster's xml_action.cgi, so login failed with "unexpected login challenge". Split the client into Duster and Zte backends behind the same set of operations, auto-detected by probing /reqproc/proc_get (override with --firmware / MIFI_FIRMWARE). raw accepts field lists and goform key=value params on zte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| .gitignore | ||
| Makefile | ||
| mifi.1 | ||
| mifi.py | ||
| README.md | ||
mifi
A dependency-free command-line client to monitor and control a portable 4G MiFi router. Two firmware families are supported and auto-detected:
| Firmware | Web API | Tested on |
|---|---|---|
Marvell "duster" ("4G Wireless Router MIFI", HTTP-Digest realm Highwmg) |
XML-over-CGI at /xml_action.cgi |
U MOBILE LV01 (PXA1802, SDK067_CW_M03) |
ZTE-style goform UI (server Demo-Webs, "4G Hostless Modem") |
JSON at /reqproc/proc_get / /reqproc/proc_post |
MX1SW1.0_10F_FB_DANENG_SL_V01.01.02P42U28_06 |
Other devices using either web UI should work too. Pure Python 3 standard
library — no pip install, nothing to build.
Install
make install # -> ~/.local/bin/mifi (no root needed)
make uninstall
Installs to your home-local directory by default. For a system-wide install:
make install PREFIX=/usr/local # may need sudo
If ~/.local/bin isn't on your PATH, the installer prints the line to add.
You can also just run the script in place: ./mifi.py <command>.
Quick start
mifi status # signal, network, battery, data usage
mifi battery # battery gauge
mifi watch # live-refreshing dashboard
mifi sms list inbox # read texts
mifi sms send +60123456789 "hi" # send a text
Configuration
Credentials default to admin / admin. Override per-invocation or via the
environment:
| Setting | Flag | Env var | Default |
|---|---|---|---|
| Host/IP | --host |
MIFI_HOST |
192.168.0.1 |
| Username | --user |
MIFI_USER |
admin |
| Password | --password |
MIFI_PASS |
admin |
| Firmware | --firmware |
MIFI_FIRMWARE |
auto (duster or zte to force) |
export MIFI_PASS='s3cret'
mifi status
Note: the credentials are the same ones you use to log into the router's web panel. The password is often printed on a sticker under the battery. ZTE-style devices have no username, so
--useris ignored there. They also lock the web login after 5 wrong passwords;mifireports how many attempts remain when a login fails.
Commands
| Command | What it does |
|---|---|
status |
One-shot summary: network, radio/band, WAN IP, signal (RSSI/RSRP/RSRQ/SINR), battery, connected clients, unread SMS, and data usage. |
battery |
Battery level as a gauge bar, plus charging state. |
watch [--interval N] |
Clears the screen and re-renders status every N seconds (default 3), with a battery gauge in the header. |
connect / disconnect |
Bring the cellular WAN up or down. |
reboot [-y] |
Reboot the router (prompts unless -y). |
sms list [inbox|sent|draft] [--page N] |
List stored messages. Bodies are UTF-16 decoded; unread are marked *. |
sms send <number> <text> |
Send an SMS. |
raw get <file> / raw set <file> [--stdin] |
duster: call any XML endpoint the web UI uses (wan, statistics, message, lan, …). Handy for exploring. |
raw get <f1,f2,…> / raw set <goformId> [key=value …] |
zte: read any proc_get fields as JSON, or POST any goform command. |
JSON output
Every read command (and the action commands) accept --json for scripting,
with --compact for single-line output:
mifi status --json | jq '{net:.network, dbm:.rsrp, batt:.battery_pct}'
mifi battery --json # {"level_pct":97,"charging":true,...}
mifi sms list inbox --json | jq length
mifi watch --json --interval 10 # newline-delimited JSON, one per tick
Numbers come back as JSON numbers, booleans as booleans, and byte counters as
raw integers (do your own formatting). Errors are emitted as
{"error": "..."} on stdout with a non-zero exit code.
How it works
These devices don't speak standard HTTP auth: the server answers every
request with 200 OK plus a WWW-Authenticate header and an UNAUTHORIZED
body, until a request carries a valid custom digest header (with the quirk that
the digest URI is always the literal /cgi/xml_action.cgi). mifi reproduces
the firmware's two-step login.cgi handshake, then issues XML GET/POST calls
to xml_action.cgi. See the module docstring in mifi.py for the gory detail.
ZTE-style devices are simpler: mifi POSTs goformId=LOGIN with the
base64'd password to /reqproc/proc_post (keeping the session cookie), reads
status as JSON from /reqproc/proc_get?cmd=a,b,c&multi_data=1, and issues
actions such as CONNECT_NETWORK, REBOOT_DEVICE and SEND_SMS as goform
POSTs.
Auto-detection probes /reqproc/proc_get once per run; if it answers with
JSON the device is treated as ZTE-style, otherwise as duster.
The device tends to allow one session at a time, so if the web UI is logged in elsewhere the CLI may occasionally get bumped — just re-run.
Files
| Path | Purpose |
|---|---|
mifi.py |
The client (also the installed mifi). |
Makefile |
install / uninstall / check. |
mifi.1 |
Man page (make install also installs it). |
Caveats
- Signal metrics (RSSI/RSRP/RSRQ/SINR) are the raw values the firmware reports; scaling varies by model.
Battery_voltageis the firmware's misnomer for the charge level (0–100), which is whatbatteryshows.- ZTE-style firmware may report no battery percentage at all, only the coarse
icon level (
battery_pers);batterythen shows that level instead. - On ZTE-style firmware,
sms listreads the device's message store (as the web UI does), 20 messages per--page. connect,disconnect,reboot, andsms sendchange live device state — use with care.
License
Provided as-is, no warranty. Do what you like with it.