Skip to content

fix(cat): match missing Hamlib rigctld functionality — VFO mode, mode mapping, CTCSS/FM tones, levels & funcs#3619

Open
K5PTB wants to merge 1 commit into
aethersdr:mainfrom
K5PTB:fix/match-hamlib-functions
Open

fix(cat): match missing Hamlib rigctld functionality — VFO mode, mode mapping, CTCSS/FM tones, levels & funcs#3619
K5PTB wants to merge 1 commit into
aethersdr:mainfrom
K5PTB:fix/match-hamlib-functions

Conversation

@K5PTB

@K5PTB K5PTB commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Resolves #3620

Summary

This PR closes the gaps between AetherSDR's rigctld emulation and what real
Hamlib clients (WSJT-X, fldigi, pat/Winlink, MacLoggerDX, …) actually expect.
The immediate trigger was pat refusing to load AetherSDR as a rig:

Cannot load rig 'AetherSDR': Unable to select VFO: rigctl is not running in VFO mode

AetherSDR was reporting chk_vfo → 0; pat and WSJT-X require chk_vfo=1
(VFO-prefix mode). That one symptom prompted a full audit of Hamlib's
tests/rigctl_parse.c and rigs/network/netrigctl.c (source read, not docs) to
find every command gap, wrong response, or mode that silently mis-mapped. The
result is a broad correctness pass across the rigctld surface — not just VFO
mode
but mode mapping, CTCSS/FM tones, AGC, PTT, func masks, dump_state,
bare-mode response framing, and thread-safety of slice mutations.

Validated by the in-tree rigctld_test suite (208/208 with --ptt --cw,
live against a radio on Mac; cross-checked on RPi 5 and Win 11) plus live smoke
tests with WSJT-X (FT8, extended mode, and "Rig" split — confirmed creating a
slice on demand) and fldigi (connect + freq/mode sync, bare mode). Bare-mode
terminator behaviour was verified command-by-command against Hamlib 4.7.1's
reference rigctld (dummy rig).


Changes by area

VFO mode — core fix

  • chk_vfo now returns 1 (was 0); extended-mode response updated to VFO Mode: 1.
  • set_vfo_opt accepted as no-op — VFO-prefix mode is always active, no switching needed.
  • dump_state targetable_vfo updated from 00x3 (FREQ|MODE targetable by VFO prefix, as required by netrigctl_open).
  • dump_state ptt_type updated from 01 (RIG_PTT_RIG — CAT PTT is functional via the T command).

VFO-prefix command routing — single resolver

A single front door, takeVfoPrefix(parts) → SliceModel*, handles the VFO token
for every VFO-sensitive command. It strips a leading VFO name if present and
resolves the slice it addresses; with no prefix it returns the port's bound
slice. It is built on sliceForVfo(vfo): VFOA/MAIN → current RX slice;
VFOB/SUB → TX slice when split is active, else nullptr; VFOMEM
nullptr (no per-slice memory VFO on Flex). nullptr maps to RPRT -8
(RIG_ENAVAIL) — an absent VFOB or VFOMEM is never silently redirected to
VFOA, which would return wrong data or tune the wrong slice.

Routed through the resolver (replacing five divergent inline strippers that had
drifted — some handled MAIN/SUB, some didn't): get_freq/set_freq,
get_mode/set_mode, get_level/set_level, get_func/set_func,
set_ptt, set_ctcss_tone, set_ts, set_ant, vfo_op, set_split_freq/
set_split_mode/set_split_freq_mode (and their short forms). Slice-specific
levels (AGC/AF/RF/SQL/APF/NR/NB/STRENGTH) now act on the resolved slice, so
get_level VFOB AGC reads the TX slice instead of silently falling back to VFOA.

Split via a targetable TX VFO (WSJT-X "Rig" split)

We advertise targetable_vfo = FREQ|MODE, which tells Hamlib it may set a VFO's
freq/mode by addressing it directly. WSJT-X "Rig" split relies on this — it sets
the TX VFO with set_freq VFOB <hz> / set_mode VFOB <mode> (and the
set_split_* forms) without a preceding set_split_vfo. AetherSDR's slice
model only has a second slice when split is active, so those calls used to fail
(RPRT -8 / -1, surfaced as "Protocol error" / "Invalid parameter setting
split TX frequency and mode"). Now a write that targets VFOB/SUB enables
split on demand
via ensureSplitTxSlice() (promote an existing second slice,
or create one with deferred promotion), honouring the advertised capability.
Reads (get_freq/get_mode VFOB) are unchanged — still -8 with no split, and
with no side effects. Verified live: WSJT-X Rig split works (creating a slice
when needed) and fldigi is unaffected.

Mode-name mapping — CW-reverse fixed

FlexRadio has a single CW slice mode (CW); CW sideband is a global radio
preference, not a per-slice mode — there is no CWL/CWR slice mode. The mode
table previously mapped Hamlib CWR ↔ Flex CWL, so set_mode CWR sent the
radio an invalid CWL which it silently coerced (observed: → PKTUSB). The
table now maps Hamlib CWR → Flex CW (and the defensive CWL → CW). This is
lossy on read-back — a client that sets CWR reads back CW — but correct,
since Flex cannot represent per-slice CW-reverse. RTTYR → RTTY and WFM → FM
were already valid. Applies to plain set_mode and set_split_mode alike (both
now go through the canonical smartsdrToHamlib/hamlibToSmartSDR tables rather
than a hand-rolled subset).

Thread safety — slice mutations off the CAT thread

The CAT protocol runs on the socket thread while SliceModel lives on the GUI
thread, so all model mutations must go through QMetaObject::invokeMethod(..., Qt::QueuedConnection). Four split-path writes were calling setters directly
(tryPromoteTxSlice, cmdSetSplitFreq, cmdSetSplitMode, and the
cmdSetSplitVfo disable path) — now all queued. The disable path also records
m_pendingTxSlice synchronously so findTxSlice() resolves correctly across the
async gap. Read-only resolution (get_freq/get_mode VFOB) no longer drives the
deferred split-promotion state machine: a query has no side effects.

Dynamic get_vfo_list

Previously hardcoded "VFOA VFOB". Now returns "VFOA VFOB" only when a distinct TX slice exists (split active), otherwise "VFOA". Applies to both extended and bare modes.

CTCSS TX encode — wired to hardware

get_ctcss_tone / set_ctcss_tone now read and write SliceModel::fmToneValue().

  • Hamlib unit: tenths-of-Hz integer (e.g. 1000 = 100.0 Hz)
  • Flex unit: float Hz string ("100.0")
  • Conversion: hamlib = round(flex_hz × 10) and inverse

get_func TONE / set_func TONE wired to SliceModel::fmToneMode():

  • TONE=1setFmToneMode("ctcss_tx") ; TONE=0setFmToneMode("off")

CTCSS RX squelch — honest error

Flex radios have no RX CTCSS squelch (TX-only hardware). Previously set_ctcss_sql silently returned RPRT 0 (success lie).

  • get_ctcss_sql0 (truthful: no RX CTCSS active)
  • set_ctcss_sqlRPRT -8 (RIG_ENAVAIL: not supported)
  • get_func TSQL0 (always off — truthful)
  • set_func TSQLRPRT -8

TSQL is included in get_func ? (readable — always 0) but excluded from set_func ? (not settable). The masks and token lists are split accordingly.

DCS — rejected at protocol layer

DCS is entirely unsupported on Flex. Previously get_dcs_code returned 0 (silent stub) and set_dcs_code returned RPRT 0 (success lie).

  • get_dcs_code, set_dcs_code, get_dcs_sql, set_dcs_sql → all return RPRT -8
  • Short-form d/D updated to match

AGC level — wired to hardware

get_level AGC / set_level AGC now read and write SliceModel::agcMode().

Hamlib RIG_AGC_* ↔ Flex agcMode mapping:

Hamlib value Flex mode
0 "off"
1–2 "fast"
3 "slow"
4–6 "med"

kRigLevelAgc (bit 6) added to both kRigGetLevelMask and kRigSetLevelMask. "AGC" added to both rigGetLevelTokens() and rigSetLevelTokens().

Protocol response format fixes — bare-mode terminators (reference-verified)

Bare-mode getter framing was aligned to match Hamlib's reference rigctld
exactly. This was verified empirically against Hamlib 4.7.1 reference rigctld
(dummy rig, model 1) command-by-command — the behavior is command-specific,
not uniform
:

  • Most getters return the value line(s) only, with no trailing RPRT 0
    (RPRT is for setter success and errors). The spurious rprt(0) is removed
    from the bare-mode path of: chk_vfo, get_powerstat, get_vfo_list,
    get_modes, get_rptr_shift, get_rptr_offs, get_ctcss_tone,
    get_split_freq_mode. (Extended-mode path unchanged throughout.)
  • get_lock_mode and hamlib_version keep the trailing RPRT 0 — the
    reference daemon terminates these two with RPRT 0 in bare mode
    (\get_lock_mode0\nRPRT 0\n). Omitting it on get_lock_mode
    reintroduces the measured 20-second WSJT-X startup stall fixed in [cat] Fix WSJT-X startup CAT lock-mode timeout #3115
    (Hamlib's NET backend blocks waiting for the terminator after that probe).

This supersedes #3120, which had over-generalized #3115's get_lock_mode fix
by adding the terminator to every bare getter — a divergence from reference
rigctld. The audit narrows it back to the two commands the reference daemon
actually terminates.

Quit / halt handling

  • quit now returns rprt(0) before disconnecting so Hamlib gets a clean acknowledgement. Previously returned empty string, causing some clients to log a read error on disconnect.
  • halt same treatment — returns rprt(0) (was empty string).
  • Q (uppercase quit, short-form) added to the short-form switch — was previously returning RPRT -4 (invalid command).

Files changed

File What changed
src/core/RigctlProtocol.h Declarations for takeVfoPrefix(), ensureSplitTxSlice(), findTxSlice(bool promote), cmdGetCtcssTone()/cmdSetCtcssTone(); sliceForVfo()
src/core/RigctlProtocol.cpp All protocol changes above
tests/rigctld_test.cpp New/updated live tests — see below

New and updated tests

Test What it verifies
1b.5 chk_vfo returns VFO Mode: 1
1b.5b set_vfo_opt returns RPRT 0
1b.7 get_vfo_list (no split) contains VFOA and not VFOB
2.6–2.8 VFO-prefixed get_freq VFOA / set_freq VFOA round-trip
2.8b get_freq VFOB with no split returns RPRT -8
2.8c–2.8d get_freq/set_freq VFOMEM return RPRT -8 (no memory-VFO slice)
3.11–3.13 VFO-prefixed get_mode VFOA / set_mode VFOA round-trip
3.14 set_mode CWR reads back CW (valid Flex mode, not coerced PKTUSB)
4.3–4.4 vfo_op UP/DOWN step move (tolerant of radio step quantization)
4.5 get_vfo_info VFOA — 5 fields present
4.6 get_vfo_info VFOB (no split) returns RPRT -8
6.4c–6.4d VFO-prefixed get_ptt / set_ptt
5.7e–5.7g split-mode round-trip via canonical table; VFOB/VFOA levels resolve to independent slices; VFO-prefixed set_split_freq/set_split_mode/set_split_freq_mode
5.11 set_freq/set_mode VFOB with split off auto-enables split (RPRT 0, not -8)
7.11–7.12 VFO-prefixed get_level VFOA STRENGTH / set_level VFOA AF
7.13–7.14 get_level AGC / set_level AGC 3 round-trip
8.11–8.15 get_func TONE/set_func TONE round-trip; get_func TSQL = 0; set_func TSQL = -8; VFO-prefixed get_func/set_func VFOA TONE
15.6b VFO-prefixed set_ctcss_tone VFOA 1000 round-trip
13.10, 13.13 Bare-mode get_lock_mode / hamlib_version return value + RPRT 0 (matches reference rigctld; guards the #3115 WSJT-X stall)
13.11–13.12, 13.14–13.20 Bare-mode value-only (no terminator) for chk_vfo, get_powerstat, get_vfo_list, get_modes, get_rptr_shift, get_rptr_offs, get_ctcss_tone, get_dcs_code (→ -8), get_split_freq_mode
15.5–15.6 get_ctcss_tone / set_ctcss_tone 1000 real round-trip (was stub check for 0)
15.9–15.12 get_ctcss_sql = 0, set_ctcss_sql = -8; get_dcs_sql = -8, set_dcs_sql = -8

What is not changed

No previously-working command is regressed. All changes are either:

  • Adding VFO-prefix handling to commands that ignored it
  • Replacing silent stubs (return rprt(0)) with honest errors (return rprt(-8)) for genuinely unsupported hardware
  • Aligning bare-mode getter framing to match Hamlib reference rigctld exactly (see above), keeping the RPRT 0 terminator on the two getters the reference daemon terminates

PREAMP, ATT, and NOTCHF levels remain unimplemented — Flex doesn't expose them over the TCP API.


Built and smoke tested on

  • MacBook Air M2rigctld_test 208/208 (--ptt --cw, 1 skip: band-edge step clamp), live against a FlexRadio on 20 m HF; WSJT-X (FT8 + Rig split) and fldigi (bare mode) confirmed working.
  • RPi 5 (Debian trixie) — release + debug builds, suite green.
  • Win 11 (i3) — release build, suite green (--ptt --cw exercised; PTY tests skip — not available on Windows).

🤖 Generated with Claude Code

Close the gaps between AetherSDR's rigctld emulation and what real Hamlib
clients (WSJT-X, fldigi, pat/Winlink, MacLoggerDX) expect. Triggered by pat
refusing to load AetherSDR ("rigctl is not running in VFO mode"), which prompted
a full audit against Hamlib's tests/rigctl_parse.c and rigs/network/netrigctl.c.

VFO mode:
- chk_vfo=1; set_vfo_opt no-op; dump_state targetable_vfo=0x3 (FREQ|MODE),
  ptt_type=1 (RIG_PTT_RIG). Dynamic get_vfo_list (VFOB only when split active).
- Single VFO-prefix resolver takeVfoPrefix() used by every VFO-sensitive command
  (freq/mode/level/func/ptt/ctcss/ts/ant/vfo_op/split), replacing divergent
  inline strippers. VFOB/SUB → TX slice or RPRT -8 (never silently redirected to
  VFOA); VFOMEM → -8. Slice-specific levels act on the resolved slice.

Split via a targetable TX VFO (WSJT-X "Rig" split):
- set_freq/set_mode VFOB (and set_split_freq/mode/freq_mode) now strip the VFO
  prefix and enable split on demand via ensureSplitTxSlice() — addressing the TX
  VFO directly works without a preceding set_split_vfo, honouring targetable_vfo.
- All split-path SliceModel writes queued onto the GUI thread (QueuedConnection);
  read-only VFOB resolution has no side effects.

Mode mapping:
- Hamlib CWR → Flex CW (Flex has no per-slice CW-reverse; sideband is global).
  set_split_mode uses the canonical smartsdrToHamlib/hamlibToSmartSDR tables.

CTCSS / FM / funcs / levels:
- CTCSS TX tone wired to fmToneValue/fmToneMode; CTCSS RX squelch and DCS return
  RPRT -8 (unsupported) instead of lying. AGC level read/write wired to agcMode.
  func TONE/TSQL handling; get/set func and level masks corrected.

Response format:
- Bare-mode getters match Hamlib 4.7.1 reference rigctld command-by-command:
  get_lock_mode and hamlib_version keep the RPRT 0 terminator (omitting it
  reintroduced the aethersdr#3115 WSJT-X 20s startup stall); other getters are value-only.
- quit/halt/Q acknowledge with RPRT 0 for a clean Hamlib session close.

Tests: rigctld_test extended to 208 cases (--ptt --cw); includes regression
tests for every VFO-prefix path, the CWR mapping, and targetable VFOB split.
Validated live on a FlexRadio (Mac), cross-checked on RPi 5 and Win 11, and
smoke-tested with WSJT-X (FT8 + Rig split) and fldigi (bare-mode).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rigctld: emulation diverges from Hamlib client expectations — VFO mode, split, CW-reverse, CTCSS/AGC, bare-mode framing

1 participant