Standalone GPS beacon mode: when no KISS host is connected for 15s, the RNode transmits position and battery telemetry over LoRa. Two beacon paths: - LXMF (recommended): encrypted per-packet messages with announces, compatible with Sideband and any LXMF application. Supports IFAC network authentication. - Legacy JSON: plaintext or encrypted raw packets for simple collectors. Key changes: - GPS support for T-Beam Supreme S3 (L76K) and Heltec V4 (external) - SX1262 radio fixes: IQ polarity, DCD preamble lockup, RX reliability - LXMF identity management with NVS-backed Ed25519/X25519 keys - IFAC authentication (CMD_IFAC_KEY 0x89) for private networks - Per-channel serial isolation (USB, BLE, WiFi) - GPS status page in OLED display rotation - Provisioning via rnlog: provision-lxmf, provision-ifac - Documentation in Documentation/BEACON.md
12 KiB
GPS Beacon Mode
RNode devices with GPS hardware (e.g. T-Beam Supreme, Heltec V4) can autonomously transmit GPS position beacons over LoRa when no host computer is connected. This is useful for vehicle tracking, field asset monitoring, or any scenario where the RNode operates standalone on battery power.
Quick start
Minimum steps to get a beacon transmitting and a receiver collecting data:
1. Build & flash firmware make firmware-tbeam_supreme && make upload-tbeam_supreme
2. Install rnlog cd rns-collector && pip install .
3. Provision collector key rnlog provision-lxmf --dest <SIDEBAND_HASH> --port /dev/ttyACM0
4. Provision IFAC key rnlog provision-ifac --name helv4net --passphrase 'R3ticulum-priv8-m3sh' --port /dev/ttyACM0
5. Start receiver rnlog serve
6. Disconnect USB & wait RNode enters beacon mode after 15s of no host activity
The rest of this document explains each step, the beacon modes, and how to verify things are working.
How it works
When no KISS host activity is detected for 15 seconds, the RNode enters beacon mode and transmits a GPS position every 30 seconds. When a host reconnects (e.g. laptop running rnsd), beaconing stops automatically and normal RNode operation resumes.
Beacon modes
There are two beacon paths. The firmware selects automatically based on what has been provisioned.
LXMF beacon (recommended)
When an LXMF identity and collector key are provisioned, the RNode:
- Sends RNS announce packets (so the receiver learns the device's identity)
- Sends LXMF messages with
FIELD_TELEMETRYcontaining lat, lon, alt, speed, battery - Messages are encrypted per-packet with X25519 ECDH + AES-256-CBC + HMAC
These are proper Reticulum announces and LXMF messages — the receiver can be rnsd, Sideband, or any LXMF-aware application.
If IFAC is also provisioned, all packets are tagged with an authentication code before transmission. Receivers with matching network_name/passphrase accept the packets; others silently drop them.
Legacy JSON beacon
Without LXMF provisioning, the RNode falls back to sending raw JSON payloads:
{"lat":51.507400,"lon":-0.127800,"alt":15.0,"sat":8,"spd":0.5,"hdop":1.2,"bat":87,"fix":true}
- Plaintext: Sent to well-known PLAIN destination
rnlog.beacon. Zero configuration needed, but anyone in range can read it. - Encrypted: When provisioned with
rnlog provisionkey, sent as SINGLE packets. Only the matching collector can decrypt.
Hardware
Tested boards
| Board | GPS | LoRa | Notes |
|---|---|---|---|
| LilyGO T-Beam Supreme S3 | L76K (UART) | SX1262 | Best option. RTC, large battery connector |
| Heltec LoRa32 V4 | External (UART) | SX1262 | Needs external GPS module |
Wiring (T-Beam Supreme)
No external wiring needed — GPS and LoRa are integrated. Connect via USB-C for provisioning and flashing.
Building firmware
Prerequisites
- Arduino CLI (installed by
make prep-esp32) - Python 3 with
pyserial - USB cable to the device
Build and flash
cd RNode_Firmware
# First time: install Arduino cores and libraries
make prep-esp32
# Build
make firmware-tbeam_supreme
# Flash (device must be connected via USB)
make upload-tbeam_supreme PORT=/dev/ttyACM0
Other board targets: firmware-tbeam, firmware-heltec32_v4, firmware-lora32_v21, etc. Run make with no arguments to see available targets.
Provisioning
Provisioning configures the RNode with cryptographic keys over USB. Keys are stored in NVS (ESP32) or EEPROM and persist across power cycles. You only need to do this once per device.
There are three independent provisioning steps. Each is optional but recommended:
1. LXMF identity + collector key
This gives the RNode an LXMF identity (Ed25519 keypair) and tells it where to send telemetry.
# Install rnlog if not already done
cd rns-collector && pip install .
# Provision (requires rnsd running with a path to the Sideband destination)
rnlog provision-lxmf --dest <SIDEBAND_DEST_HASH> --port /dev/ttyACM0
What this does:
- Resolves the Sideband destination hash via Reticulum to get its public key
- Sends the key material to the RNode via KISS
CMD_BCN_KEY(0x86) - The RNode generates an Ed25519 identity (stored in NVS) and prints its source hash
- Reads back the RNode's LXMF source hash via
CMD_LXMF_HASH(0x87)
After provisioning, the OLED display shows an "LXMF Identity" page (page 5) with the source hash and target info.
2. IFAC authentication
If your receiver's RNodeInterface uses network_name and passphrase (IFAC), the beacon must send matching authentication tags or packets will be silently dropped.
rnlog provision-ifac \
--name helv4net \
--passphrase 'R3ticulum-priv8-m3sh' \
--port /dev/ttyACM0
The --name and --passphrase must match the receiver's Reticulum config:
# Receiver's ~/.reticulum/config
[[LoRa 868 MHz]]
type = RNodeInterface
network_name = helv4net
passphrase = R3ticulum-priv8-m3sh
...
What this does:
- Derives a 64-byte IFAC key:
HKDF-SHA256(SHA256(network_name) + SHA256(passphrase), IFAC_SALT, 64) - Sends to RNode via KISS
CMD_IFAC_KEY(0x89) - The RNode derives an Ed25519 signing keypair from the key and applies IFAC tags to all transmitted packets
3. Legacy collector key (alternative to LXMF)
If you don't need LXMF and just want encrypted JSON beacons:
rnlog provision
# Copy the 64-byte hex output, then send via CMD_BCN_KEY — see the Python
# snippet in the rns-collector README.
Receiver setup
The receiver is any machine running rnsd with an RNode interface on the same LoRa parameters.
Reticulum config
# ~/.reticulum/config
[reticulum]
enable_transport = yes
[interfaces]
[[LoRa 868 MHz]]
type = RNodeInterface
interface_enabled = True
port = /dev/ttyACM0
frequency = 868000000
bandwidth = 125000
spreadingfactor = 7
txpower = 14
# Optional but recommended — must match beacon provisioning:
network_name = helv4net
passphrase = R3ticulum-priv8-m3sh
Start the receiver
# Start rnsd (if not already running as a service)
rnsd
# In another terminal, start the telemetry collector
rnlog serve
Or to also relay GPS data to a Sideband app:
rnlog serve --sideband-dest <SIDEBAND_DEST_HASH>
Query collected data
# Recent readings
rnlog query -n 10
# Last hour, as JSON
rnlog -j query -s 1h
# Database summary
rnlog summary
# Export to CSV
rnlog export -f csv > telemetry.csv
Testing and verification
USB test (device connected)
Trigger a test beacon over USB without waiting for the 30s interval:
rnlog test-lxmf --port /dev/ttyACM0
This sends CMD_LXMF_TEST (0x88) to the RNode, which immediately transmits an announce + LXMF beacon and emits the pre-encryption plaintext as CMD_DIAG frames back over USB. The test tool validates:
- Announce packet structure and Ed25519 signature
- LXMF message structure and Fernet decryption
- FIELD_TELEMETRY parsing (lat, lon, alt, speed, battery)
Over-the-air verification
To verify the receiver is accepting IFAC-authenticated packets:
import RNS
reticulum = RNS.Reticulum()
target = bytes.fromhex("YOUR_RNODE_DEST_HASH") # from provision-lxmf output
# Check if the receiver has seen the announce
identity = RNS.Identity.recall(target)
if identity:
print(f"Identity known: {identity}")
else:
print("Not yet received — trigger a test beacon or wait for next announce")
Checking the OLED display
The T-Beam Supreme has 6 display pages (cycle with the button):
| Page | Content |
|---|---|
| 0-1 | Radio diagnostics (standard RNode) |
| 2-4 | GPS (coordinates, satellites, altitude) |
| 5 | LXMF status (identity hash, target, announce/beacon timing) |
When LXMF is active, page 2 shows LX instead of BCN next to the satellite count.
Radio parameters
Beacons use fixed LoRa parameters that must match the receiver's RNode interface:
| Parameter | Value |
|---|---|
| Frequency | 868 MHz |
| Bandwidth | 125 kHz |
| Spreading Factor | 7 |
| Coding Rate | 4/5 |
| TX Power | 17 dBm |
These are set in Beacon.h. If your receiver uses different radio parameters, update the BEACON_* defines and rebuild.
Timing
| Parameter | Default | Define |
|---|---|---|
| Beacon interval | 30s | BEACON_INTERVAL_MS |
| Announce interval | 10min | LXMF_ANNOUNCE_INTERVAL_MS |
| Startup delay | 10s | BEACON_STARTUP_DELAY_MS |
| Host inactivity timeout | 15s | BEACON_NO_HOST_TIMEOUT_MS |
Packet sizes
| Mode | Header | Payload | Total |
|---|---|---|---|
| Plaintext JSON | 19B | ~93B JSON | ~112B |
| Encrypted JSON | 19B | 32+16+96+32 = 176B | ~195B |
| LXMF announce | 2B | 183B (+ 8B IFAC tag) | ~193B |
| LXMF beacon | 2B | ~250B encrypted | ~260B |
All fit within the RNS MTU of 508 bytes.
Troubleshooting
Receiver doesn't see any packets
- Radio mismatch: Verify frequency, bandwidth, and spreading factor match between beacon and receiver
- IFAC mismatch: If the receiver has
network_name/passphraseset, the beacon must be provisioned with matching IFAC key. Packets without valid IFAC are silently dropped — no error is logged - Range: LoRa SF7 at 868 MHz has limited range in urban environments. Try line-of-sight first
rnlog provision-lxmf fails to resolve destination
The Sideband destination must be reachable via Reticulum. Ensure rnsd is running and has a path to the destination (either direct or via transport nodes).
has_path() returns False but identity is recalled
This is expected. has_path() checks the RNS path table (populated by Transport routing), while Identity.recall() checks known_destinations (populated when any announce is validated). For local interfaces, the path table entry may not be created, but the identity is still usable.
Device shows "GPS searching" indefinitely
The T-Beam Supreme GPS needs clear sky view for first fix. Cold start takes 30-60 seconds outdoors, longer indoors. The RTC retains time across reboots once synced, speeding subsequent fixes.
Architecture
┌─────────────────────────┐ LoRa 868 MHz ┌────────────────────────┐
│ T-Beam Supreme │ ──────────────────────► │ Receiver │
│ │ IFAC-tagged packets │ (rnsd + RNode) │
│ GPS ──► Beacon.h │ │ │
│ ├─ announce │ Announce (185B + 8B IFAC) │ rnsd validates IFAC │
│ └─ LXMF msg │ LXMF (250B + 8B IFAC) │ ├─ known_destinations │
│ │ │ └─ LXMF delivery │
│ Provisioned via USB: │ │ │
│ - Collector key (64B) │ │ rnlog serve │
│ - IFAC key (64B) │ │ ├─ SQLite database │
│ - LXMF identity (NVS) │ │ └─ Sideband relay │
└─────────────────────────┘ └────────────────────────┘
Files
| File | Purpose |
|---|---|
Beacon.h |
Beacon state machine, LXMF/JSON path selection |
BeaconCrypto.h |
X25519 ECDH (libsodium), HKDF, AES-256-CBC, HMAC |
LxmfBeacon.h |
LXMF identity, announce construction, beacon messages |
IfacAuth.h |
IFAC key storage (NVS), Ed25519 signing, tag masking |
GPS.h |
GPS parsing (TinyGPS++) |
ROM.h |
EEPROM addresses for beacon key storage |
Framing.h |
KISS command definitions (CMD_BCN_KEY, CMD_IFAC_KEY, etc.) |
Display.h |
OLED display pages including LXMF status |