Passive LoRa and sub-GHz field logging
RX-only LoRa/sub-GHz wardriving firmware for the M5Stack Cap LoRa-1262 (SX1262) riding on a Cardputer-Adv (ESP32-S3). GPS-tagged detection logging across four mission profiles — Meshtastic, MeshCore, Reticulum, and general LoRa/spectrum exploration — built like a field instrument, not a laptop-tethered tool.
Note
Receive-only, by design. LoRaTrace has no transmit path beyond what
the antenna-switch init requires, and never will (CLAUDE.md house
rule). It observes and GPS-tags radio activity already in the air —
including other people's mesh traffic — for later analysis. It does not
inject, transmit, or decrypt anything not already in the clear. Operate
it the way you'd operate any RF-monitoring instrument: know your local
regulations and respect reasonable expectations of privacy.
- Features
- Version
- Documentation
- Build
- Install without flashing (M5Launcher)
- Configuration
- Output files
- Display and controls
- Web dashboard
- Four mission profiles, one receiver: Meshtastic, MeshCore, Reticulum,
and General Exploration (
Spectrumon-device) — presets, not separate tools. - Three radio modes, all GPS-tagged and logged to SD: Watch (continuous single-channel RX), Probe (bounded discovery scan across the full 868–923MHz band), and Sweep (frequency-binned energy map, with CAD confirmation at peaks landing in Phase 9).
- GPS-stamped CSV logging, one directory per wardrive — see Output files.
- On-device menu UI on the Cardputer-Adv's own screen and keyboard — no laptop required in the field. See Display and controls.
- On-demand WiFi dashboard for live status, per-run CSV download, and channel configuration — off by default, never running unattended unless toggled on. See Web dashboard.
- No PSRAM, no problem. Runs entirely within the ESP32-S3FN8's static RAM budget — SD is the datastore, not RAM. See docs/DESIGN.md.
No tagged vX.Y.Z release has shipped yet — main builds a rolling
dev-latest
prerelease on every merge (see Install
below), which is what the dev-latest badge above tracks. The firmware's
own semantic version lives in src/version.h and is bumped by hand when a
build-order phase lands — see
docs/STATUS.md for the current version, what's
hardware-verified, and what's still open, rather than a number restated
here that can drift out of sync.
| Doc | For |
|---|---|
| docs/STATUS.md | Current version, what's hardware-verified, what's still open. Start here. |
| docs/DESIGN.md | Hardware, RF parameters, and architecture rationale — read before making architecture changes. |
| docs/ROADMAP.md | Build-order phases, MVP-Beta scope, and an honest feasibility read against this hardware's real limits. |
| docs/LOG_GUIDE.md | Operator guide to run folders, CSV fields, identity observations, health checks, and privacy-aware export. |
| docs/HARDWARE_TESTING.md | Repeatable device-validation matrix and Phase 7 memory acceptance rules. |
| docs/BRAND.md | Naming, tone, and on-device UI copy conventions. |
| CLAUDE.md | House rules for AI-assisted development on this repo. |
| docs/README.md | Full documentation index, including archived history and research notes. |
PlatformIO + Arduino framework:
pio run -e cardputer-adv
pio run -e cardputer-adv --target upload
pio device monitorUnit tests (host-native, no board needed):
pio test -e nativeIf your Cardputer-Adv already runs bmorcelli/Launcher, you don't need to touch USB flashing at all:
- Download the latest build:
https://github.com/d3mocide/LoRaTrace-RX/releases/download/dev-latest/LoRaTraceRX-dev.bin(rebuilt automatically frommainon every merge — check that release's notes for the commit it came from). Tagged, more-stable versions will appear on the Releases page once one has shipped. - Copy the
.binonto a FAT32 SD card. - In Launcher: SD → select the file → Install.
- To get back to Launcher afterward: reset the device and press any key
during Launcher's own ~5s boot window (it prints "Press the button to
enter the Launcher!" over serial while waiting) — miss it and Launcher
auto-boots straight back into whatever ran last. To skip that timing
window entirely, enable Launcher's own Settings → "Boot to Launcher"
toggle; it then always stops at its menu on reset until you turn it
back off. Not a software hook this firmware implements — see
docs/history/PROGRESS.mdfor how this was confirmed against Launcher's own source.
Serial output still works normally over USB while running under Launcher
— pio device monitor to watch the boot banner and any [RX]/[config]
lines.
By default LoRaTrace RX locks to Meshtastic LongFast (US):
906.875MHz / SF11 / BW250kHz / CR4:8, and MeshCore's US-narrow default:
910.525MHz / SF7 / BW62.5kHz / CR5. The first time it boots with an SD
card that doesn't already have one, it creates /loratrace/config.txt on
the card pre-filled with both defaults, one block per profile
(meshtastic_freq_mhz=, meshcore_freq_mhz=, etc. — each profile has its
own independent set of keys, so editing one never touches the other's
saved values). Edit either block in place for a non-default regional
preset (e.g. MeshOregon) and reboot — see the file's own comments for the
format, or use the web dashboard's Settings tab instead of hand-editing
(below). (sd-template/loratrace/ still exists if you want to prepare a
card offline before ever booting the device with it.) A missing card, a
read-only card, or out-of-range values all fail safe back to the hardcoded
default for that profile. Channel changes apply on next boot, not live —
switching profiles on the running device (menu or web) always uses
whatever was last saved for that profile.
One wardrive is one directory. Each power-on claims the next free
/loratrace/runNNNN/ on the card, so a drive can be copied, shared or
deleted as a unit:
/loratrace/
config.txt channel override (not a run artifact)
run0001/
detections.csv
nodes.csv
session.csv
probe.csv
energy.csv
run0002/
...
detections.csv— one row per received packet: GPS-stamped, with RSSI/SNR, RF parameters and whatever routing metadata the protocol exposes in clear. The full frame is retained as hex for offline analysis; LoRaTrace does not claim a general payload decoder.session.csv— one health row a minute (packets, drops, worst SD bus hold, heap free and low-water, GPS state, battery), plus a row marking the start. An unattended run is judged on whether it held up, and nobody is watching the serial console at hour three — so the run records its own vital signs next to its findings.nodes.csv— supported Meshtastic NodeInfo and MeshCore advertisement identity observations, kept separate because many packets can belong to one node. Seedocs/LOG_GUIDE.mdfor the exact supported profiles and limits.probe.csv— written when a Probe (discovery scan) is run: which fixed-candidate channels produced CAD activity. A CAD hit is not necessarily a packet.energy.csv— written when a Sweep (energy scan) is run: sparse high-energy bins and follow-up CAD results. Not a full spectrum recording.
probe.csv and energy.csv may exist with only a header row if that
mode was never triggered during the run — their absence of data is not an
error. See docs/LOG_GUIDE.md for the full field-by-field schema of every
file above.
Runs are numbered rather than timestamped because the name has to be chosen
before the GPS knows what time it is, and this board has no verified RTC.
The wall clock still reaches the card, recorded inside the run once a fix
lands. docs/LOG_GUIDE.md is the operator reference for schemas and analysis;
docs/DESIGN.md §8 explains the design rationale. The current run number is shown
on the RADIO page as r<N>.
Boot progress (firmware version, antenna-switch/radio status, active channel, and any FATAL error) is shown on the built-in LCD as well as over serial. Once the tasks are running the panel shows four read-only status pages — RADIO, CHANNEL, GPS, SYSTEM — with a battery indicator and header status dots (GPS fix, heap health, live RX activity) on every one, plus a footer line showing the active profile and page position.
A keyboard-driven, nestable menu covers every operator-facing toggle, three
root rows deep: Trace (pause/standby the radio-listening pipeline
without losing GPS fix), Profile (Meshtastic / MeshCore / Node IDs
capture), and System (WiFi and Serial Control under Connectivity,
Debug and SD retry under Diagnostics, brightness and idle-dim under
Display). ,/. cycle pages or move the current menu level's selection,
digits 1-5 jump straight to a page, Enter acts on the highlighted row
(or scrubs a slider), and the backtick/ESC key opens/closes the menu and
steps back up one level at a time; with no keyboard detected the pages
rotate on their own, so a device sitting on a dashboard still cycles
through everything. A toast band confirms whatever action just fired.
An on-demand WiFi access point (off by default — toggle it from the
System menu, never running unattended during a drive unless you turn it
on) hosts a small embedded web page at 192.168.4.1 once it's up. Three
tabs: a live status dashboard (the same counters the on-device SYSTEM/
RADIO pages and serial [status] line already expose, plus whether Trace
is active or paused), a run browser to download any of detections.csv/
session.csv/nodes.csv/probe.csv/energy.csv per run without ejecting
the SD card, and a Settings tab with
one independent panel per profile for editing channel parameters — the
same config.txt the SD card holds, applied on next boot. Measured cost:
roughly 55-60KB of heap while the AP is up, with no impact on radio/GPS/SD
reliability under real traffic — see docs/history/PROGRESS.md for the
measurement session, or docs/STATUS.md for what's still open.