This repo contains a Python script that connects to a cellular modem over a serial port and runs AT commands to verify modem, SIM, and network status. It supports a test/diagnostics mode and a wardriving mode that logs periodic snapshots.
- Python 3.8+
pyserialpyyaml(only required when loading a YAML config file)
Install dependencies:
pip install pyserial pyyamlpython hack-wanderer.py --port /dev/cu.SLAB_USBtoUART --mode testWith a config file:
python hack-wanderer.py --config config.example.yamlWrite full results to JSON:
python hack-wanderer.py --config config.example.yaml --output-json results.jsonBy default, a log file is created under logs/ for each run.
Wardriving mode (JSONL):
python hack-wanderer.py --config config.example.yaml --mode wardriveAll settings can be provided via CLI flags or a YAML file. CLI flags override the YAML values.
Set output.json_path in YAML to always write JSON results without a CLI flag.
Example config (config.example.yaml):
mode: test
serial:
port: /dev/cu.SLAB_USBtoUART
baudrate: 115200
timeout_s: 1.0
write_timeout_s: 1.0
init_delay_s: 0.5
inter_command_delay_s: 0.1
init_retries: 3
retry_delay_s: 0.5
timeouts:
default_s: 4.0
operator_scan_s: 120.0
gps_s: 6.0
sim_read_s: 4.0
vendor_s: 5.0
features:
operator_scan: false
gps: true
vendor_specific: true
sim_read: true
auto_register: true
external_gps:
enabled: true
port: /dev/ttyACM0
baudrate: 9600
timeout_s: 0.5
read_duration_s: 2.0
max_lines: 200
sim:
pin: ""
pin_env_key: SIM_PIN
env_file: .env
wardrive:
interval_s: 5.0
duration_s: 60.0
jsonl_path: hack-wanderer.jsonl
wigle_csv_path: ""
sim_read:
files:
- name: iccid
file_id: "2FE2"
length: 10
- name: spn
file_id: "6F46"
length: 17
- name: ad
file_id: "6FAD"
length: 4
extra_commands: []
output:
raw: false
json_path: ""
status_page:
json_path: status/status.json
logging:
enabled: true
dir: logs
file: ""
file_level: debug
console_level: info
ui:
color: true
emoji: true
interactive: falseAn external NMEA GPS receiver (defaults to /dev/ttyACM0) is sampled alongside the modem's LTE/GNSS. Configure it under external_gps or via --gps-device-* flags, or disable with --no-gps-device.
- Modem info: manufacturer, model, firmware revision, IMEI
- SIM info: SIM status, ICCID, IMSI, basic SIM file reads via
AT+CRSM - SIM PIN state and optional unlock using a configured PIN
- Network status: signal quality, registration status (2G/3G/4G), current operator
- Operator scan: available nearby operators (
AT+COPS=?) - Optional vendor-specific data (if supported): band, serving cell, neighbor cell
- Optional GPS info (if supported)
- Error diagnostics (
AT+CEER)
AT+COPS=?can take a long time. It is disabled by default; enable with--operator-scan.- SIM file reads are limited to a small list of common EF files. Full SIM exploration requires APDU workflows (e.g.,
AT+CSIM), which are not implemented yet. - GPS commands are queried only (no power-on commands are issued).
- If you see
SIM PINorSIM PUK, the SIM is locked or blocked. The tool only reports it. - Raspberry Pi + SIM7600: to expose the USB Ethernet interface and AT command ports, set the USB composition once with
AT+CUSBPIDSWITCH=9011,1,1(run via UART or an existing AT port, then reboot the modem). This switches the modem into a composite mode that presents ECM/NCM networking plus AT serial devices.
- Every run writes a log file (defaults to
logs/hack-wanderer_YYYYmmdd_HHMMSS.log). - Console output is verbose and colored by default; disable with
--no-coloror--no-emoji. - Use step-by-step mode to prompt before each phase:
python hack-wanderer.py --config config.example.yaml --interactive- For full command/response detail on the console:
python hack-wanderer.py --config config.example.yaml --console-level debug- SIM PIN values are redacted in logs and console output.
- SIM PIN can be loaded from
.envviaSIM_PIN(recommended to avoid committing secrets). - Wardriving mode writes JSONL snapshots with timestamps and a
towersarray built from registration + vendor cell info; setduration_sto0to run forever. - Wigle CSV export is a draft format for now and may need adjustments for cell tower uploads.
Run python hack-wanderer.py --help for the full list. Key flags:
--port,--baudrate--operator-scan/--no-operator-scan--gps/--no-gps--gps-device/--no-gps-device,--gps-device-port,--gps-device-baudrate,--gps-device-timeout-s,--gps-device-read-duration-s--vendor-specific/--no-vendor-specific--auto-register/--no-auto-register--sim-read/--no-sim-read--sim-pinto provide a SIM PIN if required--sim-read-fileto add SIM EF reads (format:name,file_id,length)--clear-sim-read-filesto reset the SIM file list--extra-commandto add extra AT commands--output-jsonto save full results--output-rawto print raw command logs--duration-s,--interval-sfor wardriving mode--jsonl-pathto override the wardriving JSONL file--wigle-csvto write a draft Wigle CSV--log-dir,--log-file,--log-level,--console-level--no-logto disable log files--color/--no-color,--emoji/--no-emoji--interactive/--no-interactive
- Multiple QENG passes per interval (serving + neighbor) with longer timeouts/retries, configurable under
tower_scan(passes, qeng_timeout_s, qeng_retries, dwell_s, operator_scan_each_loop, detach_before_scan). This helps surface weak/ephemeral cells. - Optional operator scan each loop is available but slow; leave it off unless you need PLMN refresh.
- Optional detach/reattach before scans can provoke reselection but may interrupt service; default is off.
- If the USB/serial link to the modem drops, wardrive now auto-closes and reopens the port, re-initializes, and resumes snapshots instead of requiring a manual unplug.
- The wardrive loop writes a live snapshot to
status/status.jsonand a matching display atstatus/index.html(auto-refresh every 3s). - Serve it locally or open directly with a browser:
chromium-browser file:///home/pi/code/hack-wanderer/status/index.html. - To auto-open on boot (kiosk on :0, adjust if your user/desktop differ):
This starts a tiny local web server on
sudo cp /home/pi/code/hack-wanderer/hack-wanderer-status-http.service /etc/systemd/system/hack-wanderer-status-http.service sudo cp /home/pi/code/hack-wanderer/hack-wanderer-display.service /etc/systemd/system/hack-wanderer-display.service sudo systemctl daemon-reload sudo systemctl enable --now hack-wanderer-status-http.service sudo systemctl enable --now hack-wanderer-display.service
http://127.0.0.1:8800/and opens it in kiosk mode. It assumeschromium-browseris installed,DISPLAY=:0, and.Xauthorityis in/home/pi.
- Adjust
config.yaml(or CLI flags in the service file) somodeiswardriveandduration_sis0(run forever). - Copy the unit file into place and enable it:
sudo cp /home/pi/code/hack-wanderer/hack-wanderer.service /etc/systemd/system/hack-wanderer.service sudo systemctl daemon-reload sudo systemctl enable hack-wanderer.service sudo systemctl start hack-wanderer.service - Logs go to
logs/service.logandlogs/service.err(plus the normal run logs). Check status withsudo systemctl status hack-wanderer.service.