Audio sidecar that bridges Hamlib rigctld's TCI 2.0 driver to PulseAudio/PipeWire virtual sinks for ham radio software (JS8Call, fldigi, WSJT-X)
Project description
hamlib-tci-sidecar
An audio bridge that lets standard ham radio software (JS8Call, fldigi, WSJT-X, etc.) work with Expert Electronics SDRs (SunSDR2 Pro/DX, MB1, etc.) over Hamlib's TCI 2.0 driver.
Status: Linux only at the moment. Windows and macOS versions are planned -- the protocol code is portable but the audio glue is currently PulseAudio/PipeWire-specific. See Roadmap.
What problem does this solve?
ExpertSDR3 exposes the radio over the TCI WebSocket protocol on port 50001. ExpertSDR3 will only stream audio to one TCI client at a time -- specifically the client that asserted PTT. That makes it impossible to naively run multiple programs (e.g. a CAT controller plus a digital-mode modem) against the radio.
Hamlib's modified tci2.c driver fixes the CAT half: rigctld owns the one
allowed TCI connection and exposes a normal Hamlib radio on port 4532. But
the audio still has to get to and from the digital-mode software somehow.
This sidecar is the missing piece. It connects to rigctld over a small TCP sidechannel, forwards RX audio frames into a virtual audio sink the modem software can read, and forwards modem TX audio back through rigctld to the radio. The modem talks to the radio exactly like it would with any other soundcard-and-CAT setup; the sidecar makes that illusion possible.
┌──────────────┐ TCI ┌────────────┐
│ ExpertSDR3 │◀────WebSocket───▶│ rigctld │
│ (port 50001)│ │ (port 4532)│◀── CAT (JS8Call, etc.)
└──────────────┘ └─────┬──────┘
│ TCP audio
│ sidechannel
│ (port 4534)
▼
┌─────────────────┐
│ this sidecar │
└────┬───────┬────┘
tci-rx (sink) │ │ tci-tx (sink)
▼ ▼
ham-radio app reads RX,
writes TX
Requirements
- Linux desktop with PulseAudio or PipeWire (with
pipewire-pulse) - Python 3.8+
numpypulseaudio-utils(pactl,pacat,parec)- A Hamlib build with the TCI 2.0 driver and the patches required by this sidecar (frame reassembly + sole-reader queue). See Hamlib build.
- An Expert Electronics SDR running ExpertSDR3 with TCI enabled on the default port 50001.
Install runtime tools:
# Debian / Ubuntu / Mint
sudo apt install pulseaudio-utils python3-numpy
# Fedora / RHEL
sudo dnf install pulseaudio-utils python3-numpy
# Arch
sudo pacman -S libpulse python-numpy
Install
From PyPI (once published):
pip install hamlib-tci-sidecar
From source:
git clone https://github.com/jfrancis42/hamlib-tci-sidecar.git
cd hamlib-tci-sidecar
pip install .
Either way installs tci-sidecar-linux as a console script.
You can also just run the script directly without installing:
python3 tci-sidecar-linux.py [args]
Quick start
-
Start ExpertSDR3 with TCI enabled on port 50001.
-
Start rigctld against the radio's TCI port, exposing the audio sidechannel on port 4534:
rigctld -m 12 -r localhost:50001 -t 4532 -C audio_port=4534
Hamlib model
12is the TCI 2.0 backend.-C audio_port=4534enables the audio sidechannel that this sidecar connects to. -
Start the sidecar:
tci-sidecar-linux
It will create two PulseAudio null sinks named
tci-rxandtci-tx, connect to rigctld atlocalhost:4534, and start passing audio. -
Configure your ham-radio software:
- Audio input (RX):
tci-rx.monitor - Audio output (TX):
tci-tx - CAT: Hamlib
rigctld(network) atlocalhost:4532, or whatever CAT method your software prefers as long as it points at port 4532.
- Audio input (RX):
That's it. PTT triggered by the modem (via CAT) keys the radio; modem TX audio flows out the antenna; received audio shows up in the modem's waterfall.
Configuration
tci-sidecar-linux [-h]
[--rigctld-host HOST] default: localhost
[--rigctld-port PORT] default: 4534
[--name NAME] default: tci (creates NAME-rx and NAME-tx)
[--tx-gain-db DB] default: +20.0 dB (=10x linear)
[--rx-gain-db DB] default: 0.0 dB (=unity)
TX gain
ExpertSDR3 silently drops TX audio that's below some internal threshold -- no error, no log entry, just zero output power. JS8Call and several other modems output audio at around -20 dBFS, well below that threshold. The default +20 dB of TX gain (with hard clipping at int16 saturation) brings JS8Call's audio up to the level ExpertSDR3 actually transmits.
If you're using software that already outputs near full-scale audio you can back this off. If a modem outputs even quieter audio than JS8Call you may need more.
RX gain
Default is 0 dB (unity). Provided for symmetry. Use a small positive value to boost RX into a modem that wants louder input, or a negative value to attenuate.
Both gains have automatic clip-to-int16 saturation so you can't introduce wraparound distortion.
Hamlib build
The stock Hamlib tci2.c driver has bugs that prevent the audio
sidechannel from working reliably. You need a patched build until the fixes
are merged upstream. The required changes are:
- Single-reader queue between the audio thread and the CAT thread (avoids a race where the audio thread eats CAT replies)
- TCP-stream reassembly of TCI frames coming from the sidecar (otherwise a
partial
recv()ships a malformed WebSocket frame and ExpertSDR3 silently drops the audio -- TX appears to engage but emits 0 W) - Idempotent audio listen-socket setup so reconnecting CAT clients don't trip "address already in use"
tci2_sendis internally thread-safe so the reader thread and the CAT thread can both write to the WebSocket- TX-from-sidecar pumping runs every loop iteration, not only on WebSocket-poll timeout
If you're building Hamlib from source against a known-good tree, run:
cd Hamlib
make -C rigs/dummy tci2.lo
rm -f rigs/dummy/.libs/libhamlib-dummy.a rigs/dummy/libhamlib-dummy.la \
src/libhamlib.la src/.libs/libhamlib.so.5*
( cd rigs/dummy && make libhamlib-dummy.la )
( cd src && make libhamlib.la )
touch tests/rigctld.c && make -C tests rigctld
The aggressive rm is necessary because Hamlib's autotools/libtool
machinery can silently link a stale convenience archive into the shared
library, even after tci2.c has been recompiled. After building, verify:
strings src/.libs/libhamlib.so.5.0.0 | grep "audio listen socket already up"
If that string is present, your shared library has the patches.
Verifying the install
Three test scripts ship with the project:
-
test_audio_simple.py— Connect to rigctld's audio port, dump 20 RX_AUDIO frames, report sample stats. No PulseAudio dependency. Useful for confirming rigctld is healthy and audio is flowing. -
test_tx.py— Generate a 1 kHz sine, write it to thetci-txPulseAudio sink, key PTT via CAT for a few seconds. Should produce a clean carrier 1 kHz above the dial frequency in USB. -
monitor_tx.py— Watch a Siglent SSA3032X spectrum analyzer at the expected TX frequency and report whether RF actually appeared. Use this alongsidetest_tx.pyto do an end-to-end RF check. (Requires an SSA on 10.1.1.60 by default; edit the script if you have a different instrument.)
Roadmap
- Windows version — same protocol, different audio plumbing (likely WASAPI loopback or VB-Audio Cable). Forthcoming.
- macOS version — likely BlackHole or a CoreAudio aggregate device. Forthcoming.
The single-script structure is deliberate: the protocol logic (TCI frame codec, TX/RX gain, rigctld TCP framing) is platform-agnostic; only the audio-sink creation and capture/playback are platform-specific. The Windows and macOS versions will share the same protocol code.
License
MIT. See LICENSE.
Acknowledgements
- Expert Electronics for the TCI 2.0 protocol specification.
- eesdr-tci and the Python
eesdr-tcilibrary for showing how a working TCI client behaves. - The Hamlib project for
tci2.c-- the patched copy this sidecar depends on is a fork of that work.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hamlib_tci_sidecar-0.1.0.tar.gz.
File metadata
- Download URL: hamlib_tci_sidecar-0.1.0.tar.gz
- Upload date:
- Size: 28.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ddfe477e7c56b39e7bc493cb7c0b4e64cb46b1118601fdfb75c88fbdaffc293e
|
|
| MD5 |
4858ced94990e7eb94e4da9c0aecc01d
|
|
| BLAKE2b-256 |
74625900ed4276486edfac19f76c3acd8181e83b8dac2561243968e79ee19817
|
File details
Details for the file hamlib_tci_sidecar-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hamlib_tci_sidecar-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f578df654cf3fe3cc6aa51961439a68d90d94b1b82e4c76aeac7576a404b6575
|
|
| MD5 |
ae1c72b58fd412851e7ea99ee3c472e5
|
|
| BLAKE2b-256 |
5353ca96c328d280b3c4a8c8285c9bd3dd6b8b9c36d9ac86ed6c17878c5edad4
|