TCI Protocol Reference
Implementation guide for SDR software developers — TCI Remote & TCI Remote Compactor compatibility
Introduction
The TCI (Transceiver Control Interface) protocol is an open WebSocket-based protocol originally developed by Expert Electronics for their ExpertSDR2 software. It has since been adopted and extended by Thetis / OpenHPSDR and a growing number of other SDR platforms. TCI provides a standardised, language-agnostic way for client applications to control a software-defined radio over a local or remote network connection.
This document describes the TCI commands required for full compatibility with TCI Remote — a web-based remote SDR control application — and the TCI Remote Compactor, a companion proxy that adds audio compression and multi-user access on top of the standard TCI protocol.
If your SDR software correctly implements the commands in this document, users will be able to connect to it with TCI Remote either directly over a local network or through the Compactor proxy for remote access, without any modification to your server code.
Verified against TCI Remote v3.5 and TCI Remote Compactor v1.3.4.1 by reading both source files, not from memory.
Transport Layer
TCI uses a plain WebSocket connection (RFC 6455). The server listens on a
configurable TCP port; the conventional default is 50001. No WebSocket subprotocol
negotiation header is used. Connections are always initiated by the client.
- All commands are exchanged as UTF-8 text frames.
- All IQ and audio sample data is exchanged as binary frames.
- No TLS is required for local connections. Applications may use WSS for remote access independently of the protocol.
- Multiple clients may connect to the same TCI server simultaneously. The server must broadcast state changes to all connected clients.
iq_samplerate announcement, TCI Remote will
display an error and disconnect. A rate of 192,000 Hz is strongly recommended
for full spectrum display quality.
Server Handshake
Immediately after the WebSocket connection is established, the server must push an
initialisation block to the client. The client does not send any commands until it
receives the ready message that terminates this block.
// Required server → client init messages, in order: protocol:2; // TCI protocol version device:ExpertSDR2; // radio / software name receive_only:false; // true if TX is not supported trx_count:1; // number of independent TRX channels channels_count:2; // audio channels per TRX vfo_limits:100000,450000000; // min and max VFO frequency in Hz if_limits:-96000,96000; // min and max IF offset in Hz modulations_list:AM,SAM,DSB,LSB,USB,CWL,CWU,NFM,DIGL,DIGU,SPEC,DRM; iq_samplerate:192000; // MUST be ≥ 96000 audio_samplerate:48000; // must be 48000 tx_profiles_ex:Default,Contest,DX; // comma-separated TX profile names ready // no trailing semicolon on this one
tx_profiles_ex populates the TX profile dropdown. The remaining init fields are
accepted and ignored — see the note below.
After sending ready, the server should also push the current value of all
stateful parameters (frequency, mode, filters, PTT state, etc.) so that the client UI
immediately reflects the actual radio state without the client needing to poll.
TCI Remote does not rely on this alone: it also sends an explicit
query batch.
device, iq_samplerate,
audio_samplerate, tx_profiles_ex and ready.
protocol, receive_only, trx_count,
channels_count, vfo_limits, if_limits and
modulations_list are received and discarded.
The practical consequence is the mode list. TCI Remote has a fixed set of mode buttons and does not build them from
modulations_list:
LSB, USB, DSB, CWL, CWU,
NFM, AM, SAM, DIGL, DIGU.
Your server should accept those spellings in modulation: and echo them back in the
same spelling. A mode your radio has but this list does not is not reachable from the UI; a
different spelling for the same mode will leave the mode button showing the wrong value.
Bare messages — no colon, no semicolon Receive
Three server → client messages carry no parameters and no trailing semicolon. A parser that
splits on : must not throw them away.
| Message | Description |
|---|---|
ready | Ends the init block. The client sends nothing before this arrives. |
start |
The radio hardware is powered on and running. TCI Remote clears its standby banner. |
stop |
The radio hardware is in standby while the TCI server itself is still up — Thetis sends
this when the radio is switched off. TCI Remote shows a banner offering to power the radio
on, and if the user enabled auto-wake it sends run_cat_ex:PS1; after 500 ms.
If your server cannot distinguish "software running, radio off", simply never send
stop.
|
Message Format
All TCI text messages follow a consistent format:
command_name:param1,param2,paramN; // general form ready // no colon, no semicolon keepalive; // command with no parameters
- Command names are lowercase ASCII, no spaces.
- Parameters are comma-separated with no whitespace.
- Every message ends with a semicolon, except
ready. - Boolean parameters are the literal strings
trueorfalse. - Frequencies are always unsigned integers in Hz.
- The TRX index is zero-based. Single-radio setups always use TRX
0. - Multiple commands may be delivered in one WebSocket text frame, separated by semicolons. Parsers should handle this.
- Unknown commands should be silently ignored for forward compatibility.
The Query Form
Most commands have a query form: the same command name with the TRX index only and no value. The server answers with the normal, fully populated message. TCI Remote relies on this — it does not assume the init push covered everything.
// set // query // server answers rx_nb_enable:0,true; rx_nb_enable:0; rx_nb_enable:0,true; agc_auto_ex:0,false; agc_auto_ex:0; agc_auto_ex:0,false; tx_profile_ex:Contest; tx_profile_ex; tx_profile_ex:Contest;
Immediately after ready, TCI Remote sends this batch. If your server ignores the
query form, every one of these controls will show a stale value until the user touches it.
C→S sql_enable:0; // squelch on/off C→S sql_level:0; // squelch threshold C→S run_cat_ex:ZZSO; // squelch source — sql_enable cannot express it C→S rx_anf_enable:0; C→S lock:0; C→S split_enable:0; C→S mute:0; C→S mon_enable:0,false; // this one is a set, not a query: monitor off C→S rx_volume:0,0; // trx 0, channel 0 C→S drive:0; C→S tune_drive:0; C→S modulation:0; // query only — the app never pushes the mode at connect, C→S modulation:1; // so an externally set mode is not overridden C→S rx_filter_band:0; // or a set, if the user has a saved passband C→S rx_filter_band:1; C→S rx_nb_enable:0; C→S rx_nb2_enable:0; C→S rx_bin_enable:0; C→S agc_auto_ex:0; C→S tx_profiles_ex; // no index at all C→S tx_profile_ex;
<trx>,<value> the query form is
unambiguous. For split_enable:, whose set form carries only a state, the query
split_enable:0; is indistinguishable from "set split off" unless you look at the
argument type. Thetis treats the numeric form as a query. If you implement it the other way
round, split will be forced off at every connect.
Stream Setup
After receiving ready, the client sends these commands to configure and start
the IQ and audio streams.
Client → Server Send
| Command | Description |
|---|---|
iq_start:0; | Start IQ sample streaming for TRX 0. |
iq_stop:0; | Stop IQ sample streaming for TRX 0. |
iq_samplerate:<rate>; | Request an IQ sample rate. TCI Remote only sends this if the server announced a rate below 96,000 Hz — since v3.5 the rate configured on the radio is otherwise left exactly as it is, and the panadapter zoom follows it. |
audio_start:0; | Start RX audio streaming for TRX 0. |
audio_stop:0; | Stop RX audio streaming for TRX 0. |
audio_stream_sample_type:float32; | Request 32-bit IEEE 754 float samples. This is the only format TCI Remote supports. |
audio_stream_channels:2; | Request stereo (2-channel) audio. |
audio_stream_samples:<n>; | Number of samples per audio frame (e.g. 2400 for 50 ms at 48 kHz). |
audio_samplerate:48000; | Confirm 48 kHz audio sample rate. |
tx_stream_audio_buffering:<ms>; | TX audio buffer depth in milliseconds. Sent before TX audio begins. |
rx_sensors_enable:0,true; | Enable periodic S-meter and RX sensor reporting for TRX 0. |
tx_sensors_enable:true; | Enable periodic TX power and SWR reporting. |
Server → Client Receive
| Command | Description |
|---|---|
iq_samplerate:<rate>; | Active IQ sample rate in Hz. Also sent during init. Must be ≥ 96000. |
audio_samplerate:<rate>; | Active audio sample rate (48000). |
audio_start:0; | Confirms audio streaming has started for TRX 0. |
Frequency & VFO
Bidirectional Both
| Command | Parameters | Description |
|---|---|---|
vfo: |
<trx>,<vfo>,<freq> |
VFO frequency in Hz. vfo = 0 for VFO-A, 1 for VFO-B. Client sends to change frequency; server echoes the confirmed value and pushes unsolicited updates when frequency changes from any source. |
dds: |
<trx>,<freq> |
DDS (panadapter center) frequency in Hz. Defines the center of the spectrum display window. Client may set it; server echoes and pushes unsolicited updates. |
if: |
<trx>,<hz> |
IF offset in Hz. Used to calculate the displayed frequency relative to the DDS center. |
vfo_lock: |
<trx>,<vfo>,<state> |
Lock or unlock a specific VFO (true = locked, false = unlocked). |
lock: |
<trx>,<state> |
General VFO lock for all VFOs on a TRX. |
split_enable: |
<trx>,<state> |
Enable split operation — RX on VFO-A, TX on VFO-B. |
Client → Server only Send
| Command | Parameters | Description |
|---|---|---|
vfo_swap_ex: | <trx> | Swap VFO-A and VFO-B frequencies. |
Modulation
Bidirectional Both
| Command | Parameters | Description |
|---|---|---|
modulation: |
<trx>,<mode> |
Get or set the modulation mode. Mode strings (case-insensitive):LSB USB DSB CWL CWU
NFM AM SAM DIGL DIGU
SPEC DRMThe full list supported by your server is announced in modulations_list during init.
|
Filters & Passband
Bidirectional Both
| Command | Parameters | Description |
|---|---|---|
rx_filter_band: |
<trx>,<lo>,<hi> |
RX passband edges in Hz, relative to the VFO frequency. Negative = below carrier,
positive = above. Example for a 2800 Hz USB passband: rx_filter_band:0,0,2800;.
Example for LSB: rx_filter_band:0,-2800,0;.The server echoes the applied values back. |
tx_filter_band_ex: |
<trx>,<lo>,<hi> |
TX passband filter edges in Hz. Same sign convention as rx_filter_band. |
RIT / XIT
RIT (Receiver Incremental Tuning) shifts the receive frequency without moving the transmit frequency. XIT does the reverse.
Bidirectional Both
| Command | Parameters | Description |
|---|---|---|
rit_enable: | <trx>,<state> | Enable or disable RIT. |
rit_offset: | <trx>,<hz> | RIT offset in Hz (signed integer, applied to receive frequency). |
xit_enable: | <trx>,<state> | Enable or disable XIT. |
xit_offset: | <trx>,<hz> | XIT offset in Hz (signed integer, applied to transmit frequency). |
DSP Features
Bidirectional Both
| Command | Parameters | Description |
|---|---|---|
rx_nb_enable: | <trx>,<state> | Enable or disable Noise Blanker 1 (NB). |
rx_nb2_enable: | <trx>,<state> | Enable or disable Noise Blanker 2 (NB2). |
rx_nb_enable_ex: |
<trx>,<state>,<which> |
Extended noise-blanker control. which: 1 = NB1,
2 = NB2, 0 when state is false.TCI Remote sends the plain rx_nb_enable: and this extended
form together, so a server that implements only the plain one still works.
|
rx_bin_enable: | <trx>,<state> | Enable or disable the BIN noise reduction function. |
rx_anf_enable: | <trx>,<state> | Enable or disable the Automatic Notch Filter (ANF). |
rx_nr_enable: | <trx>,<state> | Enable or disable Noise Reduction. Basic form. |
rx_nr_enable_ex: |
<trx>,<state>,<level> |
Extended NR control with algorithm selection.level: 0 = off, 1 = NR1, 2 = NR2,
3 = NR3, 4 = NR4.
|
AGC & Squelch
Bidirectional Both
| Command | Parameters | Description |
|---|---|---|
agc_auto_ex: | <trx>,<state> | Enable automatic AGC (true) or switch to manual gain (false). |
agc_gain: | <trx>,<gain> | Manual AGC gain value. Active when agc_auto_ex is false. |
sql_enable: | <trx>,<state> | Enable or disable squelch. |
sql_level: | <trx>,<level> | Squelch threshold level. |
PTT & Transmit Control
Bidirectional Both
| Command | Parameters | Description |
|---|---|---|
trx: |
<trx>,<state> |
PTT control. true = enter TX, false = return to RX.The server must echo the actual resulting hardware state back to all connected clients so every UI reflects the correct TX/RX state, regardless of which client triggered the change. |
tune: |
<trx>,<state> |
Tune mode — transmits a continuous carrier for antenna tuning. Same echo requirement as trx. |
drive: |
<trx>,<level> |
TX drive level (0–100 or implementation-defined range). |
mute: |
<trx> |
Mute RX audio output. |
Client → Server only Send
| Command | Parameters | Description |
|---|---|---|
tune_drive: | <trx>,<level> | Drive level used in tune mode, separate from drive:. TCI Remote queries it at connect (tune_drive:0;). |
tx_antenna: | <trx>,<idx> | Select TX antenna by index. |
rx_antenna: | <trx>,<idx> | Select RX antenna by index. |
rx_enable: | <trx>,<state> | Enable or disable a secondary RX channel (e.g. dual-watch RX2). |
Audio Control
Bidirectional Both
| Command | Parameters | Description |
|---|---|---|
rx_volume: |
<trx>,<ch>,<level> |
RX audio volume. ch = 0 for main receive channel. |
mon_enable: |
<trx>,<state> |
Enable or disable TX monitor (sidetone / self-monitor). |
mon_volume: |
<trx>,<level> |
TX monitor volume level. |
Server → Client Receive
| Command | Parameters | Description |
|---|---|---|
line_in: |
<trx>,<state> |
Notification that the audio input source changed (line-in versus microphone). TCI Remote logs it; it does not send this command. |
TX Profiles
TX profiles store transmit settings (equaliser, compressor, microphone gain, etc.) under named presets. TCI Remote displays the profile list in a dropdown and allows remote switching.
Server → Client on init Receive
| Command | Parameters | Description |
|---|---|---|
tx_profiles_ex: |
<name1>,<name2>,... |
Comma-separated list of all available TX profile names. Must be sent during server initialisation. |
Bidirectional Both
| Command | Parameters | Description |
|---|---|---|
tx_profile_ex: |
<name> |
Get or set the active TX profile. Client sends the desired profile name; server echoes the currently active profile back to all clients. |
Sensors & Meters
Sensor data is pushed by the server at a regular interval once enabled. Typical update rates: S-meter at 10–20 Hz, TX telemetry at 4–10 Hz while transmitting.
Server → Client Receive
| Command | Parameters | Description |
|---|---|---|
rx_smeter: |
<trx>,<dbm> |
Received signal strength in dBm (negative integer or float, e.g. -85).TCI Remote maps dBm to S-units using the ITU scale: S1 = −121 dBm · S9 = −73 dBm · S9+60 dB = −13 dBm. Sent periodically after the client sends rx_sensors_enable:0,true;.
|
tx_sensors: |
<trx>,<mic_db>,<fwd_pwr>,<rev_pwr>,<swr> |
TX telemetry while transmitting.mic_db = microphone level in dB.fwd_pwr = forward power in watts.rev_pwr = reverse (reflected) power in watts.swr = SWR ratio (e.g. 1.5).Sent after tx_sensors_enable:true;.
|
tx_swr: |
<trx>,<swr> |
SWR value (may be sent in addition to or instead of the SWR field in tx_sensors). |
tx_forward_power: |
<pwr> |
Forward TX power in watts (alternative format, used by some implementations). |
rx_channel_sensors: |
<data> |
Extended RX sensor data block. Format is implementation-defined. |
s_meter:smeter: |
<trx>,<dbm> |
Accepted as aliases of rx_smeter:. TCI Remote reads the last comma-separated field, so a longer parameter list is handled. Send rx_smeter: in new implementations. |
tx_power: |
<pwr> |
Alias of tx_forward_power:. Last field wins. |
swr: |
<swr> |
Alias of tx_swr:. Values below 1 are discarded as implausible. |
CAT Pass-through
TCI provides a pass-through mechanism for CAT (Computer Aided Transceiver) strings, allowing clients to use radio-specific features not covered by the native TCI command set.
Client → Server Send
| Command | Parameters | Description |
|---|---|---|
run_cat_ex: |
<cat_string> |
Execute a raw CAT command string. The format depends on the radio's CAT dialect (Kenwood-style, ICOM CI-V, Elecraft, etc.). Your server may route these to the connected hardware or ignore them if CAT pass-through is not supported. |
CAT Commands Used
Everything below travels inside run_cat_ex:. These are the only CAT strings
TCI Remote sends on its own initiative. They are all Kenwood-style / Thetis
ZZ dialect, because that is what Thetis exposes.
Sent by TCI Remote Send
| CAT string | Why it is sent | Detail |
|---|---|---|
PS1; |
Power the radio on | Sent when the user taps "Turn it on" in the standby banner, and automatically 500 ms after a stop message if the user enabled auto-wake. |
PS0; |
Power the radio off | Host power control from the connection screen. Radio Network visitors are blocked from sending this. |
ZZTX0; |
Force RX before a power command | Sent immediately before PS0/PS1 so the radio is never powered while transmitting. |
ZZSO; |
Query the squelch source |
sql_enable: says whether squelch is on, not which squelch. Thetis answers
with run_cat_ex:ZZSO;,ZZSO1; — note that it echoes the query and the answer in
one message, so parse the last field.
|
ZZSO0; ZZSO1; ZZSO2; |
Set the squelch source | 0 = off, 1 = SQL (noise), 2 = VSQL (voice). Followed by a ZZSO; read-back 500 ms later. |
ZZOA<n>; |
RX antenna selection |
Thetis does not implement rx_antenna: / tx_antenna:, so the app
sends both the TCI commands and these CAT equivalents. A server that implements the TCI
commands needs neither.
|
ZZOC<n>; |
TX antenna selection | |
| user-defined | ATU / accessory buttons |
Three configurable buttons in the app send any CAT string the user types, up to 20
characters. The factory defaults are ZZOV1 and ZZOW0 with the
third slot empty, and all three are disabled until the user enables them.
|
Sent by the Compactor
None. The Compactor never originates a CAT command. It only decides whether a
client's run_cat_ex: is allowed through — see
Compactor Extensions.
Keepalive
Client → Server Send
| Command | Description |
|---|---|
keepalive; |
Sent periodically by the client (typically every 30 seconds) to prevent the WebSocket connection from being closed by intermediate network equipment. The server must not close the connection on receipt. No reply is required. |
IQ Binary Frames
IQ sample data is transmitted as WebSocket binary frames. The frame format used by Thetis / OpenHPSDR (and followed by TCI Remote) is a 64-byte header followed by interleaved float32 I/Q sample pairs.
| Field | Offset | Type | Description |
|---|---|---|---|
| flags | 0 | uint32 LE | Implementation-defined frame flags. |
| sample_rate | 4 | uint32 LE | IQ sample rate in Hz (e.g. 192000). TCI Remote uses this to distinguish IQ frames from audio frames: a value > 48000 indicates IQ data. |
| header fields | 8–19 | uint32 LE ×3 | Implementation-specific header fields. May carry TRX index, format version, etc. |
| sample_count | 20 | uint32 LE | Number of complex sample pairs in the payload. |
| frame_type | 24 | uint32 LE | 0 = IQ frame · 1 = RX audio · 2 = TX audio · 3 = chrono/timing |
| reserved | 28 | 40 bytes | Reserved / padding. Must be present to align the payload to byte offset 64. |
| IQ payload | 64 | float32 LE pairs | Interleaved I and Q samples: I₀, Q₀, I₁, Q₁, … Each value is a 32-bit float, nominally in the range −1.0 to +1.0. |
frame_type == 0 AND
sample_rate > 48000. Frames with frame_type ∈ {1, 2} and
sample_rate ≤ 48000 are treated as audio frames.
Audio Binary Frames
RX audio (server → client) and TX audio (client → server) use the same 64-byte header
structure as IQ frames, with a different frame_type value and 48,000 Hz
sample rate.
| frame_type | Direction | Description |
|---|---|---|
1 | Server → Client | RX audio — stereo PCM float32 at 48,000 Hz. |
2 | Client → Server | TX audio — stereo PCM float32 at 48,000 Hz. |
The audio payload (starting at byte offset 64) contains interleaved stereo float32 samples:
// Stereo interleaved layout: L₀, R₀, L₁, R₁, L₂, R₂, ... // float32 LE each // Total payload size: sample_count × 2 × 4 // × 2 channels × 4 bytes per float32
frame_type at offset 0,
a 2-byte reserved field at offset 2, and a 4-byte sample count at offset 4, with the audio payload
starting at offset 8. If your implementation uses the full 64-byte Thetis-standard header,
no special handling is required on either side.
Compactor Extensions
Text messages
| Message | Direction | Description |
|---|---|---|
compactor:1,<tx>,<email>,<version>,<trial>; |
Compactor → app | Identifies the proxy and its capabilities. Sent once at connect and again whenever TX compression is toggled. This is how the app knows it is not talking to a bare TCI server. |
compactor_cooldown:<secs>; | Compactor → app | Seconds remaining before an unlicensed session may reconnect. |
compactor_timeout:… | Compactor → app | Legacy. The app still handles it; Compactor v1.3.4.1 no longer sends it. |
qping:<n>; → qpong:<n>; | app ↔ Compactor | Round-trip time measurement for the connection-quality strip. The Compactor echoes the message back with the verb changed and never forwards it to the TCI server. |
rn_* (16 messages) |
Compactor → app |
Radio Network session control: rn_user_joined, rn_user_left,
rn_promoted, rn_demoted, rn_queue_update,
rn_kicked, rn_disconnected, rn_idle_warning,
rn_idle_timeout, rn_window_closing, rn_cooldown_reset,
rn_local_operator_off, rn_ic_idle, rn_ic_busy,
rn_ic_rejected, rn_demoted. Multi-user turn-taking, nothing to
do with radio control.
|
Binary frames
Between app and Compactor the standard 64-byte TCI binary frames are replaced by compressed frames with a four-byte ASCII magic. Between Compactor and your TCI server, ordinary uncompressed TCI frames are used.
| Magic | Replaces | Payload |
|---|---|---|
TCRS | IQ frames (frame_type 0) | Default. The Compactor runs the FFT itself and sends a spectrum frame instead of IQ: uint16 FFT size, uint32 sample rate, float32 noise floor, then one uint8 per bin, zstd-compressed. This costs CPU on the Compactor machine and saves a great deal of bandwidth. |
TCRC | IQ frames (frame_type 0) | Fallback for raw IQ: the original frame, losslessly zstd-compressed. |
TCRA | Audio frames (types 1 and 2) | Opus. Used in both directions when TX compression is enabled. |
TCRM | — | TX monitor audio. |
Command gating
When a client connects as a Radio Network visitor rather than as the owner, the Compactor filters what it forwards. This is the one place where a command may be silently dropped before it reaches your server.
- Never forwarded from a visitor:
trxandtunewhen the parameters containtrue, pluscw_message,cw_macro,cw_macros,run_cat,run_cat_ex,drive,tx_filter_band_ex,tx_antenna,tx_profile_ex,mon_enable,mon_volumeandsplit_enable.run_cat_ex:PS0;andrun_cat_ex:PS1;are the two exceptions that are let through. - Additionally blocked for listeners (a visitor who does not hold the control
slot): everything that changes the radio for all listeners at once —
vfo,dds,modulation,rx_filter_band,vfo_swap_ex,vfo_lock,lock,rx_enable,rx_antenna, the DSP toggles,agc_auto_ex,agc_gain,sql_enable,sql_level, RIT/XIT, andrx_volume/rx_mute/mute/volume. Listeners get a private volume control inside the app instead. - Batches are evaluated per command: one blocked command in a semicolon-separated batch blocks the batch.
What the Compactor sends on its own
One thing only: mox:0;tune:0; on its monitoring connection, to force the radio out
of TX when a session ends or a watchdog fires. Everything else it puts on the wire towards your
server is a client command it decided to forward.
Complete Initialisation Sequence
The annotated exchange below shows the full message flow at connection time.
═══ WebSocket connection established ════════════════════════════ ── Server → Client: init block ────────────────────────────────── S→C protocol:2; S→C device:MySDR; S→C receive_only:false; S→C trx_count:1; S→C channels_count:2; S→C vfo_limits:100000,450000000; S→C if_limits:-96000,96000; S→C modulations_list:AM,SAM,DSB,LSB,USB,CWL,CWU,NFM,DIGL,DIGU; S→C iq_samplerate:192000; S→C audio_samplerate:48000; S→C tx_profiles_ex:Default,Contest,DX; S→C ready ── Client → Server: stream setup ──────────────────────────────── C→S audio_stream_sample_type:float32; C→S audio_stream_channels:2; C→S audio_stream_samples:2400; C→S audio_samplerate:48000; C→S audio_start:0; C→S iq_start:0; C→S rx_sensors_enable:0,true; C→S tx_sensors_enable:true; ── Server → Client: stream confirmation + current state push ──── S→C audio_start:0; S→C vfo:0,0,14225000; S→C dds:0,14225000; S→C modulation:0,USB; S→C rx_filter_band:0,0,2800; S→C tx_profile_ex:Default; S→C trx:0,false; S→C split_enable:false; S→C agc_auto_ex:0,true; S→C agc_gain:0,60; S→C rx_nb_enable:0,false; S→C rx_anf_enable:0,false; S→C rx_nr_enable_ex:0,false,0; S→C sql_enable:0,false; S→C rit_enable:0,false; S→C xit_enable:0,false; S→C mute:false; ═══ Steady state — IQ and audio binary frames flowing ════════════ S→C rx_smeter:0,-85; // ~S5, repeated at 10–20 Hz ── User changes frequency ─────────────────────────────────────── C→S vfo:0,0,14195000; S→C vfo:0,0,14195000; // echo confirmed value to all clients S→C dds:0,14195000; // center frequency may shift too ── User presses PTT ───────────────────────────────────────────── C→S trx:0,true; S→C trx:0,true; // echoed to ALL connected clients … TX audio binary frames (frame_type=2) client → server … … tx_sensors frames server → client while TX active … C→S trx:0,false; S→C trx:0,false; // echoed to all clients
State Echo Rules
A TCI server is a stateful broker. These rules ensure that all connected clients stay in sync regardless of which client triggered a change.
- Echo all state changes to all clients. When any client sends a command that changes radio state (frequency, mode, PTT, etc.), the server must send the updated state back to every currently connected client, including the one that made the change.
- Push current state to new clients.
When a new client connects (after
ready), the server should push the current value of all stateful parameters so the client UI is immediately accurate. - Echo hardware-triggered changes. State changes caused by the hardware itself (e.g. physical PTT button, front-panel tuning) must also be sent as unsolicited server messages to all connected clients.
- Sensor data is push-only.
rx_smeterandtx_sensorsare not request/response — they are pushed continuously at a fixed rate once enabled. There is no query command for them.
Command Usage Matrix
Every TCI command that TCI Remote v3.5 or Compactor v1.3.4.1 actually puts on the wire or acts upon. Extracted from the source of both, not from this document — if a command is not in this table, it is not used.
- C→S — TCI Remote sends it.
- S→C — TCI Remote acts on it when the server sends it. Blank means the message is accepted and discarded, which is harmless.
- Compactor — relay means it is passed through unchanged; gated means it may be dropped for Radio Network visitors (see Compactor Extensions).
| Command | C→S | S→C | Compactor | Note |
|---|---|---|---|---|
| Handshake and streams | ||||
protocol | relay | accepted, discarded | ||
device | ✓ | relay | shown in the UI | |
receive_only | relay | accepted, discarded | ||
trx_count | relay | accepted, discarded | ||
channels_count | relay | accepted, discarded | ||
vfo_limits | relay | accepted, discarded | ||
if_limits | relay | accepted, discarded | ||
modulations_list | relay | accepted, discarded — mode list is fixed | ||
ready | ✓ | relay | bare, no semicolon | |
start | ✓ | relay | bare — radio powered on | |
stop | ✓ | relay | bare — radio in standby | |
iq_start / iq_stop | ✓ | relay | ||
iq_samplerate | ✓ | ✓ | relay | only sent if the radio reports < 96 kHz; drives the panadapter zoom |
audio_start / audio_stop | ✓ | ✓ | relay | |
audio_samplerate | ✓ | ✓ | relay | 48000 |
audio_stream_sample_type | ✓ | relay | float32 | |
audio_stream_channels | ✓ | relay | 2 | |
audio_stream_samples | ✓ | relay | ||
tx_stream_audio_buffering | ✓ | relay | ||
keepalive | ✓ | relay | ||
| Frequency, mode and VFO | ||||
vfo | ✓ | ✓ | gated | |
dds | ✓ | ✓ | gated | |
modulation | ✓ | ✓ | gated | queried at connect, never pushed |
vfo_swap_ex | ✓ | gated | ||
vfo_lock / lock | ✓ | ✓ | gated | |
split_enable | ✓ | ✓ | gated | blocked for all Radio Network visitors |
rit_enable / rit_offset | ✓ | gated | echo is not consumed | |
xit_enable / xit_offset | ✓ | gated | echo is not consumed | |
| Filters and DSP | ||||
rx_filter_band | ✓ | ✓ | gated | |
tx_filter_band_ex | ✓ | ✓ | gated | |
rx_nb_enable | ✓ | ✓ | gated | |
rx_nb2_enable | ✓ | gated | ||
rx_nb_enable_ex | ✓ | ✓ | gated | sent alongside rx_nb_enable |
rx_bin_enable | ✓ | ✓ | gated | |
rx_anf_enable | ✓ | ✓ | gated | |
rx_nr_enable | ✓ | ✓ | gated | |
rx_nr_enable_ex | ✓ | ✓ | gated | |
agc_auto_ex | ✓ | ✓ | gated | |
agc_gain | ✓ | ✓ | gated | |
sql_enable | ✓ | ✓ | gated | |
sql_level | ✓ | ✓ | gated | |
| PTT, TX and audio | ||||
trx | ✓ | ✓ | gated | true blocked for visitors, false always allowed |
tune | ✓ | ✓ | gated | same rule as trx |
tune_drive | ✓ | relay | queried at connect | |
drive | ✓ | ✓ | gated | |
tx_antenna | ✓ | gated | plus CAT ZZOC | |
rx_antenna | ✓ | gated | plus CAT ZZOA | |
rx_enable | ✓ | gated | second receiver | |
rx_volume | ✓ | ✓ | gated | listeners get a client-side volume instead |
mute | ✓ | ✓ | gated | |
mon_enable | ✓ | ✓ | gated | |
mon_volume | gated | blocked but never sent by the app | ||
line_in | ✓ | relay | logged only | |
| Profiles, sensors and CAT | ||||
tx_profiles_ex | ✓ | ✓ | relay | queried with no index |
tx_profile_ex | ✓ | ✓ | gated | |
rx_sensors_enable | ✓ | ✓ | relay | |
tx_sensors_enable | ✓ | ✓ | relay | |
rx_smeter | ✓ | ✓ | relay | also sent as a nudge |
s_meter / smeter | ✓ | relay | aliases | |
tx_sensors | ✓ | relay | mic level, power, SWR | |
tx_swr / swr | ✓ | relay | ||
tx_forward_power / tx_power | ✓ | relay | ||
rx_channel_sensors | ✓ | relay | ||
run_cat_ex | ✓ | ✓ | gated | see CAT Commands Used |
if, tx_frequency, tx_frequency_ex,
tx_frequency_thetis, rx_ctun_ex, rx_mute,
volume, calibration_ex, rx_channel_enable,
tx_enable, rx_step_att_ex, rx_step_att_enabled_ex,
rx_preamp_att_ex. Sending them is harmless; not sending them is equally harmless.
Compatibility Checklist
A server that satisfies all items below will be fully compatible with TCI Remote, both for direct connections and when used through the TCI Remote Compactor proxy.
Transport & Handshake
- WebSocket server on a configurable TCP port (default 50001)
- No subprotocol negotiation required
- Sends full init block before
ready(protocol, device, trx_count, modulations_list, vfo_limits, if_limits, iq_samplerate, audio_samplerate, tx_profiles_ex) - Reports
iq_samplerate≥ 96,000 Hz (192,000 Hz recommended) - Pushes current radio state to new clients immediately after
ready - Silently ignores unknown commands (forward compatibility)
- Does not close the connection on receipt of
keepalive;
Frequency & Mode
- Handles
vfo:— sets frequency, echoes to all clients - Handles
dds:— sets panadapter center, echoes to all clients - Handles
modulation:— sets mode, echoes to all clients - Handles
vfo_swap_ex: - Handles
vfo_lock:/lock:and echoes lock state - Handles
split_enable:and echoes split state - Handles
rit_enable:,rit_offset:,xit_enable:,xit_offset:
Filters & DSP
- Handles
rx_filter_band:— applies passband, echoes to all clients - Handles
tx_filter_band_ex: - Handles
rx_nb_enable:,rx_nb2_enable:,rx_bin_enable: - Handles
rx_anf_enable:,rx_nr_enable:,rx_nr_enable_ex: - Handles
agc_auto_ex:,agc_gain: - Handles
sql_enable:,sql_level:
PTT & Audio Streams
- Handles
trx:— controls PTT, echoes actual hardware state to all clients - Handles
tune: - Handles
drive: - Implements server-side TX watchdog to prevent stuck TX on client disconnect
- Handles
audio_start:/audio_stop:— sends RX audio binary frames (float32 stereo, 48 kHz, frame_type = 1) - Handles
iq_start:/iq_stop:— sends IQ binary frames (float32 interleaved I/Q, sample_rate ≥ 96000, frame_type = 0) - Accepts TX audio binary frames from the client (frame_type = 2)
TX Profiles & Sensors
- Sends
tx_profiles_ex:list during init - Handles
tx_profile_ex:— switches profile, echoes active profile name to all clients - Sends
rx_smeter:0,<dbm>;at regular intervals afterrx_sensors_enable:0,true;(values in dBm) - Sends
tx_sensors:during TX aftertx_sensors_enable:true;
Query Form and Power State
- Answers the query form — command plus TRX index, no value — for every stateful command
- Answers
tx_profiles_ex;andtx_profile_ex;sent with no index at all - Accepts the fixed mode spellings LSB, USB, DSB, CWL, CWU, NFM, AM, SAM, DIGL, DIGU and echoes them back unchanged
- Optional: sends bare
start/stopwhen the radio hardware is powered on or in standby - Optional: implements
run_cat_ex:pass-through — see CAT Commands Used for what is actually sent