feat(rigctld): full Hamlib NET rigctl implementation with 149-test suite#2975
Conversation
…ite (aethersdr#2379) Implements complete Hamlib NET rigctl (rigctld) emulation in AetherSDR, enabling bidirectional rig control from MacLoggerDX, WSJT-X, fldigi, and any other Hamlib-compatible logging or digital-mode software. Command coverage was verified against the Hamlib source (tests/rigctl_parse.c and src/rig.c) to ensure no gaps. All short-form character mappings were audited and corrected against the canonical Hamlib table. - Extended-mode (+) and bare-mode responses, short-form single-character commands — both TCP and PTY/virtual-serial paths share the same handler - Frequency: get/set_freq, VFO ops (UP/DOWN/BAND-UP/BAND-DOWN) - Mode: get/set_mode with all SmartSDR modes; passband 0 defers to radio - VFO: get/set_vfo, get_vfo_info (Freq/Mode/Width/Split/SatMode per VFO) - Split VFO: get/set_split_vfo, get/set_split_freq, get/set_split_mode, get/set_split_freq_mode — two-slice and single-slice deferred-promotion paths both tested - PTT: get/set_ptt via interlock (not mox) - Levels: AF, RF, SQL, RFPOWER, STRENGTH, KEYSPD with set+get round-trips - Functions: NB, NR, ANF, VOX, MUTE, FBKIN - RIT/XIT: get/set/clear with read-back confirmation - Antenna: get/set with capability probe (set_ant ?) - CW/Morse: send_morse (inline + two-line Not1MM form), stop_morse, set_level KEYSPD; wait_morse returns RPRT -4 (CWX queue not queryable) - Tuning step: get/set_ts with capability probe (set_ts ?) - Power conversion: power2mW / mW2power - dump_state (positional Hamlib v1 format), get_info, get_rig_info, chk_vfo, get_trn/set_trn, get_dcd - FM/repeater stubs: get/set_rptr_shift/offs, get/set_ctcss_tone/dcs_code - Hamlib init probes: get/set_powerstat, get/set_lock_mode, set_vfo_opt, hamlib_version, get_vfo_list, get_modes, client_version - Short-form character table corrected against Hamlib tests/rigctl_parse.c (a/A=trn, y/Y=ant, n/N=ts, G=vfo_op, k/K=split_freq_mode, r/R/o/O/c/C/d/D=FM) - set_mode ?: missing guard was calling hamlibToSmartSDR("?") → "USB" and clobbering mode on every MacLoggerDX connection - Split race (two-slice): setTxSlice(true) queued async; findTxSlice() returned stale slice A. Fixed with m_pendingTxSlice set synchronously. - Split race (single-slice): set_split_freq/mode arrived before new slice existed. Fixed with m_pendingSplitFreqMHz/m_pendingSplitMode stash, applied in tryPromoteTxSlice() when the new slice appears. - get_lock_mode: was RPRT -4; WSJT-X 3.0 interprets -4 as lock active and suppresses its own mode-set commands. Now returns Lock Mode: 0. - get_powerstat: was RPRT -4; now returns Power Status: 1. - set_powerstat 0 / set_lock_mode 1: now return RPRT -1 rather than silently accepting — honest about what the radio cannot do. - case 'k' (get_split_freq_mode short form): ignored m_extended flag, always returned bare format even in extended mode. - MacLoggerDX: frequency sync bidirectional, mode sync bidirectional, split (single-slice creation path and two-slice burst), morse keyer — all confirmed working. - WSJT-X 3.0.0: connects cleanly, frequency and mode sync bidirectional. - fldigi: frequency sync bidirectional, mode changes on radio are reflected in fldigi. fldigi does not send set_mode via CAT (known fldigi design choice — it follows the radio, does not lead it). Preferred fldigi path is fldigi→flrig→AetherSDR via TCI (full bidirectional mode control); direct rigctld provided as fallback. 149 checks across 15 sections + edge cases (C++ integration test, no Python dependency): - Sections 1–13: connection/init, frequency, mode, VFO, split, PTT, levels, functions, RIT/XIT, antenna, CW/morse, power conversion, bare-protocol (non-extended) mode - Section 1b: Hamlib init probe command responses (WSJT-X compat) - Section 14: short-form character mapping regression - Section 15: FM/repeater stub responses - Every set_* command followed by companion get_* round-trip verification - Optional --ptt and --cw flags for TX-active tests (dummy load required) Closes aethersdr#2379 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
I did some additional integration testing: built this on Linux (Raspberry Pi 5), and tested Not1MM for proper operation. Band switching, frequency sync, and send_morse commands work as expected. |
There was a problem hiding this comment.
Thanks for this — the rigctld work itself is impressive: thorough Hamlib audit, honest RPRT -1 for unsupported ops, and a 1591-line round-trip test harness is a real gift to future contributors. A few things worth addressing before merge.
Blocking
src/gui/SpectrumWidget.cpp changes are out of scope and revert PR #2826
The PR description says the only rebase conflict was a comment-only merge in cmdSendMorse, but the SpectrumWidget changes look like an accidental revert of commit 4d5b4d7a ("Honor FFT Line slider on QPainter (software) render path (#2722). Principle III. (#2826)"). Specifically:
setFftLineWidth()loses its trailingupdate()— slider changes no longer trigger immediate repaint.drawSpectrum()drops thedrawLine = m_fftLineWidth > 0.0f"off" gate and the cosmetic pen built fromm_fftLineWidth.- The heat-map line path is replaced with a hardcoded
QPen(heatColor(avgT), 1.5)— the user's slider value is silently ignored on the QPainter render path.
This regresses the fix for #2722 on the software render path. Please drop these hunks from the PR (or rebase again against the current main and keep #2826's logic).
Worth a look
Dangling-pointer risk on m_pendingTxSlice
RigctlProtocol::findTxSlice() returns m_pendingTxSlice (a raw SliceModel*) before the model has confirmed the slice exists, and the pointer can persist across multiple command bursts (and even across split disable→enable cycles if cmdSetSplitVfo clears it but a later op repopulates from a removed slice). If the user closes the slice via the GUI between two rigctld commands, m_pendingTxSlice becomes dangling and the next access is UB.
Two reasonable fixes:
- Hold it as
QPointer<SliceModel>and null-check on use, or - Connect to whatever slice-removed signal
RadioModelemits and clearm_pendingTxSlicethere.
dump_state extended-mode terminator
if (m_extended)
dump += rprt(0);Good catch. Worth confirming that chk_vfo in extended mode (chk_vfo:\nVFO Mode: 0\n + RPRT 0) matches what real Hamlib 4.x clients parse — most other extended responses in this PR follow the cmd: arg\nKey: value\n shape, but chk_vfo has no arg, so chk_vfo: (trailing colon, nothing after) is a bit unusual. Not a bug if you tested it with WSJT-X/MacLoggerDX and it works (the integration table says yes), just worth a sanity check against the Hamlib source if you haven't already.
Extended-mode get_level format change
"get_level:\nLevel: %1\nLevel Value: %2\n" // old
"get_level: %1\n%2: %3\n" // newThis now matches canonical Hamlib better, but it's a breaking change for anything that was parsing the old field names. Worth a one-line note in the PR description so future readers don't get confused.
Nice work
- The split-VFO race writeup in
findTxSlice()andtryPromoteTxSlice()is exactly the kind of comment that earns its keep. - The
RPRT -1vsRPRT 0distinction forset_powerstat 0/set_lock_mode 1is the right call — silent lies here would be much worse than honest errors. - The
case 'k'short-form fix and theset_mode ?early-return guard are good bug catches.
🤖 aethersdr-agent · cost: $3.6467 · model: claude-opus-4-7
…inter path The rebase accidentally reverted PR aethersdr#2826 (commit 4d5b4d7). Restoring src/gui/SpectrumWidget.cpp from current main brings back: - setFftLineWidth() update() trigger so slider repaints immediately - drawLine = m_fftLineWidth > 0.0f "off" gate - cosmetic pen built from m_fftLineWidth so slider value is honored - and the unrelated improvements that landed after PR base (panadapter center clamp aethersdr#2867, waterfall stale-echo animation guard) All rigctld work in this PR is unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Merged — thanks @K5PTB! Complete Hamlib NET rigctl support has been on the wishlist since #2379; the audit against The split-VFO race writeup (two-slice Restored an accidental revert of #2826's FFT-line-width slider gating in Shipping in the next release. 73 de KK7GWY — and welcome to the AetherSDR maintainer rotation if you're ever interested; this is real maintainer-grade work. 73, |
|
@ten9876 Jeremy, thank you for the kind words. I haven't done anything like this for a long time (former OS developer), and working with Claude has been an interesting experience. I appreciate the offer to join the team, and someday I may take you up on it. But, we travel for extended periods of time, and I don't think I could give this the attention it deserves. I'm not sure how the accidental revert of the FFT-line-width slider gating happened.. I had Claude rebase and build/test just before I submitted the PR. 🤷🏼♂️ I am winding up integration testing of a full CAT server that will be able to replace SmartCAT altogether, so external software like N1MM that doesn't support rigctld will work without a bridge. 73's, Paul |
…dr#2995 Reconciles three concurrent improvements to cmdSetSplitVfo: - pepefrog1234's edge-detection (m_lastSplitEnable tri-state) so the Hamlib-logger poll doesn't ping-pong TX away from the user's choice - K5PTB's deferred-promotion state (m_pendingSplitEnable / m_pendingTxSlice / m_pendingSplitFreqMHz / m_pendingSplitMode) from aethersdr#2975 - The m_pendingTxSliceChange same-pass race flag from aethersdr#2975 The combined logic: reclaim TX on the RX slice only when we observe a real 1→0 split transition OR the same-pass race (split-1 then split-0 in one onClientData() pass). Steady-state poll-zeros and the first-report-after- connect leave the user's TX badge alone. The race path stays distinct so the queued setTxSlice() race from aethersdr#2975 is still covered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ethersdr#2962) Implements a native SmartCAT-compatible TCP CAT server, enabling band, mode, and frequency sync with N1MM, fldigi, WSJT-X, MacLoggerDX, and other logging/digital-mode software via the industry-standard CAT protocol. ## Why CAT instead of rigctld N1MM does not speak the Hamlib NET rigctl protocol; it requires a CAT port (TS-2000 or FlexCAT dialect). This made a full CAT implementation necessary for N1MM compatibility. Not1MM (a separate Linux-only product) uses rigctld — that is covered by aethersdr#2975. ## Protocol coverage - Full Kenwood TS-2000 command set per the manual appendix - Full FlexCAT command set per SmartCAT User Guide v4.1.5, except commands not applicable to a Flex radio (see PR body for gap list) - Two TCP ports; each port has a companion PTY that accepts the same commands — operators with existing SmartCAT/SmartDAX configurations can keep them unchanged ## Integration testing Tested against real hardware (FlexRadio 6500, firmware 4.2.18): | App | Connection | Result | |--------------|-----------|--------| | fldigi | TCP + PTY | ✓ | | WSJT-X | TCP + PTY | ✓ | | MacLoggerDX | TCP | ✓ | | N1MM+ | TCP | ✓ (CAT sync; CW keying not possible — see below) | Primary platform: macOS. Also tested on Raspberry Pi 5 / Raspberry Pi OS Trixie. ### N1MM CW keying — known limitation N1MM keys CW by toggling RTS/DTR on a real serial COM port. Virtual serial ports (PTYs) are not exposed as COM ports in Windows, so RTS/DTR keying cannot reach AetherSDR. No workaround exists without a WinKeyer-compatible keyer emulator; documented here so operators don't waste time chasing it. ## Test suites - `CAT_TS-2000_test`: 15 sections, 103 checks (92 always-on + 11 PTT/CW gated) - `CAT_Flex_test`: 16 sections, 129 checks (105 always-on + 24 PTT/CW gated) PTT and CW keyer tests are **skipped by default** to avoid unintended transmissions. Enable with `--ptt` and `--cw` when a radio is connected and the operator is ready to transmit. Firmware TX race noted during CW testing: the radio briefly reports TX=0 via CAT while the PA is still hot after CW completes. Safety watches added to the test suite; documented as known hardware behaviour. ## CAT applet redesign Configuration moved to a pop-out window (port number, dialect, VFO A/B slice assignment per port) rather than the right panel. Inspired by SmartSDR for macOS but more intuitive. The pattern — space-consuming config in a pop-out, status summary in the applet — is worth adopting for other applets. UI details: dynamic PTY-path column sizing; "Flex" dialect label (was "FlexCAT"); VFO column widths; column stretch. ## Security - Applied the PTY symlink fix (same pattern as GHSA-qxhr-cwrc-pvrm, aethersdr#3027) to CatPort: per-user symlink directories, atomic rename instead of unlink+symlink. ## Code quality - UAF audit: SmartCatProtocol and the new RigctlProtocol additions are both clean — no `[this]` captures in queued lambdas; all dispatch is synchronous on the main thread. - ThemeManager: `CatControlApplet` was included in the Phase 2 mass migration (aethersdr#3102). Because this branch replaces the entire applet, that migration did not carry forward. We applied `ThemeManager::applyStyleSheet()` throughout — both the docked panel and the pop-out window — so all widgets auto-restyle on `themeChanged`, consistent with the rest of the codebase. One intentional hardcode remains: the `:checked` background `#006040` (CAT active state); there is no `color.background.success` token analogous to `color.background.tx`. Adding one would complete the conversion and is worth proposing as a follow-up. Closes aethersdr#2962 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ethersdr#2962) Implements a native SmartCAT-compatible TCP CAT server, enabling band, mode, and frequency sync with N1MM, fldigi, WSJT-X, MacLoggerDX, and other logging/digital-mode software via the industry-standard CAT protocol. ## Why CAT instead of rigctld N1MM does not speak the Hamlib NET rigctl protocol; it requires a CAT port (TS-2000 or FlexCAT dialect). This made a full CAT implementation necessary for N1MM compatibility. Not1MM (a separate Linux-only product) uses rigctld — that is covered by aethersdr#2975. ## Protocol coverage - Full Kenwood TS-2000 command set per the manual appendix - Full FlexCAT command set per SmartCAT User Guide v4.1.5, with the exception of ZZPA/ZZPE (panadapter data streaming — out of scope) - Two TCP ports by default: 4532 (rigctld) and 5001 (FlexCAT); up to 6 more ports can be configured. Each TCP port has a companion PTY that accepts the same commands and responds identically. PTY paths are shown in the pop-out applet and can be copied to the clipboard with a right-click. ## Integration testing Tested against real hardware (FlexRadio 6500, firmware 4.2.18): | App | Connection | Result | |--------------|-----------|--------| | fldigi | TCP + PTY | ✓ | | WSJT-X | TCP + PTY | ✓ | | MacLoggerDX | PTY (TS-2000 only) | ✓ | | N1MM+ | TCP | ✓ (CAT sync; CW keying not possible — see below) | Primary platform: macOS. Also tested on Raspberry Pi 5 / Raspberry Pi OS Trixie. ### N1MM CW keying — known limitation N1MM keys CW by toggling RTS/DTR on a real serial COM port. Virtual serial ports (PTYs) are not exposed as COM ports in Windows, so RTS/DTR keying cannot reach AetherSDR. No workaround exists without a WinKeyer-compatible keyer emulator; documented so operators don't waste time chasing it. ## Test suites - `CAT_TS-2000_test`: 15 sections, 103 checks (92 always-on + 11 PTT/CW gated) - `CAT_Flex_test`: 16 sections, 129 checks (105 always-on + 24 PTT/CW gated) PTT and CW keyer tests are **skipped by default** to avoid unintended transmissions. Enable with `--ptt` and `--cw` when a radio is connected and the operator is ready to transmit. Firmware TX race noted during CW testing: the radio briefly reports TX=0 via CAT while the PA is still hot after CW completes. Safety watches added to the test suite; documented as known hardware behaviour. ## CAT applet redesign Inspired by SmartSDR for macOS. The space-consuming configuration belongs in a pop-out, not the applet panel. PTY paths for running ports are shown in the table and can be copied to the clipboard with a right-click — handy when configuring external apps. ## Security - Applied the PTY symlink fix (same pattern as GHSA-qxhr-cwrc-pvrm, aethersdr#3027) to CatPort: per-user symlink directories, atomic rename instead of unlink+symlink. ## Code quality - UAF audit: SmartCatProtocol and new RigctlProtocol additions both clean — no [this] captures in queued lambdas; all dispatch synchronous on main thread - ThemeManager: CatControlApplet was included in the Phase 2 mass migration (aethersdr#3102). Because this branch replaces the entire applet, that migration did not carry forward. Applied ThemeManager::applyStyleSheet() throughout — both the docked panel and the pop-out window — so all widgets auto-restyle on themeChanged, consistent with the rest of the codebase. One intentional hardcode remains: the :checked background #006040 (CAT active state); there is no color.background.success token analogous to color.background.tx. Adding one would complete the conversion and is worth proposing as a follow-up. Closes aethersdr#2962 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ite (aethersdr#2975) ## Summary Implements complete Hamlib NET rigctl (rigctld) emulation in AetherSDR, enabling bidirectional rig control from MacLoggerDX, WSJT-X, fldigi, and any other Hamlib-compatible logging or digital-mode software. Closes aethersdr#2379. Command coverage was verified against the Hamlib source (`tests/rigctl_parse.c` and `src/rig.c`) to ensure no gaps. All short-form character mappings were audited and corrected against the canonical Hamlib table. ## What's implemented - **Extended-mode** (`+`) and **bare-mode** responses; **short-form** single-character commands — TCP and PTY paths share the same `RigctlProtocol` handler - Frequency, mode, VFO, split VFO (two-slice and single-slice deferred-promotion paths), PTT, RIT/XIT, antenna, tuning step, levels (AF/RF/SQL/RFPOWER/STRENGTH/KEYSPD), functions (NB/NR/ANF/VOX/MUTE/FBKIN) - CW/Morse: `send_morse` (inline + two-line Not1MM form), `stop_morse`, `set_level KEYSPD`; `wait_morse` returns `RPRT -4` — CWX queue depth is not queryable on a FlexRadio, so an honest error is better than a false "done" - `dump_state` (positional Hamlib v1 format), `get_info`, `get_rig_info`, `chk_vfo`, `get/set_trn`, `get_dcd`, `power2mW`/`mW2power` - FM/repeater stubs: `get/set_rptr_shift/offs`, `get/set_ctcss_tone/dcs_code` - Hamlib init probes: `get/set_powerstat`, `get/set_lock_mode`, `set_vfo_opt`, `hamlib_version`, `get_vfo_list`, `get_modes` - `set_powerstat 0` and `set_lock_mode 1` return `RPRT -1` — honest about what a network-connected FlexRadio cannot do ## Bug fixes found during integration testing | Bug | Symptom | Fix | |-----|---------|-----| | `set_mode ?` missing guard | MacLoggerDX capability probe clobbered mode to USB on every connect | Return capability list early before mode conversion | | Split race (two-slice) | `findTxSlice()` returned stale slice A after `setTxSlice(true)` was queued async | Set `m_pendingTxSlice` synchronously so `findTxSlice()` is correct before event loop fires | | Split race (single-slice) | `set_split_freq/mode` arrived before new slice existed | Stash in `m_pendingSplitFreqMHz`/`m_pendingSplitMode`, apply in `tryPromoteTxSlice()` | | `get_lock_mode` → `RPRT -4` | WSJT-X 3.0 interprets `-4` as lock active, suppresses its own mode-set commands | Return `Lock Mode: 0` | | `get_powerstat` → `RPRT -4` | WSJT-X init probe failed | Return `Power Status: 1` | | `set_powerstat 0` / `set_lock_mode 1` | Silently returned `RPRT 0` for operations the radio cannot perform | Now return `RPRT -1` | | `case 'k'` short form | Ignored `m_extended` flag, always returned bare format | Check `m_extended` and return labeled extended response | ## Integration testing results | Software | Freq sync | Mode sync | Split | Morse | |----------|-----------|-----------|-------|-------| | **MacLoggerDX** | ✅ bidirectional | ✅ bidirectional | ✅ single-slice + two-slice | ✅ keyer works | | **WSJT-X 3.0.0** | ✅ bidirectional | ✅ bidirectional | N/A | N/A | | **fldigi** | ✅ bidirectional | ✅ follows radio | N/A | N/A | **fldigi note:** fldigi does not send `set_mode` via CAT — it follows the radio rather than leading it. This is a known fldigi design choice, not an AetherSDR bug. The preferred fldigi integration path is `fldigi → flrig → AetherSDR via TCI`, which provides full bidirectional mode control. Direct rigctld support is provided as a fallback for users who prefer it. ## Test suite `tests/rigctld_test.cpp` — 149 checks across 15 sections + edge cases (C++ only, no Python dependency). Run with: ```bash cmake --build build --target rigctld_test ./build/rigctld_test # protocol tests only ./build/rigctld_test --ptt # add PTT tests (dummy load required) ./build/rigctld_test --ptt --cw # add CW/morse tests (dummy load required) ``` Every `set_*` command is followed by a companion `get_*` round-trip verification. Section 14 is a dedicated regression suite for the short-form character mapping audit; section 15 covers FM/repeater stubs. ## Test plan - [x] Build clean on macOS (darwin 25.4.0) - [x] `rigctld_test` 148/148 passed (1 skip: `set_ts` read-back — radio snaps to band-valid step) with `--ptt --cw` against FLEX-6500 fw 4.2.18 - [x] MacLoggerDX bidirectional freq/mode sync, split, morse keyer — confirmed - [x] WSJT-X 3.0.0 — connects cleanly, freq/mode sync confirmed - [x] fldigi — freq sync confirmed; mode-follow confirmed; direct set_mode limitation documented - [x] Rebased cleanly onto current upstream/main (conflict was a comment-only merge in `cmdSendMorse`, resolved by keeping upstream comment from aethersdr#2909) - [ ] CI 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Jeremy Fielder <kk7gwy@aethersdr.com>
Summary
Implements complete Hamlib NET rigctl (rigctld) emulation in AetherSDR, enabling bidirectional rig control from MacLoggerDX, WSJT-X, fldigi, and any other Hamlib-compatible logging or digital-mode software. Closes #2379.
Command coverage was verified against the Hamlib source (
tests/rigctl_parse.candsrc/rig.c) to ensure no gaps. All short-form character mappings were audited and corrected against the canonical Hamlib table.What's implemented
+) and bare-mode responses; short-form single-character commands — TCP and PTY paths share the sameRigctlProtocolhandlersend_morse(inline + two-line Not1MM form),stop_morse,set_level KEYSPD;wait_morsereturnsRPRT -4— CWX queue depth is not queryable on a FlexRadio, so an honest error is better than a false "done"dump_state(positional Hamlib v1 format),get_info,get_rig_info,chk_vfo,get/set_trn,get_dcd,power2mW/mW2powerget/set_rptr_shift/offs,get/set_ctcss_tone/dcs_codeget/set_powerstat,get/set_lock_mode,set_vfo_opt,hamlib_version,get_vfo_list,get_modesset_powerstat 0andset_lock_mode 1returnRPRT -1— honest about what a network-connected FlexRadio cannot doBug fixes found during integration testing
set_mode ?missing guardfindTxSlice()returned stale slice A aftersetTxSlice(true)was queued asyncm_pendingTxSlicesynchronously sofindTxSlice()is correct before event loop firesset_split_freq/modearrived before new slice existedm_pendingSplitFreqMHz/m_pendingSplitMode, apply intryPromoteTxSlice()get_lock_mode→RPRT -4-4as lock active, suppresses its own mode-set commandsLock Mode: 0get_powerstat→RPRT -4Power Status: 1set_powerstat 0/set_lock_mode 1RPRT 0for operations the radio cannot performRPRT -1case 'k'short formm_extendedflag, always returned bare formatm_extendedand return labeled extended responseIntegration testing results
fldigi note: fldigi does not send
set_modevia CAT — it follows the radio rather than leading it. This is a known fldigi design choice, not an AetherSDR bug. The preferred fldigi integration path isfldigi → flrig → AetherSDR via TCI, which provides full bidirectional mode control. Direct rigctld support is provided as a fallback for users who prefer it.Test suite
tests/rigctld_test.cpp— 149 checks across 15 sections + edge cases (C++ only, no Python dependency). Run with:Every
set_*command is followed by a companionget_*round-trip verification. Section 14 is a dedicated regression suite for the short-form character mapping audit; section 15 covers FM/repeater stubs.Test plan
rigctld_test148/148 passed (1 skip:set_tsread-back — radio snaps to band-valid step) with--ptt --cwagainst FLEX-6500 fw 4.2.18cmdSendMorse, resolved by keeping upstream comment from CW sidetone is missing when transmitting via CAT (rigctrld) #2909)🤖 Generated with Claude Code