Canonical, shared specification. This document is the single source of truth for the LoRa link between the OSS Transmitter and the OSS Receiver. Both firmwares must stay byte-compatible with it. Device-specific files (
transmitter/lora-protocol.md,receiver/docs/lora-protocol.md) redirect here.
RTCM3 correction data is transmitted over LoRa using a simple fragmentation protocol. Large RTCM messages are split into multiple LoRa packets and reassembled at the receiver.
This document describes Protocol v2. The header was extended by one byte (frameType)
compared to the original 4-byte v1 header.
frameType |
Symbol | Description |
|---|---|---|
0x01 |
FRAME_RTCM |
RTCM correction data |
0x02 |
FRAME_MAINTENANCE |
Telemetry broadcast (TX → air) |
0x03 |
FRAME_COMMAND |
Control command (RX → transmitter) |
0x04 |
FRAME_ACK |
Optional acknowledgement |
+────────────+──────────────+─────────────────+────────────────+─────────────+──────────────────+
│ Frame Type │ Message ID │ Total Packets │ Packet Number │ Data Length │ Data │
│ (1 byte) │ (1 byte) │ (1 byte) │ (1 byte) │ (1 byte) │ (0-245 bytes) │
+────────────+──────────────+─────────────────+────────────────+─────────────+──────────────────+
| Field | Size | Range | Description |
|---|---|---|---|
| Frame Type | 1 byte | 0x01–0x04 | Type of frame (see table above) |
| Message ID | 1 byte | 0–255 | Unique ID per message, wraps around |
| Total Packets | 1 byte | 1–255 | How many packets comprise this message |
| Packet Number | 1 byte | 0 to Total-1 | Sequential index of this packet |
| Data Length | 1 byte | 0–245 | Payload bytes in this packet |
Up to 245 bytes of raw RTCM data per packet (250 byte max LoRa frame − 5 byte header).
⚠️ TX and RX
config.hmust use identical radio parameters or the link silently fails. The values below are the canonical v2 set. See “Implementation status” at the end of this document for the current per-device config and any outstanding mismatch.
| Parameter | Value |
|---|---|
| Frequency | 433.7 MHz |
| Spreading Factor | SF7 |
| Bandwidth | 500 kHz |
| Coding Rate | 4/5 |
| TX Power | 20 dBm |
| Sync Word | 0x12 |
| Preamble | 12 symbols |
| CRC | Enabled (hardware) |
A 500-byte RTCM message with Message ID 42:
Packet 0: [0x01] [42] [03] [00] [245] [245 bytes of RTCM data]
Packet 1: [0x01] [42] [03] [01] [245] [245 bytes of RTCM data]
Packet 2: [0x01] [42] [03] [02] [010] [ 10 bytes of RTCM data]
Maximum theoretical message size: 255 × 245 = 62,475 bytes.
To reduce LoRa airtime, the transmitter can drop a configurable fraction of RTCM frames before transmission:
loraTransmissionRatio (managed via ConfigManager / web interface)1 (transmit all frames)1 = transmit all RTCM frames (100%)2 = transmit every 2nd frame (50% reduction)3 = transmit every 3rd frame (66% reduction)N = transmit every Nth frameThe transmitter tracks: total RTCM frames received from NTRIP, total frames transmitted via LoRa, the current transmission ratio, and total bytes transmitted.
Incoming LoRa Packet
│
▼
Parse header (frameType, msgId, total, pktNum, len)
│
▼
frameType == FRAME_RTCM? If not, drain and discard
│
▼
Validate: pktNum < total, total > 0, len ≤ 245
│
▼
Lookup/create MessageReassembly[msgId]
│
▼
Store packet data at pktNum slot
│
▼
receivedPackets == totalPackets?
╱ ╲
Yes No
│ │
▼ ▼
Reassemble in Wait for more
packet order (timeout: 5s)
│
▼
Feed to RTCM parser
PACKET_TIMEOUT (5 seconds)The receiver checks:
frameType == FRAME_RTCM (0x01) — other types are drained and discardedpacketNumber < totalPacketstotalPackets > 0dataLength ≤ 245totalPackets across all packets of the same messageAfter reassembly, the raw byte stream is fed into a circular buffer and parsed for RTCM3 frames:
┌──────────┬───────────────┬─────────────────┬───────────┐
│ Preamble │ Reserved + Len│ Payload │ CRC-24Q │
│ 0xD3 │ 2 bytes │ 0-1023 bytes │ 3 bytes │
│ (1 byte) │ (10+10 bits) │ │ │
└──────────┴───────────────┴─────────────────┴───────────┘
0xD3| Type | Description |
|---|---|
| 1001–1004 | GPS L1/L2 observations |
| 1005–1006 | Station coordinates |
| 1007–1008 | Antenna description |
| 1009–1012 | GLONASS L1/L2 observations |
| 1019 | GPS ephemeris |
| 1020 | GLONASS ephemeris |
| 1033 | Receiver information |
| 1074–1077 | GPS MSM4/5/7 |
| 1084–1087 | GLONASS MSM4/5/7 |
| 1094–1097 | Galileo MSM4/5/7 |
| 1124–1127 | BeiDou MSM4/5/7 |
| 1230 | GLONASS code-phase bias |
The receiver tracks:
| Counter | Description |
|---|---|
completeMessagesReceived |
Successfully reassembled messages |
timeoutMessages |
Messages that timed out (missing packets) |
crcErrorMessages |
RTCM frames with invalid CRC-24Q |
lastRssi |
RSSI of last received LoRa packet (dBm) |
| Message rate | Complete messages per second (10s window) |
Both firmwares implement the v2 5-byte header (frameType + messageId + totalPackets +
packetNumber + dataLength).
Known mismatch (action needed): the radio parameters in
config.hcurrently differ between the two devices and must be reconciled to interoperate:
Define Transmitter ( config.h)Receiver ( config.h)LORA_FREQUENCY433E6433700000LORA_BANDWIDTH125E3500E3Pick one canonical set (this doc assumes 433.7 MHz / 500 kHz) and update both
config.hfiles to match.