docs › For developers and hub operators › Wire protocol
weechat-radio:$ Wire protocol
For people writing software. If you only want to chat, see weak signals instead.
One frame
The hub uses version 2 on radio (KISS) and internet (WebSocket). Version 1 frames are still decoded.
msg_id is not sent on v2: the receiver recomputes BLAKE3 of origin, dest, seq, and the uncompressed body. The timestamp is not part of the id. Two messages in the same second differ when the destination, the text, or the per-origin sequence differs.
Version 2 header
| Field | Size | Notes |
|---|---|---|
| ver | 1 | 2 |
| packed | 2 | type (high 4 bits) + flags (low 12 bits) |
| origin | 6 | packed callsign |
| dest | 1 or 6 | 1-byte group index if GROUP_IDX (0 = BULLETIN, 1 = BEACON) |
| hops_left | 1 | TTL, default 3 |
| ts | 2 | minutes since epoch mod 216 |
| seq | varint | per-origin counter (clock-tolerant identity) |
| body_len | varint | max 300 uncompressed |
| body | n | UTF-8, optionally smaz-compressed |
| crc | 2 | CRC-16/IBM-3740 of everything before CRC |
| signature | 64 | optional Ed25519 if SIGNED |
Flags
Bit number, then the flag. They sit in the low 12 bits of packed.
- 0
INET_OK— gateways may forward to the internet- 1
GROUP- 2
SIGNED- 3
REQ_ACK- 4
THIRD_PARTY— guest nick origin- 5–6
- priority: 0 routine, 1 priority, 2 emergency
- 7
NO_INET— never forward to the internet (radiomode)- 8
GROUP_IDX— v2 wire only: dest is a 1-byte group index- 9
COMPRESSED— v2 wire only: body is smaz-compressed
!! at the start of a message is emergency (sent twice). ! is priority. Unacked frames are retried and the modem steps down: QPSK 1/2 → RDM-1200S → RDM-600S → RDM-300S → MFSK-32R.
BEACON
Type BEACON. Body is UTF-8 B|<mode>|<khz> (example B|internet-radio|144950). The kHz field is the origin’s dial so gateways and the map know which band a station is on. A fourth field carries the station’s grid square, B|<mode>|<khz>|<grid> (example B|radio|144950|IO91WM12): the GPS square while a USB GPS has a fix (8 characters, about 500 m), otherwise the grid from setup, and none when the station does not share its position on the map. Older readers stop at the frequency. Legacy B|<mode> is still decoded. Beacons heard on RF are also tagged with the receiving station’s own dial.
A station’s first three beacons go about a minute apart, then one every 5 minutes, each gap counted from when the last one reached the modem. A beacon that falls due waits while an exchange the station takes part in is in progress: a line and its receipt, or a mail and its acceptance. It goes ahead of this station's own queued lines. Keyed during an exchange, it would leave the station deaf to the answer. It waits out one exchange at most, a warm-up beacon never more than two minutes after the last, so a busy chain still hears it about every 5 minutes. A busy channel holds a due beacon only while frames are being decoded: a steady noise floor or stuck carrier sense reads near 100% with nothing on the air. The countdown shows that time: after exchange while it waits, pending until the beacon reaches the modem, and busy while a decoded frame is still fresh.
Callsign packing
6 bits per character, 8 characters, alphabet:
space A–Z 0–9 / - ~
Guests: ~ALICE. Amateur callsigns must look like a callsign (letter and digit).
Delivery marks
These marks are on your line in live chat. They only move forward: [..] then [tx], then maybe [rl], then [gw] when a gateway accepts the frame onto the hub, then [ok], then [all] for a closed group.
A later mark never slides back to an earlier one: a retry after [rl] or [ok] goes out without a new mark, and a closed group that is still missing some ACKs stays [ok] until a late ACK makes it [all]. [gw] is not an ACK, and retries continue. In radio mode [rl] is as far as the frame goes: it is marked to stay off the internet.
[rl] is your own message coming back (speaker echo or a relay). [ok] is their ACK. VOX often shows [rl] because the speaker reaches the microphone. A cleaner path often goes [tx] then [ok].A closed group keeps retrying after the first member ACK until every stored member has ACKed, or the retries run out. #bulletin is public and has no member list, so it stops at [ok] and never shows [all]. [rl] can be skipped when you do not hear your own frame.
[xN] is how many times the line was keyed, counted until [rl], [gw], or [ok]: [x1] is the first send, and with the default of 3 retries the last copy is [x4]. [no] waits two more full intervals after that copy, so a late receipt does not turn [no] into [ok].
ACK
Type ACK. This is step 3 above: it paints [ok] on the sender. Body is the 8-byte msg_id of the original, then the receiver SNR in dB as a signed byte (-128 for none), then the hops left on the original when this station heard it. The hops byte may be followed by the sender’s grid square in ASCII (4, 6 or 8 characters), added when it changed or every 10 minutes, so VOX stations, which send no beacons, still get their position out. Older readers stop at the hops byte.
Relayed with TTL, and only toward the originator: a station carries a receipt only if it heard the line with more hops left than the receipt's sender did. Next to the sender a carried receipt goes twice at most. Further out it keeps walking home, a retry interval apart, until the next station toward the sender is heard carrying it. It never goes again just because another copy of it was heard.
Unacked messages are retransmitted up to rf.max_retries times (default 3). Routine waits before each extra copy are 45s, then 90s, then 180s (priority 20/40/80, emergency 8/16/32). After the last copy the station waits that step twice (routine 360s, then 360s again) and then marks [no] if no ACK arrived. Each wait is multiplied by 0.75–1.25 when retry jitter is on. A busy channel can hold the copy after the timer, so the gap between keys can run longer than that band.
Group ACKs wait a random whole-frame slot (up to 6 for routine) plus 0–ack_dither_ms (default 800 ms) before keying, so two members on either side of the sender do not land on it together; direct ACKs are not delayed.
HAVE / WANT
Body is a comma-separated list of hex msg ids in a closed group window.
FRAG
Type FRAG (10). Group messages on HF, or any payload larger than the PHY frame, are split into Reed–Solomon shards. Any k of k+m reconstruct the original (default 2 of 3). k grows until every fragment fits the PHY frame, because a modem drops an oversized frame instead of keying it, and the TX mode is chosen from the largest fragment rather than the whole envelope. Gateways forward fragments as they hear them; the hub reassembles from shards received via different gateways.
Fragments carry the original flags with SIGNED cleared, since a signature covers the whole body rather than one shard. REQ_ACK is preserved so the reassembled message still asks for a single ACK; a fragment never acknowledges on its own.
Federation (reserved)
Hub-to-hub uses the same envelope and the same msg_id dedupe. Not enabled in v1.