rigctld TCP integration
GopherTrunk can expose the control SDR’s frequency over the
Hamlib rigctld TCP wire
protocol
so external amateur-radio tooling can read it (for logging) and
set it (for satellite Doppler correction, scheduled retunes, etc.)
without learning GopherTrunk’s REST API.
Compatible with anything that already speaks rigctld:
- Cloudlog / GridTracker / N1MM+ for logging
- PSTRotator for tracker integration
- gpredict for satellite Doppler
- The reference
rigctl(1)CLI for ad-hoc scripting
Quick start
Add a one-liner to config.yaml:
api:
rigctld: "127.0.0.1:4532"
Restart the daemon, then verify with the standard Hamlib CLI:
$ rigctl -m 2 -r 127.0.0.1:4532
Rig command: f
851012500
Rig command: F 851037500
Rig command: f
851037500
Rig command: q
(-m 2 selects the “NET rigctl” backend — that’s what every
rigctld client uses to talk to a remote rigctld server.)
What’s wired
| Command | Behaviour |
|---|---|
F / set_freq <hz> |
Tunes the control SDR to <hz>. Internally routes through the iqtap broker so the change survives a USB-disconnect cycle. |
f / get_freq |
Returns the last-set centre frequency in Hz. |
M / set_mode <mode> <pb> |
Accepted, no-op. Real clients call this on connect with FM 12500 / PKT 0; the value doesn’t influence GopherTrunk’s pipeline. |
m / get_mode |
Returns FM\n12500\n — a reasonable proxy for what the daemon is doing on the control channel. |
V / set_vfo VFOA |
Accepted (single-VFO backend). Other VFO names return RPRT -11. |
v / get_vfo |
Returns VFOA. |
T / set_ptt 0 |
Accepted. set_ptt 1 returns RPRT -11 — GopherTrunk is RX-only. |
t / get_ptt |
Returns 0. |
chk_vfo |
Returns CHKVFO 0 (single-VFO daemon). |
dump_state |
Minimal but well-formed capability dump so handshake-on-connect clients (Cloudlog, rigctl) don’t choke. |
q / Q / exit |
Terminates the connection. |
| Anything else | Returns RPRT -1 (Hamlib’s “command not supported”). |
Which SDR is the “rig”?
The rigctld server controls the SDR with role: control in the
pool. If no control SDR is configured (or none is attached at
startup) the server logs a warning and skips wiring itself — the
daemon keeps running, the trunking pipeline is unaffected, but
external rigctld clients will get connection refused on the
configured port until a control SDR comes up and the daemon is
restarted.
When the control SDR USB-disconnects and is re-acquired by the pool watchdog, the rigctld server keeps working: tuning goes through the iqtap broker which transparently retargets the fresh handle.
Security
rigctld is plaintext, unauthenticated TCP. Treat it like
SMTP or unauthenticated Redis: bind only to interfaces you trust,
or front it with an SSH / WireGuard / Tailscale tunnel.
- Loopback only (default-safe):
rigctld: "127.0.0.1:4532"— same-host clients only. - LAN exposure:
rigctld: "0.0.0.0:4532"— every device on your network can retune the SDR. Acceptable on a trusted home network, not on a shared / coworking / hostel LAN. - Tunnelled remote: Bind to loopback on the daemon host and
forward over SSH from your laptop:
ssh -L 4532:localhost:4532 daemon-host rigctl -m 2 -r 127.0.0.1:4532
Diagnostics
The daemon logs rigctld: listening addr=... rig_serial=... on
successful bind. A bind failure (“port already in use” — typically
a real Hamlib rigctld already running on 4532) is non-fatal: the
trunking pipeline continues, but the rigctld endpoint is
unavailable. Either stop the conflicting service or pick a
different port (rigctld: "127.0.0.1:14532" then point clients
at the new port with rigctl -r 127.0.0.1:14532).
Implementation notes
- The rigctld backend is a
Controllerover the iqtap broker (the same primitive the live spectrum panel taps). SetFreq mutations funnel through the broker so the broker’sCenterHz()cache stays current — this is what feeds the spectrum panel’s frequency-axis labels after an external rigctld retune. - Only the ~10 commands real clients send are implemented. Adding
more is mechanical (
internal/api/rigctld/server.go’sdispatchis a switch table). Rotctld (rotctldrotor control) is out of scope. - The server is wired only when
api.rigctldis set AND a control SDR exists in the pool — a daemon configured without a tuner doesn’t pretend to be a controllable rig.