Jump to content

CLI: Difference between revisions

From SimTX Wiki
Create the CLI page
 
m Protected "CLI" ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite))
 
(No difference)

Latest revision as of 20:22, 27 October 2026

CLI

simtx-cli is the headless companion to SimTX Transmitter. It can transmit audio files, generated white noise, or GNU Radio IQ from one of your persistent stations, serve the simulated world over rtl-tcp, manage your stations and satellites, predict satellite passes, sign in to a SimTX account, query a server, and check for updates. It speaks SimTX client protocol v25.

This document covers every command, option, environment variable, preset key, output format, and exit condition.

Contents


Quick start

# Sign in once (stores credentials in the OS keyring by default)
simtx-cli login

# Create a ground station (or use the app's station editor)
simtx-cli --name Home --callsign N0CALL --lat 52.37 --lon 4.90 \
  --antenna-type efhw stations create

# List your stations (ids, antennas, rigs)
simtx-cli stations

# Transmit a WAV file on 40 m USB from your station "Home"
simtx-cli --server-id myserver --station Home audio argon.simtx.net 5556 \
  7100000 message.wav USB

# Transmit generated white noise on 80 m AM
simtx-cli --server-id myserver --station Home white argon.simtx.net 5556 3700000 AM_DSB_FC

# Feed IQ from GNU Radio into SimTX on a local loopback port
simtx-cli --server-id myserver --station Home iq argon.simtx.net 5556 7100000 4567

# Serve the simulated world to SDR++ over rtl-tcp (ad-hoc receiver)
simtx-cli --server-id myserver rx argon.simtx.net 5556 1234 N0CALL 7100000 1024000

# ... or receive at a persistent station (yours or a PUBLIC one)
simtx-cli --server-id myserver --station Home rx argon.simtx.net 5556 1234

# Launch a satellite and predict its passes over your station
simtx-cli --name Cube --callsign CUBE1 --altitude-km 550 --inclination 97.6 stations launch
simtx-cli --sat Cube --station Home passes

# One-shot queries
simtx-cli stations argon.simtx.net 5556
simtx-cli health argon.simtx.net 5556
simtx-cli catalog argon.simtx.net 5556

Command summary

Command Purpose
version Print the local build version. No network access.
update Print update metadata and whether an update is available.
login Sign in with the device authorization flow and store credentials.
audio Stream a local audio file from one of your stations.
white, whitenoise Transmit a generated white-noise signal from one of your stations.
iq, gnuradio Stream IQ from a local GNU Radio TCP sink via one of your stations.
rx Serve the simulated world over rtl-tcp (ad-hoc or station-bound).
stations List/create/launch/delete/deorbit your stations, or (with <host> <port>) fetch a server's live station list.
passes Predict passes of a satellite over a ground station.
health Fetch a server health report as JSON.
catalog, catalogue Fetch the catalog listing as JSON.
serve Run a multiplexed NDJSON session engine for game integration.

All commands except version and update require an exact version match with the update service. If the CLI is outdated or the update service is unreachable, the command refuses to run. Upgrade the CLI, then retry.

Unknown commands fail before any network access.


Output conventions

  • Human-readable logs and progress messages always go to stderr.
  • Machine-readable results go to stdout.
  • Query results and update metadata are printed as one compact line beginning

with J::

  simtx-cli stations argon.simtx.net 5556 | grep '^J:' | cut -c3- | jq .
  • version prints plain text, not JSON.
  • login prints the verification URL and code as plain text.
  • Transmitter commands (audio, white, iq) print nothing to stdout unless

--telemetry-json is enabled.


Global options

These options work before or after the subcommand. Explicit flags win over environment variables, which win over preset values, which win over built-in defaults.

Option Default Meaning
--lat DEG 52.3676 Station latitude, -90..90. Receiver default is 47.50; see rx.
--lon DEG 4.9041 Station longitude, -180..180. Receiver default is 19.04; see rx.
--power W 100 Transmit power in watts.
--sample-rate HZ auto (0) Transmission sample rate (0 = automatic wire rate based on mode/bandwidth).
--rate HZ unset Alias for --sample-rate when greater than zero.
--gain GAIN 0.9 Transmission audio/carrier gain (or from preset source.gain).
--duty-cycle DC 0.5 PA duty cycle 0.0..1.0 (or from preset source.dutyCycle).
--radio-preset P empty Radio model preset override (GENERIC_HF, CLEAN_LAB, etc.).
--debug off Verbose logging. Also logs every telemetry frame instead of only the first.
--telemetry-json off Stream every transmitter telemetry frame to stdout as JSON.
--plain-tcp off Connect using unencrypted TCP instead of TLS on custom server ports.
--account-url URL official service Account service to use for login and tickets.
--server-id ID empty Game server ID used to mint an authenticated ticket. Requires login.
--ticket T empty Use a literal game ticket instead of account authentication.
--auth-file PATH empty Store credentials in a plaintext JSON file instead of the OS keyring.
--preset FILE empty Load defaults from a preset file.
--save-preset OUT empty Write the effective settings to a preset file and continue.

Station options

Option Default Meaning
--station REF SIMTX_STATION Persistent station id or name (or callsign). Required for TX; makes rx station-bound.
--antenna REF first antenna Station antenna id or name.
--rig REF none Station rig id or name. Without a rig the generic HF defaults and the account PA limit apply.
--track ID none Point the antenna at and Doppler-correct toward this station id (usually a satellite).
--name N — stations create / stations launch: station name.
--callsign C — stations create / stations launch: station callsign.
--visibility V PUBLIC stations create / stations launch: PUBLIC or UNLISTED.
--altitude-km KM 550 stations launch: circular orbit altitude above the 6371 km sphere.
--inclination DEG 97.6 stations launch: orbit inclination.
--raan DEG 0 stations launch: right ascension of the ascending node.
--sat REF — passes: satellite id or name (yours or the public catalog).
--hours H 24 passes: prediction window (max 168).
--min-el DEG 0 passes: minimum elevation.
--yes off stations deorbit: fire the burn without the confirmation question.

Boolean options accept --flag or --flag=true / --flag=false.

Flags can appear before or after the subcommand. Use -- to stop flag parsing before positional values that begin with -.

Antenna options

Antenna options describe the inline antenna of ad-hoc receivers (rx without --station) and the first antenna of stations create. Transmissions always use the antenna stored on the station, so these options do not apply to audio, white or iq.

Option Default Meaning
--antenna-type TYPE default EFHW Antenna template. See the list below.
--antenna-height M 10 Antenna height in metres.
--wire-length M 0 Wire or element length in metres. 0 selects a resonant length.
--azimuth DEG 0 Azimuth in degrees clockwise from north.
--feedline-loss DB 1.0 Feedline loss in dB.
--nec-deck PATH empty Path to a custom NEC-2 deck file (.nec). Alias: --nec.

Antenna types (case-insensitive, - and _ are interchangeable):

dipole, inverted-v, vertical, yagi-3el, yagi-5el, loop, beverage, longwire, efhw.

Leaving --antenna-type empty uses the default shortwave EFHW antenna.

Built-in antenna templates automatically synthesize a resonant NEC-2 deck at the transmission frequency so servers requiring NEC models radiate properly without manual deck configuration. Supplying --nec-deck (or --nec) overrides the built-in template with a custom NEC-2 deck.

Identity and authentication

  • With no --server-id and no --ticket, sessions are anonymous. Anonymous

sessions are intended for local or test servers and may be rejected by official servers. Anonymous sessions are receive-only: transmitting needs a station-bound ticket.

  • Transmitter tickets are minted bound to --station (and the resolved

antenna and rig); every reconnect mints a fresh bound ticket. A literal --ticket must already be bound to the same station, and then --station, --antenna and --rig must be ids.

  • Station management (stations create/launch/delete/deorbit) needs the

station.manage scope. Sessions signed in before stations existed lack it; run simtx-cli login again (after simtx-cli has been updated) when the account service answers scope_required.

  • --server-id mints a fresh game ticket through the signed-in account. Run

simtx-cli login first.

  • --ticket passes a literal ticket. For long-lived sessions such as rx,

a reused single-use ticket may stop working after the first reconnect.

  • --auth-file PATH (or SIMTX_AUTH_FILE) stores the account session in a

plaintext JSON file with owner-only permissions. This is intended for headless servers without an OS keyring. Anyone who can read that file can act as the signed-in device.

Preset precedence and positional filling

Precedence is:

  1. Explicit command-line flags and positional arguments.
  2. SIMTX_* environment variables.
  3. Values loaded with --preset.
  4. Built-in defaults.

Missing trailing positional arguments are filled left-to-right from preset keys. Leading positionals cannot be skipped. For example, if a preset provides cli.audioFile and rf.modulation, you can supply only the connection and station positionals:

simtx-cli --preset station.stxpreset audio argon.simtx.net 5556 7100000

The audio file, modulation, bandwidth, and low cut then come from the preset. A preset containing every required positional also allows a bare command:

simtx-cli --preset station.stxpreset audio

Environment variables

Variable Equivalent option
SIMTX_TX_LAT --lat
SIMTX_TX_LON --lon
SIMTX_TX_POWER --power
SIMTX_SAMPLE_RATE --sample-rate
SIMTX_PLAIN_TCP --plain-tcp
SIMTX_FORCE_PLAIN_TCP --plain-tcp
SIMTX_ACCOUNT_URL --account-url
SIMTX_AUTH_FILE --auth-file
SIMTX_SERVICE_CREDENTIAL --service-credential (receiver only)
SIMTX_STATION --station

Environment variables apply to every command. The receiver location (47.50, 19.04) only applies when none of --lat, --lon, SIMTX_TX_LAT, SIMTX_TX_LON, or the preset latitude/longitude keys are set.


Transmitter commands

All transmitter commands run on one of your persistent stations: --station is required (else the command fails with "transmitting needs --station"), together with --server-id (or a station-bound --ticket). The station dictates callsign, location, antenna and pointing, the PA cap and the rig's realism; the session chooses modulation, frequency, power (up to the cap), source, sample rate, gain, bandwidth, low cut, tone and duty. Only the PA knobs you give (a radio preset, positional overrides or rf.* preset keys) are sent; with a rig the server takes final drive, audio compression, frequency offset and AM index from them and everything else from the rig. Ad-hoc transmission (inline callsign/location/antenna) no longer exists.

Transmitter commands use the global power and sample-rate options and run until interrupted with Ctrl-C (SIGINT) or SIGTERM or until the session ends. The server confirms the station with a session status log line (BOUND, station name, callsign, PA cap); rejections carry the server's reason and code (for example FORBIDDEN for a station that is not yours or a ticket bound to another station).

Ports must be in 1..65535. Frequency must be positive and power must be non-negative. Sample rates at or below zero fall back to 48000. Bandwidth values below the modulation minimum are raised to the minimum; values above 10,000,000 Hz are capped.

A session can be queued. The CLI logs queue position, acceptance, live update acknowledgements, kicks, and telemetry.

audio

simtx-cli [global flags] --station S audio <host> <port> <freqHz> <file> \
  [MOD] [BW] [LOWCUT] [PRESET|DRIVE ...]

Streams a local audio file.

  • <host> <port> — SimTX server address.
  • <freqHz> — transmit frequency in Hz; must be positive.
  • <file> — local WAV, AIFF, or MP3 file.
  • [MOD] — modulation; defaults to AM_DSB_FC.
  • [BW] — occupied bandwidth in Hz; defaults to the modulation's typical

bandwidth.

  • [LOWCUT] — audio low cut in Hz; defaults to the modulation's typical

low cut.

  • [PRESET|DRIVE ...] — optional radio preset and PA knobs; see

Radio presets and PA knobs.

The file is streamed once. The session ends when the file ends, when the server disconnects, or when you interrupt the command. For WFM, only the first channel is used.

Examples:

simtx-cli --server-id myserver --station Home audio argon.simtx.net 5556 7100000 \
  message.wav USB

simtx-cli --server-id myserver --station Home --antenna Yagi --rig IC-7300 \
  audio argon.simtx.net 5556 145500000 fm.wav NFM 12500 200

simtx-cli --server-id myserver --station Home audio argon.simtx.net 5556 7100000 \
  message.wav LSB 2700 350 ICOM_IC_7300

white / whitenoise

simtx-cli [global flags] --station S white <host> <port> <freqHz> \
  [MOD] [BW] [LOWCUT] [PRESET|DRIVE ...]

Transmits a generated white-noise signal. No audio samples are sent by the client; the signal is generated for the session. Positional arguments are the same as audio without the file.

simtx-cli --server-id myserver --station Home white argon.simtx.net 5556 3700000 AM_DSB_FC 10000 80

iq / gnuradio

simtx-cli [global flags] --station S iq <host> <port> <freqHz> <localIqPort> \
  [sampleRate] [BW] [PRESET|DRIVE ...]

Streams IQ from a local GNU Radio TCP sink into a transmitter session.

  • <localIqPort> — TCP port on 127.0.0.1 where GNU Radio connects.
  • [sampleRate] — IQ sample rate. Overrides the global sample rate.
  • [BW] — transmit bandwidth in Hz; defaults to the sample rate.
  • [PRESET|DRIVE ...] — optional radio preset and PA knobs.

The input stream must be interleaved little-endian 32-bit float I/Q pairs (gr_complex). The CLI listens only on loopback. One producer can stream at a time; a restarted flowgraph can reconnect and take over.

Examples:

simtx-cli --server-id myserver --station Home iq argon.simtx.net 5556 7100000 4567

simtx-cli --server-id myserver --station Home iq argon.simtx.net 5556 7100000 4567 \
  96000 96000 GENERIC_HF

In GNU Radio, use a TCP sink pointed at 127.0.0.1:4567 with the matching sample rate and complex float output.

Modulations

Modulation names are case-insensitive. The default is AM_DSB_FC.

Name Common name Typical bandwidth Typical low cut
AM_DSB_FC AM double sideband, full carrier 10000 Hz 80 Hz
AM_DSB_SC AM double sideband, suppressed carrier 6000 Hz 100 Hz
USB Upper sideband 2700 Hz 350 Hz
LSB Lower sideband 2700 Hz 350 Hz
CW Continuous wave 150 Hz 300 Hz
NFM Narrow FM 12500 Hz 200 Hz
WFM Wide FM 180000 Hz 30 Hz
IQ External complex IQ 48000 Hz 0 Hz
WSPR Weak Signal Propagation Reporter 6 Hz 0.5 Hz
FT8 Franke-Taylor FT8 50 Hz 20 Hz

Radio presets and PA knobs

The optional trailing arguments start with a radio preset name, followed by up to 15 numeric overrides in the order shown below. The preset name is case-insensitive and -/_ are interchangeable. Missing or unparseable values use the preset's defaults.

Which knobs are sent: naming a radio preset (positionally, with --radio-preset or rf.radioPreset) sends all 15; otherwise only the positional values and rf.* preset keys you give are sent and the rest come from the station's rig (or the generic HF preset when the session has no rig).

Preset Character
GENERIC_HF Generic HF transceiver
CLEAN_LAB Clean lab exciter
ICOM_IC_7300 Icom IC-7300 class HF rig
ICOM_IC_705 Icom IC-705 class QRP SDR
ICOM_IC_7610 Icom IC-7610 class base SDR
YAESU_FTDX10 Yaesu FTDX10 class HF SDR
ELECRAFT_KX3 Elecraft KX2/KX3 class QRP
YAESU_FT_817 Yaesu FT-817/818 class QRP
KENWOOD_TS_590 Kenwood TS-590 class HF rig
XIEGU_G90 Xiegu G90 class portable HF
USDX_QRP (tr)uSDX / uSDX QRP Class-E
MIL_MANPACK Rugged HF manpack
VINTAGE_TUBE Vintage tube SSB
COLLINS_KWM2 Collins KWM-2 class tube SSB
BOAT_ANCHOR_AM Vintage AM boatanchor
CB_EXPORT_AM Overdriven export AM/SSB
VHF_HANDHELD_FM Budget VHF/UHF handheld FM
HOME_BREW_CLASS_E_AM Homebrew Class-E AM
DIRTY_DRIVER Overdriven driver / poor ALC

Numeric override order:

# Knob Unit
1 Final drive dB
2 PA headroom dB
3 Audio compression dB
4 PA softness —
5 PA AM/PM degrees
6 Carrier leak dB
7 Opposite-sideband suppression dB
8 Frequency offset Hz
9 Mains hum level dB
10 Mains hum frequency Hz
11 AM modulation index —
12 PA supply sag dB
13 TX noise floor dB
14 Oscillator phase noise dB
15 CW key rise time ms

Example: ... USB 2700 350 ICOM_IC_7300 1.5 9 selects the IC-7300 preset and overrides final drive to 1.5 dB and PA headroom to 9 dB.

Telemetry

Transmitter sessions receive server-reported telemetry. By default the first frame is logged at info level and later frames at debug level. Use --debug to log every frame.

With --telemetry-json, every frame is also written to stdout as:

J:{"type":"telemetry","telemetry":{...}}

The full telemetry object:

JSON field Meaning
Sequence Frame sequence number.
ServerTimeMillis Server timestamp in milliseconds.
ActualFrequencyHz Actual transmit frequency.
RequestedPowerWatts Requested power.
ForwardPowerWatts Forward power at the finals.
DeliveredPowerWatts Delivered power.
ReflectedPowerWatts Reflected power.
VSWR Standing-wave ratio.
MismatchLossDb Mismatch loss in dB.
TransformerLossDb Transformer loss in dB.
FoldbackLossDb Foldback loss in dB.
AlcDb Automatic level control, in dB.
BufferSeconds Server-side buffer depth in seconds.
FinalsTemperatureC Finals temperature in °C.
ThermalFoldbackDb Thermal foldback in dB.
PaCompressionDb PA compression in dB.
PaHeatWatts PA heat dissipation in watts.
PaEfficiencyPercent PA efficiency in percent.
PaThermalFaultActive True when the PA thermal fault is active.

Human-readable log lines use these short field names: seq, serverTimeMillis, freqHz, requestedW, forwardW, deliveredW, reflectedW, vswr, mismatchLossDb, transformerLossDb, foldbackLossDb, alcDb, bufferS, tempC, thermalFoldbackDb, paCompressionDb, paHeatW, paEfficiencyPct, paThermalFault.

Example:

simtx-cli --telemetry-json --server-id myserver --station Home audio argon.simtx.net 5556 \
  7100000 message.wav USB \
  | grep '^J:' | cut -c3- | jq '.telemetry.FinalsTemperatureC'

Receiver command: rx

simtx-cli [global flags] rx <simtxHost> <simtxPort> [rtlTcpPort] [callsign] \
  [centerHz] [bandwidthHz]

Serves the simulated world over rtl-tcp so an SDR application such as SDR++ can tune around the requested center frequency and bandwidth. Point the SDR client at the local rtl-tcp endpoint.

Defaults:

Positional Default
rtlTcpPort 1234
callsign RTLTCP
centerHz 7100000
bandwidthHz 1024000
location Budapest (47.50, 19.04)

Without --station the receiver is ad-hoc: it uses the antenna options, the callsign positional and --lat/--lon, and works anonymously. With --station (your own station by id or name, or someone else's PUBLIC station by id) every upstream session mints a fresh receiver ticket bound to that station (needs --server-id, or a station-bound --ticket): the station's location, antenna and rig apply and the callsign positional and antenna options are ignored. --antenna, --rig and --track work as for TX. --timestamped sets the v25 timestamped flag of the receiver session (there is no separate timestamped role any more). The upstream connection is to the SimTX server given by <simtxHost> <simtxPort>. The callsign follows the same rules as transmitter sessions. The center frequency must be zero or greater, the bandwidth must be positive, and both ports must be in 1..65535.

simtx-cli --server-id myserver rx argon.simtx.net 5556 1234 N0CALL 7100000 1024000

Then connect SDR++ to 127.0.0.1:1234.

Receiver options

Option Default Meaning
--hardware-profile PATH generic R820T RTL tuner/oscillator profile file.
--service-credential VALUE empty Service credential for authenticated receiver access. Requires --server-id.
--service-credential-file PATH empty Read the service credential from an owner-only (0600) file.
--timestamped off Use the timestamped receiver mode.
--rx-read-timeout-ms N 5000 Upstream read timeout, 1..60000. 0 uses the default.
--rx-max-retries N 3 Upstream attempts before failing. -1 retries forever. 0 uses the default.
--rx-retry-base-ms N 250 Base retry delay, 1..5000. Retries back off with jitter. 0 uses the default.
--rx-jitter-ms N 300 Jitter buffer, 1..2000. Negative disables prefetch. 0 uses the default.
--rx-auto-stepdown off Narrow the upstream request after repeated stalls.
--rx-no-lossy-link off Disable all lossy-link tuning at once: synchronous reads, fixed backoff, and resets on reconnect.
--rx-listen-host IP 127.0.0.1 rtl-tcp bind address. Must be an IP literal.

WARNING: --rx-listen-host 0.0.0.0 exposes the bridge to the LAN. The rtl-tcp bridge is unauthenticated, so anyone who can reach the port can retune it.

Service credential notes:

  • A service credential requires --server-id.
  • --service-credential can also come from SIMTX_SERVICE_CREDENTIAL.
  • --service-credential-file must be readable only by its owner (chmod 600)

on Unix-like systems.

  • Service credentials are never written to preset files.
  • With a credential or account identity, the receiver asks for a fresh ticket

on each upstream reconnect.

Press Ctrl-C to stop the bridge.


Station commands: stations, passes

simtx-cli [global flags] stations [list]
simtx-cli [global flags] stations show <id|name>
simtx-cli [global flags] --name N --callsign C --lat L --lon L [antenna options] \
  [--visibility PUBLIC|UNLISTED] stations create
simtx-cli [global flags] --name N --callsign C [--altitude-km KM] [--inclination DEG] \
  [--raan DEG] [--visibility PUBLIC|UNLISTED] stations launch
simtx-cli [global flags] stations delete <id|name>
simtx-cli [global flags] [--yes] stations deorbit <id|name>
simtx-cli [global flags] --sat <id|name> --station <id|name> [--hours H] [--min-el DEG] passes

These commands talk to the account service through your signed-in session (run login first) and print one J: line:

Command type Payload
stations, list my-stations count, stations: [StationView]
show station station: StationView
create station.created station: StationView
launch station.launched station: StationView (orbit epoch = launch)
delete station.deleted stationId
deorbit station.deorbiting station: StationView (state: DEORBITING, deorbit)
passes passes satellite, station, from, to, reentryAt, passes

StationView is the account service's JSON (camelCase): stationId, kind (GROUND/SATELLITE), state, name, callsign, visibility, revision, limits, location (ground) or orbit + attitude (satellite), antennas (antennaId, name, ordinal, type, heightMeters, … mount, pointing) and rigs (rigId, name, paMaxWatts, noiseFigureDb, tx).

  • create makes a ground station with one antenna built from the antenna

options (default EFHW) at --lat/--lon. Add more antennas and rigs, or edit them, in the app's station editor.

  • launch puts a satellite on a circular simtx-j2-v1 orbit (--altitude-km

above the 6371 km sphere, --inclination, --raan; the mean anomaly is 0 at the launch instant), nadir-pointing with one turnstile antenna. The perigee must be at least 200 km and the apogee at most 40,000 km. Satellites are sealed at launch: only name, callsign, visibility and limits can change afterwards; deorbit and relaunch to change anything else. Use the app's mission designer for custom orbits, attitudes and antennas.

  • deorbit fires a real retrograde burn. It first fetches a preview and

prints the burn (delta-v) and when and where the satellite will reenter, then asks Fire the deorbit burn? This cannot be undone. [y/N] on stderr; only y/yes fires it. --yes skips the question (needed when stdin is not a terminal). The satellite becomes DEORBITING on its falling orbit, stays fully usable (tickets, sessions, passes) and burns up at deorbit.reentryAt (80 km), after which it is DEORBITED. StationView carries `deorbit: {burnAt, reentryAt, reentryLocation: {latitudeDeg, longitudeDeg}, deltaVMps}. A second burn fails with already_deorbiting`.

  • delete removes ground stations (satellites must be deorbited and reenter

first).

  • passes predicts locally with the Go port of simtx-j2-v1 (the server is

authoritative). Each pass has aos, tca, los (RFC 3339 UTC), maxElevationDeg, aosAzimuthDeg, losAzimuthDeg and maxDopplerHzPerMHz (multiply by the carrier in MHz). For a deorbiting satellite the window ends at reentryAt (also printed), a pass in progress at reentry ends there, and after reentry the list is empty.

Without arguments stations lists your stations; stations <host> <port> is the live server query described under Query commands.


Account command: login

simtx-cli [--account-url URL] [--auth-file PATH] login

Starts a device authorization flow:

  1. The CLI prints a verification URL and a short code.
  2. Open the URL and approve the code in a browser.
  3. The CLI stores the account session and exits.

If a usable session is already stored, login reports that you are signed in and exits successfully.

By default the session is stored in the OS keyring. With --auth-file PATH, it is stored in a plaintext JSON file with owner-only permissions. The CLI logs a warning when the plaintext store is used.

If the code expires, run login again.


Query commands

simtx-cli [global flags] stations <host> <port>
simtx-cli [global flags] health <host> <port>
simtx-cli [global flags] catalog <host> <port>

Queries are one-shot. Each opens a session to the server, requests the data, prints a single J: line, and exits. The request waits up to 10 seconds for a response.

catalogue is an alias for catalog.

Queries accept the identity options: anonymous by default, --ticket for a literal ticket, or --server-id to mint a ticket through the signed-in account. If the server bans the session, the command fails with the server's message.

stations output

stations <host> <port> lists what the server sees: persistent stations with their live sessions, every active PUBLIC satellite (even with nobody attached; latDeg/lonDeg is the sub-satellite point), and ad-hoc sessions (kind ADHOC, no stationId, one session). UNLISTED stations appear as ad-hoc sessions with the callsign only.

{
  "type": "stations",
  "host": "argon.simtx.net",
  "port": 5556,
  "capturedAtMs": 1792843466892,
  "count": 1,
  "stations": [
    {
      "stationId": "…",
      "kind": "GROUND",
      "callsign": "N0CALL",
      "name": "Home",
      "ownerHandle": "n0call",
      "visibility": "PUBLIC",
      "latDeg": 52.37,
      "lonDeg": 4.9,
      "altitudeMeters": 0,
      "revision": 3,
      "sessions": [
        {
          "sessionId": "…",
          "role": "TX",
          "antennaId": "…",
          "callsign": "N0CALL",
          "frequencyHz": 7100000,
          "modulation": "LSB",
          "powerWatts": 100,
          "bandwidthHz": 2700,
          "latDeg": 52.37,
          "lonDeg": 4.9
        }
      ]
    }
  ]
}

address appears on sessions only for moderation sessions.

health output

{
  "type": "health",
  "host": "argon.simtx.net",
  "port": 5556,
  "health": {
    "capturedAtEpochMillis": 0,
    "status": "OK",
    "uptimeMillis": 0,
    "connectedClients": 0,
    "connectedStations": 0,
    "activeTransmitters": 0,
    "receiverGroups": 0,
    "acceptorAlive": true,
    "worldTick": 0,
    "processCpuLoad": 0.0,
    "systemCpuLoad": 0.0,
    "heapUsedBytes": 0,
    "heapMaxBytes": 0,
    "transmitters": [
      {
        "stationUuid": "…",
        "callsign": "N0CALL",
        "address": "…",
        "sourceType": "AUDIO_STREAM",
        "frequencyHz": 7100000,
        "modulation": "LSB",
        "sampleRate": 48000
      }
    ],
    "receivers": [
      {
        "stationUuid": "…",
        "callsign": "RTLTCP",
        "address": "…",
        "centerFrequencyHz": 7100000,
        "sampleRate": 1024000,
        "wideband": false,
        "framesWritten": 0,
        "framesDropped": 0
      }
    ]
  }
}

catalog output

{
  "type": "catalog",
  "host": "argon.simtx.net",
  "port": 5556,
  "count": 1,
  "entries": [
    {
      "id": 1,
      "kind": "…",
      "name": "…",
      "description": "…",
      "author": "…",
      "sizeBytes": 0,
      "fileCount": 0,
      "types": [],
      "categories": [],
      "packageId": "…",
      "packageVersion": "…",
      "dependencies": [
        {
          "id": "…",
          "versionRange": "…"
        }
      ],
      "revision": 0,
      "uploadedAtEpochMillis": 0
    }
  ]
}

Empty station, transmitter, receiver, catalog, type, category, and dependency lists are always encoded as [], never null.


Version and update commands

version

simtx-cli version

Prints simtx-cli <version> using the local build version, including build metadata. No network access and no version gate.

update

simtx-cli update

Prints one J: line:

{
  "current": "…",
  "latest": "…",
  "updateAvailable": true,
  "appimageUrl": "…",
  "zsyncUrl": "…",
  "sha256": "…",
  "builtAt": "…"
}

Behaviour:

  • Exits successfully and logs that the build is up to date when no update is

available.

  • Exits with a non-zero status when an update is available.
  • Exits with a non-zero status when update metadata cannot be fetched.
  • Bypasses the strict version gate.
  • The CLI never modifies its own installation. AppImage users can use the

printed URLs or a standard AppImage delta update tool.


Presets

Preset files are plain-text Java-properties-style files. The recommended extensions are .stxpreset for transmit sessions and .srxpreset for receiver sessions.

Format:

# Comments start with # or !
station.id=Home
rf.frequencyHz=7100000
rf.modulation=LSB
rf.bandwidthHz=2700
rf.lowCutHz=350
rf.powerWatts=100
rf.radioPreset=ICOM_IC_7300
cli.host=argon.simtx.net
cli.port=5556

Rules:

  • Lines are key=value. key:value is also accepted.
  • Lines beginning with # or ! are comments.
  • Blank lines are ignored.
  • \n, \r, \t, \\, and \uXXXX escapes are supported.
  • Unknown keys are ignored.
  • Preset values are overridden by environment variables and explicit flags.

Load and save:

simtx-cli --preset station.stxpreset audio argon.simtx.net 5556 \
  7100000 message.wav USB
simtx-cli --station Home --save-preset station.stxpreset audio argon.simtx.net 5556 \
  7100000 message.wav USB

--save-preset writes the effective settings and continues running the command. Files are written with owner-only permissions and sorted by key. The literal --ticket value and service credentials are never written to a preset; the CLI warns when a ticket was supplied but not saved.

rx can write an .srxpreset:

simtx-cli --save-preset receiver.srxpreset rx argon.simtx.net 5556 1234

Transmit preset keys

Common keys:

Key Meaning
station.id Persistent station id or name (--station).
station.callsign Receiver callsign (ad-hoc rx); ignored for TX.
station.lat, station.latitude Latitude (ad-hoc rx, stations create); ignored for TX.
station.lon, station.longitude Longitude (ad-hoc rx, stations create); ignored for TX.
rf.frequencyHz, rf.frequency, rf.freq Frequency in Hz.
rf.modulation Modulation name.
rf.bandwidthHz, rf.bandwidth Bandwidth in Hz.
rf.lowCutHz Audio low cut in Hz.
rf.powerWatts, rf.power Power in watts.
rf.radioPreset Radio preset name.
antenna.type Antenna type.
antenna.height Antenna height in metres.
antenna.length Wire or element length in metres.
antenna.azimuth Azimuth in degrees clockwise from north.
antenna.feedlineLossDb Feedline loss in dB.
antenna.necDeck Custom NEC-2 antenna deck text.
antenna.necFile Path to custom NEC-2 antenna deck file (.nec).
source.tone Reserved for tone-controlled sources; accepted but currently unused by the built-in commands.
cli.host Server host.
cli.port Server port.
cli.accountUrl Account service URL.
cli.serverId Game server ID.
cli.ticket Literal ticket. Loaded, but never saved.
cli.authFile Plaintext credential file path.
cli.audioFile Audio file path.
cli.iqLocalPort Local IQ loopback port.
cli.iqSampleRate IQ sample rate.
cli.sampleRate, source.sampleRate, rf.sampleRate General sample rate.
cli.debug Enable verbose logging.
cli.telemetryJson Enable telemetry JSON output.

PA knob keys, in the same order as the positional overrides:

Key Knob
rf.finalDriveDb Final drive, dB
rf.paHeadroomDb PA headroom, dB
rf.audioCompressionDb Audio compression, dB
rf.paSoftness PA softness
rf.paAmPmDeg PA AM/PM, degrees
rf.carrierLeakDb Carrier leak, dB
rf.sidebandSuppressionDb Opposite-sideband suppression, dB
rf.frequencyOffsetHz Frequency offset, Hz
rf.mainsHumDb Mains hum level, dB
rf.mainsHumHz Mains hum frequency, Hz
rf.amModulationIndex AM modulation index
rf.paSupplySagDb PA supply sag, dB
rf.txNoiseFloorDb TX noise floor suppression below peak carrier, dBc (0–160, higher = cleaner)
rf.oscillatorPhaseNoiseDb Oscillator phase noise suppression at 10 kHz offset, dBc/Hz (0–160, higher = cleaner)
rf.cwKeyRiseMs CW key rise time, ms

Receiver preset keys

Receiver presets accept the shared station.*, rf.frequencyHz, rf.bandwidthHz, antenna., and cli. keys, plus:

Key Equivalent option
rx.rtlPort Local rtl-tcp port.
rx.hardwareProfile --hardware-profile
rx.serviceCredential --service-credential
rx.serviceCredentialFile --service-credential-file
rx.timestamped --timestamped
rx.readTimeoutMs --rx-read-timeout-ms
rx.maxRetries --rx-max-retries
rx.retryBaseMs --rx-retry-base-ms
rx.jitterMs --rx-jitter-ms
rx.autoStepdown --rx-auto-stepdown
rx.noLossyLink --rx-no-lossy-link
rx.listenHost --rx-listen-host

Receiver location defaults to Budapest (47.50, 19.04) unless overridden. Service credentials are never saved to receiver presets.


Exit status and signals

  • Successful commands exit with status 0.
  • Invalid arguments, network failures, authentication failures, server kicks,

disconnects, query timeouts, and update availability exit with status 1.

  • version never contacts the network and always exits successfully.
  • Ctrl-C (SIGINT) and SIGTERM stop transmitter and receiver sessions cleanly.

The CLI exits successfully (status 0) after an interrupt.

  • When a transmitter audio file stream finishes playing to completion (EOF), the CLI

exits cleanly with status 0.

  • An abnormal session termination (such as an upstream server kick or network drop)

exits with status 1.

  • Query commands print their JSON result before exiting.

Troubleshooting

Symptom Likely cause
update required CLI version does not exactly match the update service. Upgrade, then retry.
update check failed Update service unreachable. Check network access.
run 'simtx-cli login' first --server-id was used without a signed-in account.
transmitting needs --station TX always runs on a persistent station; pass --station (see stations).
--server-id is required to mint a station-bound ticket Station-bound sessions need an account ticket.
scope_required / sign in again The stored session predates station management; run login again.
The account service does not support station-bound sessions yet The account service predates v25 stations.
rejected: … (FORBIDDEN) Not your station (TX), an UNLISTED station of someone else (RX), or a mismatched ticket.
server did not answer with a protocol v25 hello The server runs an older protocol; it needs the v25 update.
--server-id is required with a service credential Receiver credential supplied without a game server ID.
service credential file must be owner-only Run chmod 600 on the credential file.
Connection refused on rtl-tcp The SDR client is pointed at the wrong host/port, or rx is not running.
IQ connection refused GNU Radio is not connected to 127.0.0.1:<localIqPort>, or the sample rate does not match.

Multiplexed Serve Mode (serve)

simtx-cli serve runs a single long-lived multiplexing process designed for game engines and integration layers (such as C#/.NET on Windows and Linux). Rather than spawning a separate process per radio or session, serve manages multiple concurrent receivers, scope panadapters, and persistent control channels over standard input/output using newline-delimited JSON (NDJSON), accompanied by local loopback binary TCP streaming sockets for high-rate float32 IQ data.

Standard I/O Protocol (NDJSON)

Communication occurs over two streams:

  • stdin: Receives single-line JSON commands from the controlling game or application.
  • stdout: Emits single-line JSON responses, events, and push notifications. Lines are newline-terminated and thread-safe.

Command Format (Application -> CLI)

{
  "id": "req-101",
  "cmd": "rx.open",
  "session": "radio-1",
  "params": { ... }
}
Field Type Description
id string (optional) Correlates responses to requests. Echoed back in the corresponding event.
cmd string (required) Command identifier (e.g. rx.open, control.subscribe_health, serve.ping).
session string (optional) Session identifier chosen by the application (e.g. radio-1, panadapter, ctl-main).
params object (optional) Command-specific arguments.

Event & Response Format (CLI -> Application)

{
  "event": "rx.opened",
  "session": "radio-1",
  "id": "req-101",
  "data": { ... }
}
Field Type Description
event string (required) Event type (e.g. serve.ready (its data.version is "v25"), rx.opened, control.health, error).
session string (optional) Associated session identifier.
id string (optional) Corresponds to the request id for direct command replies. Omitted for asynchronous pushes.
data object (optional) Event payload data.
error string (optional) Error description when event is "error" or a command fails.

Command Reference

1. System Lifecycle

  • serve.ready:

Emitted immediately upon startup to advertise protocol and engine capabilities:

  {
    "event": "serve.ready",
    "data": {
      "serve_version": 2,
      "protocol_version": 24,
      "app_version": "0.1.0-alpha-14"
    }
  }
  • serve.ping:

Checks serve process responsiveness and fetches system clock times.

    • Request: {"id": "p1", "cmd": "serve.ping"}
    • Response:
    {
      "event": "serve.pong",
      "id": "p1",
      "data": {
        "time_unix_nanos": 1792686378727327947,
        "time_utc": "2026-10-22T16:26:18.727342195Z"
      }
    }
  • serve.stop / exit / quit:

Closes all active receiver, transmitter, and control sessions, frees resources, and terminates the serve process with exit code 0.

    • Request: {"id": "p2", "cmd": "serve.stop"}
    • Response: {"event": "serve.stopped", "id": "p2", "data": {"ok": true}}

2. Receiver Sessions (rx.*)

  • rx.open:

Opens an upstream receiver session (v25 ReceiverStart, timestamped flag set by default) to the SimTX server and starts a local loopback TCP listener on 127.0.0.1:<port> for zero-copy binary IQ streaming. The receiver is ad-hoc unless station_id is given.

    • Parameters:
Parameter Type Default Description
host string argon.simtx.net SimTX upstream server host.
port int 5556 SimTX upstream server port.
ticket string "" Single-use session ticket (anonymous if blank).
callsign string "HAMSIMTEST" Receiver station callsign.
lat, lon float 0.0, 0.0 Receiver geographic latitude and longitude.
altitude float 0.0 Receiver antenna altitude in meters.
noise_figure_db float 0.0 Front-end noise figure in dB.
frequency_hz float 7093000 Center tuned frequency in Hz.
sample_rate int 24000 IQ sample rate in Hz (e.g. 24000 for voice radio, 64000 for panadapter, 2400000 for SDR view).
bandwidth_hz int 0 Receiver filter bandwidth in Hz (0 defaults to sample_rate).
chunk_samples int 0 Samples per chunk (0 defaults to 512).
plain_tcp bool false Force plain TCP connection instead of TLS.
hardware_profile string "" Optional hardware calibration profile path.
timestamped bool true Enables nanosecond server timestamping on IQ blocks.
antenna_type string "DIPOLE" Template type (DIPOLE, INVERTED_V, VERTICAL, YAGI_3EL, YAGI_5EL, LOOP, BEVERAGE, LONGWIRE, EFHW_32M_49_1).
antenna_height float 10.0 Height above ground in meters.
antenna_length float 0.0 Wire length in meters (0 = resonant).
azimuth float 0.0 Antenna azimuth heading in degrees.
feedline_loss_db float 1.0 Feedline transmission loss in dB.
ground string "AVERAGE" Ground model (AVERAGE, FREESPACE, PERFECT).
nec_deck string "" Raw NEC-2 deck card text (GW/GE/EX cards). Overrides template parameters.
nec_file string "" Path to a local .nec antenna file.
station_id string "" Bind the receiver to this persistent station (UUID). ticket must then be a receiver ticket minted for the same station (and antenna/rig); callsign, location and antenna parameters are ignored.
antenna_id, rig_id string "" Station antenna (default: first) and rig (default: none) for station_id.
    • Response:
    {
      "event": "rx.opened",
      "session": "radio-1",
      "id": "req-101",
      "data": {
        "stream_addr": "127.0.0.1:41235",
        "sample_rate": 24000,
        "chunk_samples": 512,
        "antenna_accepted": {
          "source": "synthesized",
          "type": "DIPOLE",
          "wire_count": 1,
          "segment_count": 21,
          "deck_bytes": 191
        }
      }
    }
    • Rejection / Error:

If the server limits or refuses the receiver connection, rx.rejected is emitted with the upstream reason:

    {
      "event": "rx.rejected",
      "session": "radio-1",
      "id": "req-101",
      "error": "upstream receiver session refused: limit exceeded"
    }
  • rx.retune:

Retunes an active receiver to a new frequency without reconnecting or tearing down the loopback streaming socket.

    • Request: {"id": "r2", "cmd": "rx.retune", "session": "radio-1", "params": {"frequency_hz": 7095000}}
    • Response: {"event": "rx.retuned", "session": "radio-1", "id": "r2", "data": {"frequency_hz": 7095000}}
  • rx.close:

Closes the local loopback TCP listener and disconnects the upstream receiver session.

    • Request: {"id": "r3", "cmd": "rx.close", "session": "radio-1"}
    • Response: {"event": "rx.closed", "session": "radio-1", "id": "r3", "data": {"ok": true}}

3. Control Sessions & Subscriptions (control.*)

  • control.open:

Establishes a persistent control connection (role CONTROL) to the server. The CLI pings the server every 15 s to keep the link alive.

    • Parameters: host, port, ticket, plain_tcp.
    • Response: {"event": "control.opened", "session": "ctl-1", "id": "c1", "data": {"host": "argon.simtx.net", "port": 5556}}
  • control.subscribe_stations:

Starts periodic polling of the server station list (v25: persistent stations with live sessions, public satellites, ad-hoc sessions).

    • Parameters: interval_sec (default 5), on_change_only (bool, default false).
    • Response: {"event": "control.subscribed_stations", "session": "ctl-1", "id": "c2", "data": {"interval_sec": 5}}
    • Push Event (control.stations):
    {
      "event": "control.stations",
      "session": "ctl-1",
      "data": {
        "count": 1,
        "stations": [
          {
            "kind": "ADHOC",
            "callsign": "HAMSIMTEST",
            "visibility": "UNLISTED",
            "latDeg": 39.53,
            "lonDeg": -119.81,
            "altitudeMeters": 0,
            "sessions": [
              {
                "sessionId": "a4b1...",
                "role": "TX",
                "callsign": "HAMSIMTEST",
                "frequencyHz": 7093000,
                "modulation": "USB",
                "powerWatts": 100.0,
                "bandwidthHz": 2700,
                "latDeg": 39.53,
                "lonDeg": -119.81
              }
            ]
          }
        ]
      }
    }
  • control.subscribe_health:

Starts periodic polling of server health, computing the real-time simulation clock offset $\Delta_{\text{sim}} = T_{\text{sim}} - T_{\text{local}}$.

    • Parameters: interval_sec (default 30).
    • Push Event (control.health):
    {
      "event": "control.health",
      "session": "ctl-1",
      "data": {
        "health": {
          "SimulationTimeEpochMillis": 1729600000123,
          "CapturedAtEpochMillis": 1729600000120,
          "WorldTicks": 45129,
          "ActiveTransmitters": 1,
          "ActiveReceivers": 3
        },
        "time_sync": {
          "simulation_time_millis": 1729600000123,
          "server_captured_millis": 1729600000120,
          "local_time_millis": 1729600000000,
          "offset_millis": 123
        }
      }
    }
  • control.weather:

Requests a regional weather map raster from the server.

    • Parameters:
      • center_lat, center_lon: Center geographic coordinates.
      • lat_span, lon_span: Extent in degrees (default 5.0).
      • width, height: Grid dimensions (e.g. 64 $\times$ 32).
      • type: Raster layer type: "RAIN_RATE", "DUCT_STRENGTH", "CLOUD_WATER", "SURFACE_TEMPERATURE", "LIGHTNING_RATE".
    • Push Event (control.weather):

Carries grid bounds (SouthLatDeg, NorthLatDeg, WestLonDeg, EastLonDeg), surface parameters, and raw raster values array (Values).

  • control.catalog / control.catalog_fetch:

Browses community presets and downloads specific items by entry_id.

  • control.close:

Closes the control connection and cancels all subscription polling.

4. Transmitter Sessions (tx.*)

  • tx.open:

Opens an upstream transmitter session (RoleTransmitter) using protocol v24 and starts a local loopback TCP listener on 127.0.0.1:<port> for zero-copy streaming of live microphone audio or complex IQ baseband.

    • Parameters:
Parameter Type Default Description
host string argon.simtx.net Upstream server hostname.
port int 5556 Upstream server port.
plain_tcp bool false Force plain TCP connection instead of TLS.
ticket string "" Single-use session ticket (anonymous if blank).
callsign string "HAMSIMTEST" Transmitter station callsign.
lat, lon float 0.0, 0.0 Geographic coordinates.
altitude float 0.0 Antenna altitude in meters.
frequency_hz float 7093000 Center transmission carrier frequency in Hz.
power_watts float 100.0 Transmit power in watts.
sample_rate int 48000 Declared input sample rate (e.g. 22050 for voice or up to 10000000 for IQ).
bandwidth_hz int 2700 Occupied RF bandwidth in Hz.
low_cut_hz int 350 Audio low-frequency cutoff in Hz.
modulation string "USB" Modulation (USB, LSB, AM_DSB_FC, CW, NFM, WFM, IQ).
audio_gain float 1.0 Input audio gain factor.
tone_hz float 0.0 Test tone frequency in Hz (0 to disable).
duty_cycle float 1.0 PA duty cycle factor (0.0 to 1.0).
clean_lab bool false Enable clean lab mode (disables driver distortion).
antenna_type string "DIPOLE" Template type (DIPOLE, INVERTED_V, VERTICAL, YAGI_3EL, YAGI_5EL, LOOP, BEVERAGE, LONGWIRE, EFHW_32M_49_1).
antenna_height float 10.0 Height in meters.
antenna_length float 0.0 Wire length in meters (0 = resonant).
azimuth float 0.0 Azimuth heading in degrees.
feedline_loss_db float 1.0 Feedline loss in dB.
ground string "AVERAGE" Ground model (AVERAGE, FREESPACE, PERFECT).
nec_deck string "" Raw NEC-2 deck card text (GW/GE/EX cards). Overrides template parameters.
nec_file string "" Path to a local .nec antenna file.
mode_name string "" Custom mode info identifier.
mode_description string "" Custom mode info human-readable description.
mode_bandwidth_hz int 0 Custom mode bandwidth limit.
Impairment fields:
final_drive_db float 0.0 Final driver stage level in dB.
pa_headroom_db float 6.0 PA headroom in dB.
audio_compression_db float 0.0 Mic audio speech compressor level in dB.
pa_softness float 0.0 PA saturation softness curve factor.
pa_am_pm_deg float 0.0 PA AM-to-PM phase conversion in degrees.
carrier_leak_db float -50.0 Residual carrier leak level in dB.
opposite_sideband_suppression_db float 50.0 Opposite sideband suppression in dB.
freq_offset_hz float 0.0 Local oscillator frequency offset in Hz.
mains_hum_level_db float -60.0 Power supply mains hum level in dB.
mains_hum_freq_hz float 50.0 Mains hum base frequency in Hz (e.g. 50 or 60).
am_modulation_index float 0.85 AM modulation index.
pa_supply_sag_db float 0.0 PA power supply voltage sag in dB.
tx_noise_floor_db float 120.0 Broadband noise floor suppression in dBc.
oscillator_phase_noise_db float 120.0 Oscillator phase noise suppression at 10 kHz in dBc/Hz.
cw_key_rise_ms float 5.0 CW keying rise/fall time envelope in ms.
    • Responses / Events:
      • tx.opened: Session accepted and on-air or ready.
      {
        "event": "tx.opened",
        "session": "tx-1",
        "id": "t1",
        "data": {
          "stream_addr": "127.0.0.1:43521",
          "sample_rate": 22050,
          "wire_rate": 48000,
          "audio_in": true,
          "iq_in": false,
          "antenna_accepted": { ... }
        }
      }
      • tx.queued: Session queued by server if channel or server capacity is full.
      {"event": "tx.queued", "session": "tx-1", "id": "t1", "data": {"queue_position": 2}}
      • tx.rejected: Connection refused by server.
      {"event": "tx.rejected", "session": "tx-1", "id": "t1", "error": "callsign already in use"}
      • tx.kicked: Disconnected during session.
      {"event": "tx.kicked", "session": "tx-1", "data": {"reason": "time limit expired"}}
  • tx.key:

Keys the transmitter (keyed: true). Incoming audio or IQ on the loopback socket is actively processed, modulated, and pushed to the upstream server.

    • Request: {"id": "t2", "cmd": "tx.key", "session": "tx-1"}
    • Response: {"event": "tx.keyed", "session": "tx-1", "id": "t2", "data": {"keyed": true}}
  • tx.unkey:

Unkeys the transmitter (keyed: false). The session remains warm and active:

    • Loopback audio/IQ ingestion is muted, and the CLI automatically pumps warm silence heartbeats (20ms frames) to keep server lease renewed.
    • Server buffer behavior: Any already-buffered audio or IQ in the server finishes draining through playout without being cut off mid-packet or triggering timeouts.
    • Request: {"id": "t3", "cmd": "tx.unkey", "session": "tx-1"}
    • Response: {"event": "tx.unkeyed", "session": "tx-1", "id": "t3", "data": {"keyed": false}}
  • tx.set:

Applies live mid-session parameter modifications without interrupting the connection or closing sockets.

    • Parameters: frequency_hz, power_watts, bandwidth_hz, low_cut_hz, and any of the 15 impairment fields.
    • Request:
    {
      "id": "t4",
      "cmd": "tx.set",
      "session": "tx-1",
      "params": {
        "frequency_hz": 7094000,
        "power_watts": 80.0,
        "bandwidth_hz": 2400
      }
    }
    • Response: {"event": "tx.set_applied", "session": "tx-1", "id": "t4", "data": {"ok": true}}
  • tx.close:

Stops loopback streaming and closes the upstream transmitter session.

    • Request: {"id": "t5", "cmd": "tx.close", "session": "tx-1"}
    • Response: {"event": "tx.closed", "session": "tx-1", "id": "t5", "data": {"ok": true}}
  • tx.telemetry:

Asynchronous push event carrying full RF and PA telemetry from the server on every frame:

  {
    "event": "tx.telemetry",
    "session": "tx-1",
    "data": {
      "sequence": 104,
      "server_time_millis": 1792686379220,
      "actual_frequency_hz": 7093000,
      "requested_power_watts": 100.0,
      "forward_power_watts": 78.4,
      "delivered_power_watts": 76.1,
      "reflected_power_watts": 2.3,
      "vswr": 1.25,
      "mismatch_loss_db": 0.12,
      "transformer_loss_db": 0.08,
      "foldback_loss_db": 0.0,
      "alc_db": -0.5,
      "buffer_seconds": 0.42,
      "finals_temperature_c": 46.5,
      "thermal_foldback_db": 0.0,
      "pa_compression_db": 0.4,
      "pa_heat_watts": 21.6,
      "pa_efficiency_percent": 78.0,
      "pa_thermal_fault_active": false
    }
  }

Loopback Streaming Protocols

Receiver Output Protocol (CLI -> Application)

For receiver sessions, high-bandwidth complex samples are transmitted over a dedicated loopback TCP socket (stream_addr) rather than base64-encoded on standard I/O. This avoids JSON parser CPU overhead, memory allocations, and garbage collection stalls when streaming at high sample rates (e.g. 2.4 Msps $\approx$ 153 Mbit/s).

Packet Header & Payload Layout

Each chunk sent across the local TCP socket consists of a 12-byte header followed by raw interleaved 32-bit floating-point samples:

+-------------------------------------------------------------+
| Field                         | Size    | Byte Order        |
+-------------------------------------------------------------+
| ServerTimestampUnixNanos      | 8 bytes | Big-Endian (BE)   |
| FloatCount (samples * 2)      | 4 bytes | Big-Endian (BE)   |
| Interleaved float32[] I/Q     | N bytes | Little-Endian(LE) |
+-------------------------------------------------------------+
  1. ServerTimestampUnixNanos (int64, 8 bytes, Big-Endian):

The authoritative simulation clock timestamp of the first sample in the block. Used for FT8/FT4 synchronization and synchronized voice audio playout.

  1. FloatCount (uint32, 4 bytes, Big-Endian):

The number of float32 values in the payload ($= \text{ChunkSamples} \times 2$).

  1. **I/Q Samples (float32[], FloatCount * 4 bytes, Little-Endian)**:

Alternating real and imaginary components: $[I_0, Q_0, I_1, Q_1, I_2, Q_2, \dots]$.

Transmitter Ingest Protocol (Application -> CLI)

For transmitter sessions (tx.open), the application connects to the local TCP listener (stream_addr):

  1. Audio Streams (Microphone / Voice Modes):
    • When opened with voice/analog modulations (USB, LSB, AM_DSB_FC, CW, NFM, WFM), the socket accepts continuous 32-bit Little-Endian IEEE-754 float mono PCM audio at the declared sample_rate (e.g. 22,050 Hz or 48,000 Hz).
    • Audio is resampled internally in real time to the RF pipeline wire rate via RatePump, where microphone compression, frequency response, and exciter impairments are applied before modulation.
  1. IQ Streams (GNU Radio / Digital Modes):
    • When opened with modulation: "IQ", the socket accepts complex baseband samples (interleaved float32 real and imaginary pairs).
    • Ingest supports two modes:
      • Framed packets with 12-byte header: Same format as receiver packets ([ServerTimestampUnixNanos (int64 BE)][FloatCount (uint32 BE)][samples float32 LE]). This allows precise scheduling of FT8/JS8 blocks at specific server time slots ($T_{\text{server}}$). Block sizes up to 65,536 values are supported.
      • Raw stream: Continuous stream of interleaved float32 pairs sent directly into the RF chain.

Server Buffer Drain at Unkey

When a transmitter is unkeyed via tx.unkey:

  • The client CLI stops reading audio/IQ from the loopback socket and immediately switches to emitting 20 ms frames of silence (holdSilence) upstream.
  • This serves two functions:
    1. It acts as an active keep-alive heartbeat, preventing session timeouts and lease expiration between overs.
    2. The server's internal audio buffer drains gracefully to the simulated air. The trailing 0.5–1.0 s of voice audio is never truncated or abruptly silenced mid-sentence.
  • When tx.key is reissued, normal audio/IQ ingest resumes instantly with no need to renegotiate tickets or reconnect.

Serve Protocol Changelog

Version 2 (CLI alpha-14 / Protocol v24)

  • Added full transmitter session control: tx.open, tx.key, tx.unkey, tx.set, tx.close.
  • Added real-time audio loopback streaming with automatic resampling from game rates (e.g. 22,050 Hz) to internal wire rates.
  • Added IQ loopback streaming supporting sample rates up to 10 MS/s and server timestamped scheduling blocks.
  • Added live mid-session knob adjustment via tx.set.
  • Added complete 18-parameter telemetry event emissions (tx.telemetry).
  • Added serve.ready banner containing serve_version: 2, protocol_version: 24, and app_version.
  • Added receiver options to rx.open: altitude, noise_figure_db, bandwidth_hz, chunk_samples, plain_tcp.
  • Added rx.rejected event when upstream receiver sessions are refused by server quotas or limits.
  • Added --plain-tcp / SIMTX_PLAIN_TCP / SIMTX_FORCE_PLAIN_TCP for environments operating over unencrypted TCP.
  • Fixed informational server health banners (BannerInfo) causing query commands to fail.

C# (.NET) Zero-Copy Read Example

using System;
using System.Buffers.Binary;
using System.Net.Sockets;
using System.Runtime.InteropServices;

public async Task ReadIqStreamAsync(string host, int port, CancellationToken ct)
{
    using var client = new TcpClient();
    await client.ConnectAsync(host, port, ct);
    using var stream = client.GetStream();

    byte[] header = new byte[12];
    byte[] payload = new byte[65536];

    while (!ct.IsCancellationRequested)
    {
        // 1. Read 12-byte header
        await stream.ReadExactlyAsync(header, 0, 12, ct);
        long timestampNanos = BinaryPrimitives.ReadInt64BigEndian(header.AsSpan(0, 8));
        uint floatCount = BinaryPrimitives.ReadUInt32BigEndian(header.AsSpan(8, 4));

        int byteCount = (int)floatCount * 4;
        if (payload.Length < byteCount)
            Array.Resize(ref payload, byteCount);

        // 2. Read float32 payload
        await stream.ReadExactlyAsync(payload, 0, byteCount, ct);

        // 3. Zero-copy cast into float span (x86_64 and arm64 are Little-Endian)
        ReadOnlySpan<float> iqSpan = MemoryMarshal.Cast<byte, float>(payload.AsSpan(0, byteCount));

        // iqSpan[0] = I0, iqSpan[1] = Q0, iqSpan[2] = I1, iqSpan[3] = Q1...
        ProcessIqBlock(timestampNanos, iqSpan);
    }
}

RF Impairment Knob Conventions

Fields 14 through 28 define the transmitter simulation impairments. In particular:

  • Field 26 (rf.txNoiseFloorDb): Broadband exciter noise floor suppression below peak carrier power, expressed in dBc (positive values, typically 0–160 dB, default 120 dB). Higher values mean greater suppression (quieter, cleaner background).
  • Field 27 (rf.oscillatorPhaseNoiseDb): Close-in oscillator phase noise suppression at 10 kHz offset, expressed in dBc/Hz (positive values, typically 0–160 dB, default 120 dB). Higher values mean greater spectral purity (less skirt broadening).

Both fields follow the suppression convention across both audio and GNU Radio IQ transmitter sessions in protocol v25 (TxImperfections.tx_noise_floor_db / oscillator_phase_noise_db).

Precedence Order

When resolving transmitter knobs:

  1. Explicit command-line positional floats have highest priority.
  2. Explicit keys in --preset files (rf.*) override template base defaults.
  3. Radio preset template defaults (e.g. CLEAN_LAB, ICOM_IC_7300, GENERIC_HF) provide base defaults for any unspecified knobs.