OpenPulseHF · HF software modem · OSI architecture

The stack that keeps a link alive when everything else is down.

A resilient, open-source software modem that turns an SSB transceiver and a sound interface into an adaptive, long-range data link — carrying messages and data with no towers, no internet and no satellites.

Rust · open source Adaptive 14-rung ladder FEC on every rung + ARQ ARDOP TNC for Pat Raspberry Pi → desktop GPL v3
CI status Latest release License: GPL v3 Donate via PayPal

Author: Simon Keimer · DC0SK

Architecture

Seven layers, from keyboard to RF

Amateur radio does not map perfectly onto the OSI model, but the model shows where each part of OpenPulseHF lives. A message travels down the stack to the air — and back up on receive at the far station.

TX ↓ down the stack to RFRX ↑ up the stack at the far end
On-air / RF peer link

Authenticates the remote station over the radio: Ed25519 signed handshake and a trust store (Release 1), plus an experimental post-quantum path (ML-DSA / ML-KEM, not part of Release 1).

Local control channel

Authenticates the operator client that commands the transmitter: a PSK-authenticated, encrypted Noise channel between the daemon and its control clients.

Abstract

What OpenPulseHF is

OpenPulseHF is a software-defined data modem and messaging stack for HF (shortwave) radio. It turns an ordinary SSB transceiver and a computer sound interface into a digital link that, on HF, can span hundreds to thousands of kilometres — without any intervening infrastructure. At its core is an adaptive modem that measures the channel and steps its speed and modulation up or down a single 14-rung ladder to keep the link alive as propagation changes. Messages are protected by forward error correction on every rung and automatic retransmission; station identities by Ed25519 signatures, with an experimental post-quantum option.

The first release (Release 1, v0.17.0) is deliberately narrow: the ladder, session compression, the background daemon and CLI, and an ARDOP-compatible TNC for Pat (Winlink). The much larger rest of the workspace ships disabled or experimental.

Status

Where the project is now

A multi-crate Rust workspace whose first release is deliberately narrow: one adaptive rate ladder with FEC on every rung, session compression, the daemon and CLI, and an ARDOP-compatible TNC for Pat. The rest of the stack — KISS/AX.25, Winlink B2F and the CMS gateway, QSY, mesh, relay, repeater, JS8 discovery, file transfer, the post-quantum handshake, GPU kernels and the GUIs — is in the code but ships disabled or experimental. All tests run against a deterministic loopback backend — no audio hardware is required to build or test.

In progress · Release 1 = v0.17.0 · not yet tagged

One ladder, one TNC, tested on air on 2 m before the tag

In Release 1: openpulse-daemon (binary openpulse-server), openpulse-cli (binary openpulse) and the ARDOP TNC (openpulse-ardop, binary openpulse-tnc); the hpx_hf ladder SL1–SL14 and its plugins (MFSK16 with its return channel, BPSK, QPSK250-D, OFDM52, FSK4 ACK); each rung's FEC; session compression; PTT backends; station ID; the signed classical (Ed25519) handshake.

Not in Release 1 (still in the code; ships disabled or experimental, with no release gate): mesh, repeater, relay, discovery/JS8, file transfer, QSY, FreeDV auth, Winlink B2F and the CMS gateway, KISS, the post-quantum handshake, GPU acceleration, the operator panel and TUI, and the plugins not on hpx_hf (psk8, 64qam, scfdma, pilot).

Where it stands (15 October 2026): M0 (governance reset) and M1 (v0.17 wire format, 1 October) are done. M2 (release path) is done in code as of 14 October; its on-air follow-ups — the PTT leader measured at gate G1 and a Raspberry Pi receive-cost re-measure — wait for the radios. Next is M3: on air on 2 m (IC-9700 ↔ FT-818, 144.640 MHz) — gates G0–G3, the A1/A2/A4/A5 runs and a Pat session over the ARDOP TNC. HF follows only with the release candidate; M4 cuts v0.17.0. main still reports version 0.16.0.

Update both ends of a link: v0.16.0 and v0.17 do not talk to each other at all. Every transmitted frame is now whitened (added after v0.16.0 in #1027, fixed in #1148), the signed handshake became binary (#1147), and since 10 October (#1062) BPSK63, BPSK100 and BPSK250 (SL3–SL5) send a PN-63 preamble. Profiles changed too: there are now exactly two, fast and robust; the ten earlier names were deleted with no aliases.

Latest release · v0.16.0 · 18 July 2026

A documentation and test-integrity release

It shipped the OpenPulseHF book and a rewritten operator manual, and replaced acceptance tests that could not fail with real AWGN round-trips. The modem itself was unchanged from v0.15.0. What changed since, for operators: the release notes, Unreleased section.

v0.15.0 — Winlink stack hardening: one live DoS fixed, an aggregate decompression cap, per-operation read deadlines.  ·  v0.4.0 — JS8-based station discovery and rendezvous, signed P2P file transfer, OFDM re-seat and HARQ soft-combining.  ·  v0.3.0 — signed handshake over the air on connect, automatic ADIF logbook, opt-in ARDOP adaptive ARQ, generic serial CAT.  ·  v0.2.1 — CE-SSB transmit envelope conditioning for the high-PAPR multicarrier modes.

14
rungs on the hpx_hf ladder, SL1–SL14
~9 → 7.7k
net bps, MFSK16 → OFDM52-64QAM + LDPC
2
session profiles, fast and robust
3
FEC families on the ladder: RS → soft-concatenated → LDPC
5
plugins in Release 1, of 10 in the workspace
2 m
first on-air stage (M3); HF with the release candidate

Why OpenPulseHF

Notable capabilities

Only the first three are part of Release 1. The others are implemented and tested in simulation, but ship disabled or experimental.

One adaptive ladder, coded on every rung

hpx_hf, SL1–SL14: MFSK16 → coded BPSK → differential QPSK250-D → OFDM52 up to 64QAM with high-rate LDPC (r≈8/9), ~9 bps to 7.7 kbps. ACK/NACK- and SNR-driven. Part of Release 1.

LLR-accumulating ARQ

Soft LLR values accumulate across retransmissions (PACTOR-style Memory-ARQ), turning each retry into a soft combining gain. Part of Release 1.

CE-SSB conditioning for data modes

Controlled-Envelope SSB (Hershberger W9GR, QEX 2014) — a voice-SSB technique — applied as a per-mode TX conditioner on the QPSK-subcarrier OFDM modes (OFDM52 on the ladder; the denser OFDM and all SC-FDMA modes are excluded). +1.18 dB average power measured on air at fixed PEP (FT-991A, 2 m). Part of Release 1, default on.

experimentalPost-quantum handshake

ML-DSA-44 + ML-KEM-768 in the ConReq/ConAck frames; Hybrid mode signs with both Ed25519 and ML-DSA-44. Not part of Release 1.

experimentalSC-FDMA on HF

DFT-spread OFDM with DFT-CE pilot channel estimation and MMSE equalisation. The frequency-interleaved pilots limit the realised PAPR advantage; OFDM, not SC-FDMA, is the high-throughput path. Not part of Release 1.

experimentalPilot-framed single carrier

PILOT-* modes up to DVB-S2 32APSK: known in-band pilots drive carrier tracking instead of a decision-directed loop. Not part of Release 1.

experimentalGPU-accelerated DSP

wgpu compute kernels (BPSK modulate/demodulate, timing search, RRC FIR matched filter, 256-pt FFT, soft LLR demod) with automatic CPU fallback, behind the gpu feature. Not part of Release 1.

experimentalEd25519-signed QSY frequency agility

A signed QSY_REQ/LIST/VOTE/ACK protocol, verified against the handshake peer, with initiator and responder in the daemon and rig CAT via rigctld. Not part of Release 1.

experimentalRelay, mesh, repeater

Trust-weighted multi-hop relay with query propagation, mesh re-broadcast with authenticated beacons, and a cross-band repeater. Not part of Release 1.

experimentalJS8-based discovery + rendezvous

A native JS8 waveform (8-GFSK, LDPC(174,87)) to find OpenPulse stations on the JS8 calling frequency, marked by an @OPULSE hint, and negotiate a working frequency. Off by default; RX-only until the operator opts into TX. Not part of Release 1.

experimentalSigned P2P file transfer

Offer/accept transfer with an inline signed TransferManifest + SHA-256 checked against the peer's handshake key; a failed check quarantines the file. Not part of Release 1.

experimentalFreeDV authentication

Ed25519-signed beacons for FreeDV voice via the FreeDV GUI's UDP data port (openpulse-freedv-auth) — no FreeDV fork. Not part of Release 1.

On-air status: a first rig-to-rig decode on 2 m passed on 28 July 2026 (phase A1, on the pre-v0.17 format), and the CE-SSB power and spectrum checks were made on real RF. The Release 1 on-air campaign (M3) has not run yet. Fade performance on HF is validated in the Watterson simulator only.

Modulation modes

The hpx_hf ladder, ~9 bps to 7.7 kbps

Release 1 has one rate ladder, SL1–SL14, with forward error correction on every rung. An adaptive session walks it up and down, driven by ACK/NACK feedback and per-level SNR gates. Adaptive operation is opt-in: [modem] ota_enabled = true on the daemon (default fixed mode BPSK250), [ardop] enable_adaptive_arq = true on the ARDOP TNC.

SLModePluginFECNet bps SNR floor (dB)Declared BW (Hz)Preamble
Sub-floor · deep-fade rung
1MFSK16mfsk16RS~9none5007-symbol Costas sync
Coded single carrier · ≤ 500 Hz · the robust profile tops out at SL6
2BPSK31bpskRS27362.532-symbol --++ run · initial level
3BPSK63bpskRS544125PN-63 (x⁶+x+1) since #1062
4BPSK100bpskRS874.5200PN-63 (x⁶+x+1) since #1062
5BPSK250bpskRS2195500PN-63 (x⁶+x+1) since #1062
6QPSK250-DqpskRS4377500Designed 16-symbol sequence · differential QPSK
OFDM52 · 52 subcarriers · ~2 031 Hz · soft-concatenated FEC
7OFDM52ofdmsoft-concatenated1 26492 031Schmidl-Cox symbol
8OFDM52-8PSKofdmsoft-concatenated1 895102 031Schmidl-Cox symbol
9OFDM52-16QAMofdmsoft-concatenated2 527122 031Schmidl-Cox symbol
10OFDM52-32QAMofdmsoft-concatenated3 159142 031Schmidl-Cox symbol
11OFDM52-64QAMofdmsoft-concatenated3 790162 031Schmidl-Cox symbol
OFDM52 · high-rate LDPC (r≈8/9)
12OFDM52-16QAMofdmLDPC high-rate5 141182 031Schmidl-Cox symbol
13OFDM52-32QAMofdmLDPC high-rate6 426192 031Schmidl-Cox symbol
14OFDM52-64QAMofdmLDPC high-rate7 710202 031Schmidl-Cox symbol

The BPSK rungs carry RS(255,223), upgraded per frame to RS(255,191) where that costs no extra RS block. ACKs go out on FSK4-ACK (fsk4, 300 Hz) or, when the receiver recommends SL1, on the MFSK16 return channel. "Declared BW" is what the plugin reports and what ARDOP ARQBW sizes against; for the single-carrier modes it is a conservative 2 × baud. Wire break: v0.16.0 and v0.17 do not talk to each other on any rung — update both ends of a link.

Two session profiles, one ladder

ProfileSL rangeTop modeOccupied BWUse when
fast (default)SL1–SL14OFDM52-64QAM + LDPC r≈8/9up to ≈2 031 HzGood conditions, a 2.4 kHz SSB filter, a linear PA
robustSL1–SL6QPSK250-D≤ 500 HzPoor conditions or limited gear (narrow filters, small or non-linear PAs); single-carrier, all coded

robust is the same ladder capped at SL6, so a fast and a robust station interoperate; the robust side never climbs past SL6. The ten earlier profile names (hpx500, hpx_modcod, hpx_pilot*, hpx_ofdm_hf, hpx_wideband*, hpx_narrowband, …) were deleted on 1 October 2026 with no aliases; hpx_hf is now fast, and any other name is rejected with the list of valid names. The ARDOP TNC floors its adaptive ladder at SL2 (it has no MFSK16).

Other modes · not part of Release 1

PluginModesNotes
bpskBPSK250-RRCOff-ladder; keeps the --++ preamble
qpskQPSK125, QPSK250, QPSK500, QPSK500-D, QPSK1000, QPSK1000-HF, QPSK2000, plus -RRC variantsQPSK250 is coherent; the ladder uses the differential QPSK250-D
ofdmOFDM1616 subcarriers, QPSK; LS + ZF
psk88PSK500, 8PSK1000, 8PSK1000-HF, plus -RRC variants, 8PSK2000-RRCGray-coded
64qam64QAM500, 64QAM1000, 64QAM2000-RRCUp to 12 000 bps gross; 64QAM2000-RRC needs a very clean channel
scfdmaSCFDMA16, SCFDMA26-{8PSK,16QAM,32QAM}, SCFDMA52, SCFDMA52-P2, SCFDMA52-LP, SCFDMA52-{8PSK,16QAM,32QAM,64QAM}, SCFDMA52-64QAM-P4DFT-CE + MMSE; SCFDMA52-P2 and -LP are low-PAPR demonstrators (-LP is flat-channel CE only and mis-decodes on selective channels)
pilotPILOT-{QPSK,8PSK,16QAM,32APSK} at 500/1000 baud and -RRC, plus 2000-RRCPilot-framed single carrier; up to DVB-S2 32APSK
js8JS8-SLOW, JS8-NORMAL, JS8-FAST, JS8-TURBO, JS8-ULTRA8-GFSK; discovery only, RX-only by default

These are registered and selectable as fixed modes ([modem] mode), but are not on the Release 1 ladder and carry no release gate; openpulse modes prints the live registry. At 2000 baud use the -RRC variants: plain rectangular QPSK2000 is registered but RRC-superseded, and 8PSK2000 is no longer advertised (#1359), because the crossfade pulse is ISI-limited at 4 samples/symbol. The 9600-baud QPSK9600-RRC / 8PSK9600-RRC waveforms keep their code but are not advertised: they need ≥ 38.4 kHz audio and the engine runs at 8 kHz (post-1.0).

The full rationale for the ladder, its FEC choices and its floors is in docs/mode-fec-ladder.md.

Who it's for

Read it from your side of the screen

Operator interfaces

Five ways to drive it

In Release 1 you drive the modem from the CLI against the background daemon (openpulse-server), or from Pat through the ARDOP TNC (openpulse-tnc). The GUIs are not part of Release 1; two of them are developer tools.

InterfaceRelease 1Description
CLI
openpulse
yes transmit, receive, modes, devices, mode-advisor, adaptive, arq, benchmark, monitor, calibrate, config init, and daemon … to control a running openpulse-server.
Operator panel
openpulse-panel
no iced GUI on the daemon control port: controls, spectrum, waterfall, rate ladder, messages, event log.
TUI
openpulse-tui
no ratatui terminal UI: HPX state, AFC/rate meters, DCD energy bar, transitions log.
Twin-station view
openpulse-twinview
dev tool egui viewer over two daemons, so both link directions show at once.
Signal testbench
openpulse-testbench
dev tool egui TX / channel / mixed / RX views with waterfall, spectrum and scatter.

Quick start

Build it, test it — no radio needed

The test suite runs entirely against a deterministic loopback backend. No audio hardware, radio or station is required to build or test.

# Toolchain preflight (required: rustc >= 1.97.1)
./scripts/check-toolchain.sh

# Build (requires libasound2-dev on Linux for the CPAL audio feature)
cargo build --workspace

# Test suite — no audio hardware required
cargo test --workspace --no-default-features

# Lint
cargo clippy --workspace --no-default-features --all-targets -- -D warnings
cargo fmt --all -- --check

# Workspace gate (quote results only from its GATE: line)
scripts/gate.sh

# Benchmark regression gate
cargo run -p openpulse-cli --no-default-features -- --backend loopback --log error benchmark run

# Automated mode × channel test matrix (outputs to docs/test-reports/)
cargo run -p openpulse-testmatrix --no-default-features

Try it without a radio

A purely rig-less demo to get a first impression of the GUIs and the adaptive modem. None of it needs a radio or sound card, and none of it transmits.

# Two-station ARQ link simulator GUI — simulates BOTH stations over a virtual
# HF channel. Change SNR / channel model / mode live and watch the ladder adapt.
cargo run -p openpulse-linksim --features gui --bin openpulse-linksim-gui

# With the operator panel: --serve makes the simulator speak the daemon control
# protocol. Press Run in the simulator, then Connect in the panel (default 127.0.0.1:9000).
cargo run -p openpulse-linksim --features "gui,serve" --bin openpulse-linksim-gui -- --serve 127.0.0.1:9000
cargo run -p openpulse-panel   # in another terminal

For on-air operation, build the daemon or TNC with audio (--features cpal on openpulse-daemon and openpulse-ardop; the CLI has it by default) plus the PTT feature your backend needs (serial for RTS/DTR, gpio), and configure ~/.config/openpulse/config.toml — see the example config, the operator manual and the on-air test plan.

Interoperability

Talking to other software

OpenPulseHF is GPL v3, but these interfaces let non-GPL software interoperate without GPL obligations — each sits behind a process or network protocol boundary. Note that plugins which statically link openpulse-core are derivative works and must be GPL-compatible; the only supported path for a proprietary DSP backend is a separate process bridged over one of these interfaces.

InterfaceTransportRelease 1Description
ARDOP TNC
openpulse-tnc
TCP 8515/8516yes ARDOP ASCII command protocol, as used by Pat — including GRIDSQUARE, ARQBW, ARQTIMEOUT, CWID, SENDID and PING
Daemon control portNDJSON / TCP 9000yes Full event stream + control commands; operator panel and scripting
Daemon WebSocketJSON / WS 9001yes Same protocol as the TCP port; browser and Electron clients
CLI subprocessstdin / stdoutyes NDJSON output; invocation crosses the process boundary
KISS/AX.25 TNC
openpulse-kisstnc
TCP 8100no Standard KISS framing, AX.25 UI frames
Winlink CMS gateway
openpulse-gateway
B2F / TCP 8772no Outbound-only client to cms.winlink.org (CMS is proprietary); B2F Type D (gzip) only — Type C/LZHUF is unsupported and inbound Type C proposals are rejected
PKI REST APIHTTP/JSON 8080no Trust-bundle and key-management endpoints; read-only routes unauthenticated

Supporters

Big thanks to

DD2ZM

For providing access to a remote-controllable station for testing.

DB1IUA

For helping stuff the budget sink-hole.

Notfunk Deutschland e.V.

For providing space on their booth at HAM RADIO 2026.

Support this work

OpenPulseHF is free and open source under GPL v3. If it is useful to you, a donation funds sustained time on the modem, on-air testing, and better documentation — and keeps the whole stack open, auditable and vendor-independent.