Files
trx-rs/docs/User-Manual.md
sjg ba48de2d30
Sync docs to Wiki / wiki (push) Has been cancelled
Initial commit
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-05-17 23:25:14 +02:00

19 KiB
Raw Permalink Blame History

trx-rs Manual

What trx-rs is

trx-rs is a modular amateur radio control stack written in Rust. It splits hardware access, DSP, transport, and user-facing interfaces into separate components so a radio or SDR can be controlled locally while audio, decoding, and remote control are exposed elsewhere on the network.

In practice, trx-server owns the rig or SDR backend and runs the DSP pipeline, while trx-client connects to it and provides frontends such as the web UI, JSON control, and rigctl-compatible access. The workspace also includes protocol decoders and plugin-based extension points for adding backends and frontends.


Configuration

Both trx-server and trx-client use TOML configuration files. Use --print-config to generate a fully commented example.

File Locations

trx-server lookup order:

  1. --config <FILE>
  2. ./trx-server.toml
  3. ~/.trx-server.toml
  4. ~/.config/trx-rs/server.toml
  5. /etc/trx-rs/server.toml

trx-client lookup order:

  1. --config <FILE>
  2. ./trx-client.toml
  3. ~/.config/trx-rs/client.toml
  4. /etc/trx-rs/client.toml

CLI arguments override config file values.

Environment Variables

  • TRX_PLUGIN_DIRS: additional plugin directories (path-separated), used by both server and client.

Server Options

[general]

Field Type Default Description
callsign string "N0CALL" Station callsign
log_level string trace, debug, info, warn, or error
latitude float Station latitude (-90..90)
longitude float Station longitude (-180..180)

latitude and longitude must be set together or both omitted.

[rig]

Field Type Default Description
model string Backend name (ft817, ft450d, soapysdr)
initial_freq_hz u64 144300000 Startup frequency (must be > 0)
initial_mode string "USB" Startup mode

[rig.access]

Field Type Description
type string serial, tcp, or sdr
port string Serial port path (serial mode)
baud u32 Serial baud rate (serial mode)
host string Remote host (tcp mode)
tcp_port u16 Remote port (tcp mode)
args string SoapySDR device args (sdr mode, e.g. "driver=rtlsdr")

[behavior]

Field Type Default Description
poll_interval_ms u64 500 Rig polling interval
poll_interval_tx_ms u64 100 Polling interval during TX
max_retries u32 3 Connection retry limit
retry_base_delay_ms u64 100 Base retry delay

[listen]

Field Type Default Description
enabled bool true Enable JSON TCP listener
listen ip 127.0.0.1 Bind address
port u16 4530 Bind port

[listen.auth]

Field Type Default Description
tokens string[] [] Allowed auth tokens (empty = no auth)

[audio]

Field Type Default Description
enabled bool true Enable audio streaming
listen ip 127.0.0.1 Bind address
port u16 4531 Bind port
rx_enabled bool true Enable RX audio
tx_enabled bool true Enable TX audio
device string CPAL device name (empty = default)
sample_rate u32 48000 Sample rate (8000192000)
channels u8 1 Channel count (1 or 2)
frame_duration_ms u16 20 Opus frame duration (3, 5, 10, 20, 40, 60)
bitrate_bps u32 24000 Opus bitrate

When audio is enabled, at least one of rx_enabled or tx_enabled must be true.

[sdr]

Field Type Default Description
sample_rate u32 1920000 IQ capture rate in Hz
bandwidth u32 1500000 Hardware IF filter bandwidth in Hz
center_offset_hz i64 100000 Offset from dial to avoid DC spur

[sdr.gain]

Field Type Default Description
mode string "auto" "auto" (hardware AGC) or "manual"
value f64 30.0 Gain in dB (manual mode only)

[sdr.squelch]

Field Type Default Description
enabled bool false Enable software squelch
threshold_db f32 -65.0 Open threshold in dBFS (-140..0)
hysteresis_db f32 3.0 Close hysteresis in dB (0..40)
tail_ms u32 180 Tail hold time in ms (0..10000)

[[sdr.channels]]

Defines virtual receiver channels within the wideband IQ stream. The first channel is the primary channel (controlled by set_freq/set_mode).

Field Type Default Description
id string "" Human-readable label
offset_hz i64 0 Frequency offset from dial
mode string "auto" Demod mode (auto, LSB, USB, CW, AM, FM, WFM, etc.)
audio_bandwidth_hz u32 3000 Post-demod audio bandwidth
fir_taps usize 64 FIR filter tap count
cw_center_hz u32 700 CW tone centre frequency
wfm_bandwidth_hz u32 75000 WFM pre-demod filter bandwidth
decoders string[] [] Decoder IDs for this channel (ft8, wspr, aprs, cw)
stream_opus bool false Stream this channel's audio to clients

Notes:

  • Each decoder ID may appear in at most one channel.
  • At most one channel may set stream_opus = true.
  • Channel IF constraint: |center_offset_hz + offset_hz| < sample_rate / 2.

[pskreporter]

Field Type Default Description
enabled bool false Enable PSKReporter uplink
host string "report.pskreporter.info" Server host
port u16 4739 Server port
receiver_locator string Maidenhead grid (derived from lat/lon if omitted)

[aprsfi]

Field Type Default Description
enabled bool false Enable APRS-IS IGate
host string "rotate.aprs.net" Server host
port u16 14580 Server port
passcode i32 -1 APRS-IS passcode (-1 = auto from callsign)

Notes:

  • [general].callsign must be non-empty when enabled.
  • Only APRS packets with valid CRC are forwarded.
  • Reconnects with exponential backoff (1 s → 60 s) on TCP errors.

[decode_logs]

Field Type Default Description
enabled bool false Enable decoder logging
dir string "$XDG_DATA_HOME/trx-rs/decoders" Log directory
aprs_file string "TRXRS-APRS-%YYYY%-%MM%-%DD%.log" APRS log filename
cw_file string "TRXRS-CW-%YYYY%-%MM%-%DD%.log" CW log filename
ft8_file string "TRXRS-FT8-%YYYY%-%MM%-%DD%.log" FT8 log filename
wspr_file string "TRXRS-WSPR-%YYYY%-%MM%-%DD%.log" WSPR log filename

Files are appended in JSON Lines format. Supported date tokens: %YYYY%, %MM%, %DD% (UTC).

Multi-Rig Configuration

Use [[rigs]] arrays instead of the flat [rig] section for multi-rig setups:

[[rigs]]
id    = "ft817_0"
name  = "HF Transceiver"
[rigs.rig]
model = "ft817"
[rigs.rig.access]
type  = "serial"
path  = "/dev/ttyUSB0"
baud  = 9600

[[rigs]]
id    = "sdr_0"
name  = "VHF/UHF SDR"
[rigs.rig]
model = "soapysdr"
[rigs.rig.access]
type  = "sdr"
args  = "driver=rtlsdr"

When [[rigs]] is present it takes priority over the flat [rig] section. Rigs without an explicit id get auto-generated IDs like ft817_0, soapysdr_1.

Client Options

[general]

Field Type Default Description
callsign string "N0CALL" Station callsign
log_level string trace, debug, info, warn, or error

[remote]

Field Type Default Description
url string Server address (e.g. localhost:4530)
poll_interval_ms u64 750 State poll interval

[remote.auth]

Field Type Default Description
token string Auth token (must not be empty if set)

[frontends.http]

Field Type Default Description
enabled bool true Enable web UI
listen ip 127.0.0.1 Bind address
port u16 8080 Bind port

[frontends.rigctl]

Field Type Default Description
enabled bool false Enable Hamlib rigctl
listen ip 127.0.0.1 Bind address
port u16 4532 Bind port

[frontends.http_json]

Field Type Default Description
enabled bool true Enable JSON-over-TCP
listen ip 127.0.0.1 Bind address
port u16 0 Bind port (0 = ephemeral)
auth.tokens string[] [] Allowed auth tokens

[frontends.audio]

Field Type Default Description
enabled bool true Enable audio client
server_port u16 4531 Server audio port
bridge.enabled bool false Enable local CPAL audio bridge
bridge.rx_output_device string Local playback device
bridge.tx_input_device string Local capture device
bridge.rx_gain float 1.0 RX playback gain
bridge.tx_gain float 1.0 TX capture gain

The bridge is intended for WSJT-X integration via virtual audio devices (ALSA loopback on Linux, BlackHole on macOS).

CLI Override Summary

trx-server: --config, --print-config, --rig, --access, --callsign, --listen, --port. SDR options are file-only.

trx-client: --config, --print-config, --url, --token, --poll-interval, --frontend, --http-listen, --http-port, --rigctl-listen, --rigctl-port, --http-json-listen, --http-json-port, --callsign.


Authentication

The HTTP frontend supports optional passphrase-based authentication with two roles:

  • rx — read-only access (monitoring, audio, decode streams)
  • control — full access (frequency, mode, PTT, and all settings)

Configuration

[frontends.http.auth]
enabled = false
rx_passphrase = "rx-only-passphrase"
control_passphrase = "full-control-passphrase"
tx_access_control_enabled = true
session_ttl_min = 480
cookie_secure = false      # true if served via HTTPS
cookie_same_site = "Lax"   # Strict|Lax|None

When enabled = false (the default), all auth is bypassed and the UI behaves as before. When enabled, at least one passphrase must be set.

Behaviour

  • On login, the server issues an HttpOnly session cookie.
  • Sessions are in-memory; a server restart invalidates all sessions.
  • Rate limiting is applied per IP to mitigate brute-force attempts.
  • When tx_access_control_enabled = true, TX/PTT controls are hidden and rejected for unauthenticated or rx-role users.

Routes

Endpoint Method Description
/auth/login POST Submit { "passphrase": "..." }
/auth/logout POST Clear session
/auth/session GET Check current session/role

Protected routes require at least rx role. Control routes (set frequency, mode, PTT, etc.) require control role.

Frontend Flow

  1. On load, the UI calls /auth/session.
  2. If unauthenticated, a login screen is shown.
  3. On successful login, the normal UI loads.
  4. rx users see a read-only interface; control users get full controls.
  5. If a session expires mid-use, streams stop and the login screen returns.

Transport Security

There is no built-in TLS. For remote access, place trx-rs behind a TLS-terminating reverse proxy (nginx, Caddy) and set cookie_secure = true.


Background Decoding Scheduler

The scheduler automatically retunes the rig to pre-configured bookmarks when no users are connected to the HTTP frontend. It runs as a background task inside trx-frontend-http, polling every 30 seconds.

Modes

Disabled (default)

Scheduler is inactive. The rig is not touched automatically.

Grayline

Retunes around the solar terminator (day/night boundary).

The user provides:

  • Station latitude and longitude (decimal degrees)
  • Optional transition window width (minutes, default 20)
  • Bookmark IDs for four periods:
    • Dawn — window around sunrise (sunrise ± window_min/2)
    • Day — after dawn until dusk
    • Dusk — window around sunset (sunset ± window_min/2)
    • Night — after dusk until next dawn

Period precedence (most specific wins): Dawn > Dusk > Day > Night.

If no bookmark is assigned to a period, the rig is not retuned for that period.

Sunrise/sunset is computed inline using the NOAA simplified algorithm. Polar regions (midnight sun / polar night) fall back to Day/Night accordingly.

TimeSpan

Retunes according to a list of user-defined time windows (UTC).

Each entry specifies:

  • start_hhmm — start of window (e.g. 600 = 06:00 UTC)
  • end_hhmm — end of window (e.g. 700 = 07:00 UTC)
  • bookmark_id — bookmark to apply
  • label — optional human-readable description

Windows that span midnight (end_hhmm < start_hhmm) are supported. When multiple entries overlap, the first match (by list order) wins.

Storage

Configuration is stored in PickleDB at ~/.config/trx-rs/scheduler.db.

Keys: sch:{rig_id} → JSON SchedulerConfig.

HTTP API

All read endpoints are accessible at the Rx role level. Write endpoints require the Control role.

Method Path Description
GET /scheduler/{rig_id} Get scheduler config for a rig
PUT /scheduler/{rig_id} Save scheduler config (Control only)
DELETE /scheduler/{rig_id} Reset config to Disabled (Control only)
GET /scheduler/{rig_id}/status Get last-applied bookmark and next event

Activation Logic

Every 30 seconds the scheduler task checks:

  1. No SSE clients connected
  2. Active rig has a non-Disabled scheduler config
  3. Current UTC time matches a scheduled window or grayline period
  4. If the matching bookmark differs from last applied, send SetFreq + SetMode

The scheduler does not revert changes when users reconnect.

Web UI

A dedicated tab with a clock icon provides:

  • Rig selector (read-only, shows active rig)
  • Mode picker: Disabled / Grayline / TimeSpan
  • Grayline section: lat/lon inputs, transition window slider, four bookmark selectors
  • TimeSpan section: table of entries with start/end times, bookmark, label
  • Status card: last applied bookmark name and timestamp
  • Save button (Control role only)

SDR Noise Blanker

The noise blanker suppresses impulse noise (clicks, pops, ignition interference) on raw IQ samples before any mixing or filtering takes place. It works by tracking a running RMS level of the signal and replacing any sample whose magnitude exceeds threshold x RMS with the last known clean sample.

Configuration (server-side)

The noise blanker is configured per rig. In a multi-rig setup each [[rigs]] entry has its own [rigs.sdr.noise_blanker] section:

[[rigs]]
id = "hf"

[rigs.rig]
type = "sdr"

[rigs.sdr.noise_blanker]
enabled = true
threshold = 10.0     # 1  100; lower = more aggressive blanking

For the legacy single-rig (flat) config the path is [sdr.noise_blanker]:

[sdr.noise_blanker]
enabled = true
threshold = 10.0
Field Type Default Range Description
enabled bool false Turn the noise blanker on or off.
threshold float 10.0 1 100 Multiplier applied to the running RMS. A sample whose magnitude exceeds this multiple is replaced. Lower values blank more aggressively; higher values only catch strong impulses.

The noise blanker is off by default.

Choosing a threshold

The threshold controls how aggressively the blanker suppresses impulses. A value of N means: blank any sample whose magnitude exceeds N times the running average signal level.

Threshold Behavior Use case
3 5 Very aggressive — blanks frequently Dense impulse noise (motors, power lines, LED drivers nearby)
8 12 Moderate — catches clear spikes without touching normal signals Typical HF conditions with occasional ignition or switching noise
15 25 Conservative — only blanks strong impulses well above the noise floor Light interference, or when you want minimal artifacts on weak signals
30 100 Very light — rarely triggers Faint, infrequent clicks; mostly a safety net

Start at 10 (the default) and adjust while listening:

  • If impulse noise is still audible, lower the threshold.
  • If weak signals sound choppy or distorted, raise it — the blanker may be mistaking signal peaks for noise.
  • On bands with steady atmospheric noise (e.g. 160 m / 80 m), a threshold of 5 8 usually works well.
  • On quieter VHF/UHF bands where the noise floor is low, values of 15 25 avoid false triggers from strong signals.

Web UI

When the server reports noise-blanker support, two controls appear in the SDR Settings row of the web interface:

  • Noise Blanker checkbox — enables or disables the blanker in real time.
  • NB Threshold number input (1100) with a Set button — adjusts the detection threshold. Press Enter or click Set to apply.

Both controls stay hidden until the server sends filter state containing NB fields, so they only appear when connected to an SDR backend.

HTTP API

POST /set_sdr_noise_blanker?enabled=true&threshold=10
Parameter Type Required Description
enabled bool yes true or false
threshold float yes Value between 1 and 100

How it works

The blanker runs on every IQ block (4096 samples) before the mixer stage in the DSP pipeline:

  1. For each sample, compute magnitude² (re² + im²).
  2. Compare against threshold² × mean_sq (the exponentially-smoothed running mean of magnitude²).
  3. If the sample exceeds the threshold, replace it with the previous clean sample.
  4. Otherwise, update the running mean with smoothing factor α = 1/128 and store the sample as the last clean value.

Because the blanker operates on raw IQ before frequency translation, it removes impulse noise across the entire captured bandwidth regardless of the tuned channel offset.