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
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #3620
Summary
This PR closes the gaps between AetherSDR's
rigctldemulation and what realHamlib clients (WSJT-X, fldigi, pat/Winlink, MacLoggerDX, …) actually expect.
The immediate trigger was pat refusing to load AetherSDR as a rig:
AetherSDR was reporting
chk_vfo → 0; pat and WSJT-X requirechk_vfo=1(VFO-prefix mode). That one symptom prompted a full audit of Hamlib's
tests/rigctl_parse.candrigs/network/netrigctl.c(source read, not docs) tofind 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_testsuite (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_vfonow returns1(was0); extended-mode response updated toVFO Mode: 1.set_vfo_optaccepted as no-op — VFO-prefix mode is always active, no switching needed.dump_statetargetable_vfoupdated from0→0x3(FREQ|MODE targetable by VFO prefix, as required bynetrigctl_open).dump_stateptt_typeupdated from0→1(RIG_PTT_RIG — CAT PTT is functional via theTcommand).VFO-prefix command routing — single resolver
A single front door,
takeVfoPrefix(parts) → SliceModel*, handles the VFO tokenfor 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, elsenullptr;VFOMEM→nullptr(no per-slice memory VFO on Flex).nullptrmaps toRPRT -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-specificlevels (AGC/AF/RF/SQL/APF/NR/NB/STRENGTH) now act on the resolved slice, so
get_level VFOB AGCreads 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'sfreq/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 theset_split_*forms) without a precedingset_split_vfo. AetherSDR's slicemodel only has a second slice when split is active, so those calls used to fail
(
RPRT -8/-1, surfaced as "Protocol error" / "Invalid parameter settingsplit TX frequency and mode"). Now a write that targets
VFOB/SUBenablessplit 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-8with no split, andwith 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 radiopreference, not a per-slice mode — there is no
CWL/CWRslice mode. The modetable previously mapped Hamlib
CWR ↔ Flex CWL, soset_mode CWRsent theradio an invalid
CWLwhich it silently coerced (observed:→ PKTUSB). Thetable now maps Hamlib
CWR → Flex CW(and the defensiveCWL → CW). This islossy on read-back — a client that sets
CWRreads backCW— but correct,since Flex cannot represent per-slice CW-reverse.
RTTYR → RTTYandWFM → FMwere already valid. Applies to plain
set_modeandset_split_modealike (bothnow go through the canonical
smartsdrToHamlib/hamlibToSmartSDRtables ratherthan a hand-rolled subset).
Thread safety — slice mutations off the CAT thread
The CAT protocol runs on the socket thread while
SliceModellives on the GUIthread, so all model mutations must go through
QMetaObject::invokeMethod(..., Qt::QueuedConnection). Four split-path writes were calling setters directly(
tryPromoteTxSlice,cmdSetSplitFreq,cmdSetSplitMode, and thecmdSetSplitVfodisable path) — now all queued. The disable path also recordsm_pendingTxSlicesynchronously sofindTxSlice()resolves correctly across theasync gap. Read-only resolution (
get_freq/get_mode VFOB) no longer drives thedeferred split-promotion state machine: a query has no side effects.
Dynamic
get_vfo_listPreviously 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_tonenow read and writeSliceModel::fmToneValue().1000= 100.0 Hz)"100.0")hamlib = round(flex_hz × 10)and inverseget_func TONE/set_func TONEwired toSliceModel::fmToneMode():TONE=1→setFmToneMode("ctcss_tx");TONE=0→setFmToneMode("off")CTCSS RX squelch — honest error
Flex radios have no RX CTCSS squelch (TX-only hardware). Previously
set_ctcss_sqlsilently returnedRPRT 0(success lie).get_ctcss_sql→0(truthful: no RX CTCSS active)set_ctcss_sql→RPRT -8(RIG_ENAVAIL: not supported)get_func TSQL→0(always off — truthful)set_func TSQL→RPRT -8TSQL is included in
get_func ?(readable — always 0) but excluded fromset_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_codereturned0(silent stub) andset_dcs_codereturnedRPRT 0(success lie).get_dcs_code,set_dcs_code,get_dcs_sql,set_dcs_sql→ all returnRPRT -8d/Dupdated to matchAGC level — wired to hardware
get_level AGC/set_level AGCnow read and writeSliceModel::agcMode().Hamlib
RIG_AGC_*↔ FlexagcModemapping:"off""fast""slow""med"kRigLevelAgc(bit 6) added to bothkRigGetLevelMaskandkRigSetLevelMask."AGC"added to bothrigGetLevelTokens()andrigSetLevelTokens().Protocol response format fixes — bare-mode terminators (reference-verified)
Bare-mode getter framing was aligned to match Hamlib's reference
rigctldexactly. 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:
RPRT 0(
RPRTis for setter success and errors). The spuriousrprt(0)is removedfrom 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_modeandhamlib_versionkeep the trailingRPRT 0— thereference daemon terminates these two with
RPRT 0in bare mode(
\get_lock_mode→0\nRPRT 0\n). Omitting it onget_lock_modereintroduces 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_modefixby adding the terminator to every bare getter — a divergence from reference
rigctld. The audit narrows it back to the two commands the reference daemonactually terminates.
Quit / halt handling
quitnow returnsrprt(0)before disconnecting so Hamlib gets a clean acknowledgement. Previously returned empty string, causing some clients to log a read error on disconnect.haltsame treatment — returnsrprt(0)(was empty string).Q(uppercase quit, short-form) added to the short-form switch — was previously returningRPRT -4(invalid command).Files changed
src/core/RigctlProtocol.htakeVfoPrefix(),ensureSplitTxSlice(),findTxSlice(bool promote),cmdGetCtcssTone()/cmdSetCtcssTone();sliceForVfo()src/core/RigctlProtocol.cpptests/rigctld_test.cppNew and updated tests
1b.5chk_vforeturnsVFO Mode: 11b.5bset_vfo_optreturnsRPRT 01b.7get_vfo_list(no split) contains VFOA and not VFOB2.6–2.8get_freq VFOA/set_freq VFOAround-trip2.8bget_freq VFOBwith no split returnsRPRT -82.8c–2.8dget_freq/set_freq VFOMEMreturnRPRT -8(no memory-VFO slice)3.11–3.13get_mode VFOA/set_mode VFOAround-trip3.14set_mode CWRreads backCW(valid Flex mode, not coerced PKTUSB)4.3–4.4vfo_op UP/DOWNstep move (tolerant of radio step quantization)4.5get_vfo_info VFOA— 5 fields present4.6get_vfo_info VFOB(no split) returnsRPRT -86.4c–6.4dget_ptt/set_ptt5.7e–5.7gset_split_freq/set_split_mode/set_split_freq_mode5.11set_freq/set_mode VFOBwith split off auto-enables split (RPRT 0, not-8)7.11–7.12get_level VFOA STRENGTH/set_level VFOA AF7.13–7.14get_level AGC/set_level AGC 3round-trip8.11–8.15get_func TONE/set_func TONEround-trip;get_func TSQL= 0;set_func TSQL= -8; VFO-prefixedget_func/set_func VFOA TONE15.6bset_ctcss_tone VFOA 1000round-trip13.10,13.13get_lock_mode/hamlib_versionreturn value +RPRT 0(matches reference rigctld; guards the #3115 WSJT-X stall)13.11–13.12,13.14–13.20chk_vfo,get_powerstat,get_vfo_list,get_modes,get_rptr_shift,get_rptr_offs,get_ctcss_tone,get_dcs_code(→ -8),get_split_freq_mode15.5–15.6get_ctcss_tone/set_ctcss_tone 1000real round-trip (was stub check for 0)15.9–15.12get_ctcss_sql= 0,set_ctcss_sql= -8;get_dcs_sql= -8,set_dcs_sql= -8What is not changed
No previously-working command is regressed. All changes are either:
return rprt(0)) with honest errors (return rprt(-8)) for genuinely unsupported hardwarerigctldexactly (see above), keeping theRPRT 0terminator on the two getters the reference daemon terminatesPREAMP,ATT, andNOTCHFlevels remain unimplemented — Flex doesn't expose them over the TCP API.Built and smoke tested on
rigctld_test208/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.--ptt --cwexercised; PTY tests skip — not available on Windows).🤖 Generated with Claude Code