Each power-on creates one wardrive under /loratrace/runNNNN/. Treat that
directory as the unit of collection: copy or archive the whole folder, not
individual CSV rows. The run number is chosen before GPS time is available,
so it is an index rather than a date.
detections.csv, nodes.csv, and the optional scan files can contain precise
GPS coordinates, radio identifiers, names, public keys, and raw frames. Keep
the original private unless every affected operator has agreed to publication.
For a public summary, remove lat, lon, timestamp_utc, raw_packet_hex,
and identity fields first; do not rely on rounding alone to anonymize a route.
Always use each file's header rather than a fixed column position. Schemas are append-only where practical, and older firmware runs may have fewer fields.
| File | Written when | What it answers |
|---|---|---|
detections.csv |
Every received packet | What was heard, where/when, and RF quality. |
nodes.csv |
A supported identity advertisement/NodeInfo is decoded | Which identifiable nodes were observed. |
session.csv |
Boot and once per minute | Did the receiver, GPS, queue, and SD logger stay healthy? |
probe.csv |
A Probe is run | Which fixed-candidate channels produced CAD activity. A CAD hit is not necessarily a packet. |
energy.csv |
An Energy Sweep is run | Sparse high-energy bins and follow-up CAD results. Not a full spectrum recording. |
focus.csv |
A Focus survey is run | How one frequency bin behaved over a longer dwell than a Sweep gives it. Reports what was observed; it does not conclude that anything transmitted. |
probe.csv, energy.csv and focus.csv may exist with only a header when the
feature was not used. Their absence is not an error on older runs.
timestamp_utc is the GPS UTC time at logger dequeue. It is blank before the
receiver has both a usable position and date/time. rx_uptime_ms is the
device uptime at radio reception and is the reliable ordering key during that
startup window.
lat and lon are blank rather than 0,0 when there was no fresh position.
fix_quality is the GPS receiver's GGA fix-quality value: 0 means no fix;
1 is a normal GPS fix; 2 is DGPS. run repeats the directory number so
multiple runs can be combined safely.
energy.csv's rssi_avg_dbm and rssi_peak_dbm read about 2.4 dB lower
from v1.0.2 onward than they did before it. That release replaced Sweep's
per-bin full radio.begin() with a lighter retune, which measurably shifts
the reported noise floor: measured at -120.30 dBm before and -122.70 dBm
after, reproduced identically across five independent bench runs
(docs/research/2026-09-04-project-audit.md, M6).
This does not affect which bins are flagged as peaks. Pass A's test is
peak >= floor + margin, and peak and floor are measured the same way in the
same sweep, so a uniform shift cancels — the 35.0 dB margin still means what
it was calibrated to mean.
It does matter if you compare absolute RSSI values across that boundary:
runs logged before and after v1.0.2 are on slightly different scales, so
subtract the offset or compare within one firmware version. detections.csv
is unaffected — packet RSSI is read from the radio's own packet status, not
from Sweep's per-bin sampling.
The current header is:
timestamp_utc,lat,lon,fix_quality,run,rx_uptime_ms,profile,classification,channel_or_node_id,packet_id,hop_limit,hop_start,relay_node,freq_mhz,sf,bw_khz,rssi_dbm,snr_db,raw_len,raw_packet_hex,decoded
The most useful common fields are profile, freq_mhz, sf, bw_khz,
rssi_dbm, snr_db, and raw_len. classification describes the listening
mode, not a cryptographically proven protocol identity. raw_packet_hex is
the exact received frame and decoded is intentionally blank unless a future
payload decoder has a verified schema.
For Meshtastic, channel_or_node_id is the sender's !-prefixed node ID and
packet_id is the packet ID in hexadecimal. The same packet_id may appear
more than once because a mesh relay retransmits it. Read the fields together:
- Same sender + same
packet_id+ changedhop_limitorrelay_nodeis usually a direct/relay observation pair, not an accidental duplicate. hop_start - hop_limitis the hops consumed when both values are present.relay_nodeis the final one-byte relay hint carried by the Meshtastic header, not a complete verified route.
channel_or_node_id, packet_id, hop_limit, hop_start, and relay_node
are Meshtastic header fields. MeshCore does not use that header, so those
columns are blank or zero for MeshCore rows. Use the RF fields and
raw_packet_hex for those observations; identity advertisements appear in
nodes.csv separately. Reticulum and General Exploration are likewise raw
packet observations unless their framing has been explicitly identified.
The current header is:
timestamp_utc,lat,lon,fix_quality,run,rx_uptime_ms,profile,node_id,node_type,long_name,short_name,public_key_hex,raw_len
This is an observation table, not a de-duplicated inventory. A node can appear
many times as it advertises or is heard through a relay. For an inventory,
group by profile and public_key_hex, retaining the newest non-empty name
and the latest location only when you are authorized to retain it.
| Profile | node_id |
Names and public key |
|---|---|---|
| Meshtastic | Full !-prefixed sender node ID |
long_name, short_name, and 32-byte public key from a decoded public-default-channel NodeInfo. |
| MeshCore | # plus the one-byte public-key hash |
Advert name in long_name; node_type is chat, repeater, room_server, or sensor; full 32-byte Ed25519 public key is present. |
Identity capture starts enabled and can be paused at Diagnostics -> Identities. Meshtastic private/custom-channel NodeInfo remains raw-only; the
firmware only decrypts the published default public channel. MeshCore
advertisements are signed, but LoRaTrace records the signed observation and
does not claim on-device Ed25519 signature verification.
Three columns added 2026-09-07 (audit A13) separate what the radio was configured for from what was actually established about the bytes:
classificationis unchanged and is the listening configuration — the mission profile in use. It is not an identification. Reticulum and General Exploration fall back to the Meshtastic channel tuple, so traffic heard under them could readreticulumwith no Reticulum parser involved.protocol_candidateis evidence-based:unknownuntil a parser actually got somewhere, thenmeshtastic_headerormeshcore_advert, orunknown_lora_candidatefor an off-grid Pass-B hit.parse_statusisnone,header, oridentity— how far parsing got.auth_statusis alwaysunauthenticated, and that is the point. MeshCore adverts carry an Ed25519 signature this firmware does not verify, and decrypting with a published default PSK proves possession of a public key, not a sender's identity. Treat every node id, name and key in these files as an observed claim.
manifest.txt is downloadable alongside the CSVs over the AP.
Use this file before drawing conclusions from a drive. A reason=boot row
marks the run start; later rows are periodic.
The first health checks are:
sd=ok,queue_drop=0, androw_drop=0: packets reached durable logging without the known queue/logger loss modes.bus_miss=0: the radio did not time out waiting for the shared SPI bus.identities_decodedandidentity_drops: identity pipeline activity and loss since boot. A nonzero decode count with zero drops is the desired result.heap_free,heap_min,heap_largest, and block counts: look for a sustained trend, not one isolated sample.gps_max_loop_gap_ms,nmea_bad_crc,sats, andfix_type: distinguish poor sky view from a healthy-but-still-acquiring receiver.
max_flush_ms is the worst detection-batch SD bus hold. max_session_ms is
the separate worst health-row write; do not use it to tune the batch size.
Five columns added 2026-09-07 answer questions the older ones could not:
homeisarmedonly when the radio was confirmed listening on the home channel at the moment the row was written; it isdownwhile a bounded action owns the radio, while Trace is paused, and after a restore or re-arm that failed. A quiet run withhome=downis not evidence that the band was quiet.read_errandrearm_errsplit reception faults thatcrc_errused to absorb:crc_erris now only a CRC rejection (the radio heard a corrupted packet),read_erris our own side failing to read one, andrearm_errcounts re-arms the radio refused — each one is airtime the receiver spent deaf.sd_short_writescounts appends where the card accepted fewer bytes than asked. Any nonzero value means rows were lost, usually a full or failing card, andsdwill have gonedownat that point.sd_csv_repairscounts CSVs this run had to close a partial last row on, or set aside because their header was not ours. Nonzero means the card carried damaged or foreign files before this run started; check for.badfiles in the run directory.
sd_outages and sd_last_outage_ms (added 2026-09-07, audit A26) count
times the card went away this run and how long the most recent gap lasted. A
health row cannot be written while the card is missing, so an outage used to be
nothing but a hole between two sd=ok rows. A reason=sd_recovered row is
now written the moment the card comes back, carrying the outage duration.
session_id names the boot that wrote the row. A card reseated mid-drive
rejoins the run directory it left on purpose, so the run number alone cannot
separate two boots' rows; manifest.txt carries a matching block per boot.
None of these count RF the receiver never had a chance to hear. Unknown reception loss stays unknown.
Added 2026-09-07 (audit A18). One [session] block per boot that opened this
run directory, appended, never rewritten. Read it before interpreting anything
else in the folder: the settings files at the SD root can be edited after a run
ends, so they are not evidence of what the run used.
Each block records the firmware version and git revision (including a -dirty
marker when the build had uncommitted changes), the board and radio, the
resolved modem parameters actually in use, the region, capture window and sweep
margin, and the mission profile. Two lines state the semantics that host
analysts most often get wrong:
- every
*_milliscolumn is device uptime, not wall clock; and - a position is where the receiver was, never where a transmitter was.
rejoined_existing_run=1 means this boot appended to a directory that already
existed — a reseated card, or run 9999 with no free number left. When you see
it, the folder holds more than one measurement session and session_id is what
separates them.
ui_redraw_max_us and ui_redraw_mean_us are the worst and mean cost of one
full screen redraw since boot. The UI task runs on Core 0 and never touches the
radio, so a slow frame cannot explain a missed packet on its own; what these
answer is whether redraw cost moves at all while a bounded action owns the
radio. Compare rows from a quiet window against rows written during a Sweep.
A Sweep gives each bin a few tens of milliseconds. Focus gives one bin up to two seconds, sampling it every 20 ms, and writes a single row per request. Run it from Activity (press down to the Focus view, then Enter); it surveys the last Sweep's strongest peak, or the bin containing the home channel if no sweep has completed.
Read the row in this order:
request_statusandhome_restorefirst. Only acompleterow withhome_restore=1is evidence of anything. Atimeoutrow means the request hit its wall-clock deadline under bus contention and stopped sampling; its partial statistics are still real, butobservation_mswill be short ofrequested_dwell_ms.sample_countagainstrequested_samples. Samples are derived from the dwell at a fixed 20 ms spacing, not requested independently, so a shortobservation_msmeans fewer samples and a correspondingly thinner picture.rssi_median_dbm,rssi_p90_dbm,rssi_peak_dbm. These are exact percentiles from a 1 dB histogram, or blank when the histogram cannot give an exact answer. A blank is not a zero and not a missing reading; it means the summary was refused rather than estimated.qualifying_countis the number of samples that sat above the pass's own median by the qualifying margin. It is the count that survived controlled measurement where every RSSI summary statistic failed.skipped_samples(2026-09-07, audit A22) counts sample slots abandoned because the SPI bus made them arrive more than a full slot late. They are skipped rather than taken back-to-back to catch up, sosample_countand the requested spacing describe samples that really were spaced that way. A non-zero value means the pass was measured under contention — not that anything was wrong with the signal.partial_observation_msis sampling time from a pass that did not qualify for coverage credit. A cancelled or failed pass used to reportobservation_ms0 whatever it had actually sampled.
Two things this file deliberately does not tell you:
coveragesays how much looking a bin has had — nothing else. It readsinsufficient,sampled, orrepeated, derived only from valid-pass count and accumulated observation time. It is not signal strength, not a likelihood that the bin is empty, and not confidence in anything. With the shipped 2s pass, one Enter earnssampledand three earnrepeated.- Coverage accumulates per bin, for consecutive requests at that bin, and resets when you survey a different one. Accumulated time means time on that frequency, so it cannot be carried across a retune.
- A cancelled, failed or timed-out request contributes nothing, and neither does a pass shorter than 500ms.
- The raw
valid_passesandobservation_msstay in the row beside the label. If the two ever disagree with your reading of it, trust the counts.
- Elevated RSSI is not a transmission, and coverage does not imply
activity. A high
qualifying_countsays samples were elevated over that pass's own floor. Whether that is a transmission depends on link quality the device cannot know, and controlled measurement found the indication least reliable exactly where a band is busiest. Usedetections.csvfor what was actually received.
wifi_on records whether the AP was up during the request; keep it in mind
when comparing rows, since it changes the resource picture around the radio.
- Copy the complete
runNNNNfolder before editing or importing it. - Open
session.csvfirst and record the run number, GPS availability, and any nonzero drop counters. - Filter
detections.csvbyprofile, then inspect frequency/modem/RSSI/SNR before interpreting protocol-specific fields. - Join
nodes.csvto packet observations by run and nearbyrx_uptime_ms; do not assume every packet from a node carries an identity. - Treat
focus.csvas a follow-up toenergy.csv, not a replacement: join them onselection_bin_indexto see what a longer look at a swept peak found. - Preserve the original header with exports. If combining runs, union fields by column name rather than concatenating by position.
raw_packet_hex is useful for offline protocol research, but it is not a
claim that a payload has been decrypted or authenticated. Keep raw data and
GPS traces out of public spreadsheets by default.