Configuration
Everything past a one-off command lives in a station TOML file. One file describes every SDR, every mode, and every output — and xng station runs the whole site from it in a single process.
Station files
A station file is read by xng station station.toml. It has one mandatory top-level key, a [outputs] block shared by every decoder, and one[[session]] block per decode job. A full annotated example ships in the repo at contrib/station.example.toml, with a hardened systemd unit alongside it.
station-id = "XX-KSEA-1"
[outputs]
feed-airframes = true
jsonl = "/var/log/xng/messages.jsonl"
metrics = "0.0.0.0:9090" # Prometheus
http = "0.0.0.0:8080" # live web dashboard + tar1090 data API
# mqtt = "mqtt://user:pass@broker:1883"
# sbs = "0.0.0.0:30003"
# beast = "0.0.0.0:30005"
# nmea-tcp = "0.0.0.0:10110"
# Two ACARS channels on one RTL-SDR
[[session]]
sdr = "driver=rtlsdr,serial=00000001"
gain = 48
mode = "acars"
sample-rate = 2400000
center = "131.000M"
channels = ["130.025", "131.125", "131.550", "131.725"]
# VDL2 on an Airspy — tuning derives from the mode plan when omitted
[[session]]
sdr = "driver=airspy"
mode = "vdl2"
# ADS-B; receiver position unlocks surface-position decode
[[session]]
sdr = "driver=rtlsdr,serial=00000002"
mode = "adsb"
sample-rate = 2000000
center = "1090M"
channels = ["1090"]
receiver-pos = "47.62,-122.35"Don’t feel like writing tuning by hand? Run xng scan (see the Quickstart) and it prints a ready-to-paste session block. Per-mode channels and sample rates auto-derive from each mode’s built-in plan when you leave them out.
Top level
station-idYour Airframes station identifier. Required.
[outputs]
One block, applied to every session. See Inputs & outputs for what each destination does.
feed-airframesFeed decoded messages to feed.airframes.io. Classic ACARS-only behavior unless an [outputs.airframes] block opts more modes in.
jsonlAppend one normalized JSON message per line to this file.
metricsServe Prometheus metrics (frames, CRC, levels, per-label counts, FEC corrections).
httpServe the live web dashboard and the tar1090 / VRS data API.
mqtt · sbs · beast · nmea-tcpMQTT broker URL; SBS/BaseStation TCP; Beast binary TCP; and NMEA AIVDM TCP server, respectively.
Per-mode Airframes feeding
New in 0.21.0: an optional [outputs.airframes] block gives each mode its own station ID, can auto-suffix derived IDs, and lets you decode a mode locally without feeding it.
[outputs.airframes]
enabled = true
station-id = "XX-KSEA" # base id
auto-suffix = true # → XX-KSEA-ACARS / -VDL2 / -HFDL / -AIS
[outputs.airframes.hfdl]
enabled = false # decode HFDL locally, but do not feed it[[session]]
Repeat one block per decode job. A session reads from an SDR or a recorded file.
sdrSoapySDR-style device string, e.g. driver=rtlsdr,serial=00000001. Use file = "capture.cf32" instead to replay a recording.
modeWhich decoder to run — acars, vdl2, hfdl, ais, adsb, and so on. See Modes.
gainFront-end gain in dB. Omit for hardware AGC.
sample-rate · center · channelsCapture rate (Hz), center frequency, and the channel list. Accept M/k suffixes. Auto-derived from the mode plan when omitted.
receiver-poslat,lon — enables ADS-B surface positions and AIS/APRS own-ship correlation.
feed · airframes-station-idDisable feeding for just this session, or override its station ID.
Config is validated strictly (deny_unknown_fields) — a typo’d key is an error, not a silent no-op. Also note: a sample rate must be an integer multiple of the mode’s channel rate, which is the most common cause of a session that loads but never decodes.
Run it as a service
The repo includes contrib/xng-station.service. Drop in your config and enable it:
sudo cp contrib/xng-station.service /etc/systemd/system/
sudo cp station.toml /etc/xng/station.toml
sudo systemctl enable --now xng-station
xng status # live view of every session