Skip to content

feat(smartcat): SmartCAT TCP server — TS-2000 and FlexCAT dialects#3131

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
K5PTB:feat/smartcat-tcp-server
May 25, 2026
Merged

feat(smartcat): SmartCAT TCP server — TS-2000 and FlexCAT dialects#3131
ten9876 merged 1 commit into
aethersdr:mainfrom
K5PTB:feat/smartcat-tcp-server

Conversation

@K5PTB

@K5PTB K5PTB commented May 25, 2026

Copy link
Copy Markdown
Contributor

CAT Protocol Coverage

  • Full Kenwood TS-2000 command set, referencing the manual appendix, except commands not applicable to a FlexRadio
  • Full FlexCAT command set per SmartCAT User Guide v4.1.5, with the exception of ZZPA/ZZPE (panadapter data streaming — out of scope for a CAT server)
  • Two TCP ports by default: 4532 (rigctld) and 5001 (FlexCAT). Up to 6 more ports can be configured/enabled; each 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 — handy when configuring external apps.

Why CAT in addition to rigctld

Some external apps do not support rigctld, but expect to send TS-2000 CAT commands, with extensions by FlexRadio. AetherSDR now speaks both protocols, giving operators the flexibility to use whichever their software requires — setups that currently use SmartCAT can work directly with AetherSDR without change. TCI is preferred, but this supports both legacy apps and legacy hams.

CAT applet redesign

Inspired by SmartSDR for macOS. The space-consuming configuration only appears when popped out, avoiding clutter of the right panel.

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.

A firmware TX race was noted during CW testing: the radio briefly reports TX=0 via CAT while the PA is still hot after CW completes. Safety watches are added to the test suite; documented as known hardware behaviour.

Integration testing

Tested against real hardware (FlexRadio 6500, firmware 4.2.18). Primary platform: macOS. Also tested on Raspberry Pi 5 / Raspberry Pi OS Trixie.

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)

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.

Security

Applied the PTY symlink fix (same pattern as GHSA-qxhr-cwrc-pvrm, #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 (#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 — 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 #2962

🤖 Generated with Claude Code

@K5PTB K5PTB requested review from a team as code owners May 25, 2026 13:26

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — protocol coverage and test depth are impressive, the GHSA‑qxhr‑cwrc‑pvrm pattern is correctly applied to CatPort, and the AppSettings/RAII conventions are followed throughout the new GUI surface. A few findings worth addressing before merge.

Dead, broken code in tree

src/core/SmartCatServer.{h,cpp} are present but not added to CMakeLists.txt (only CatPort.cpp, SmartCatProtocol.cpp, SmartCatSession.cpp are). CatPort.h:25 explicitly says "Replaces the separate RigctlServer / SmartCatServer / RigctlPty hierarchy" and the CMake hunk removes RigctlServer.cpp — looks like SmartCatServer.{h,cpp} were meant to be deleted in the same pass.

It's also genuinely broken: SmartCatServer.cpp:74 calls new SmartCatSession(socket, m_model, m_vfoAIndex, m_vfoBIndex, this) — a 5‑arg ctor that no longer exists (current sig is socket, model, vfoA, vfoB, flexExtensions, parent). If anything in the future picks this up, it won't compile.

Suggested fix: git rm src/core/SmartCatServer.{h,cpp}.

TX safety: PTT can be left keyed on disconnect

If a client sends TX;/ZZTX; and then drops the TCP connection (crash, network blip, app SIGKILL) without sending RX;, SmartCatSession::~SmartCatSession() only calls setAI(false) — the radio stays keyed. Rigctld has the same gotcha, but here SmartCatProtocol::cmdTX() deliberately uses PttSource::Dax to bypass voice‑mode interlocks, so it's worth handling explicitly.

One option: track a m_pttAssertedByMe bit in the session and call m_model->setTransmit(false, PttSource::Dax) from onDisconnected() / dtor if set. Don't unconditionally force RX (would clobber legitimate PTT from another source — front panel, footswitch, another CAT client).

Minor: PTY write return ignored

CatPort::onPtyData() (CatPort.cpp:347, :363) issues ::write(m_ptyMasterFd, …) and discards the return. The master is O_NONBLOCK, so on a full kernel buffer a short or EAGAIN write will silently drop response bytes. In practice CAT responses are tiny and PTY buffers large, so unlikely to bite — but at minimum a qCWarning on n < 0 would make it diagnosable if it does.

Out of scope but nice

The "Save As… → color.background.success" point you raised in the description is a clean follow‑up — happy to see it filed as an issue separately rather than expanding this PR.

Otherwise the rest looks solid: dialect dispatch is clean, the atomic symlink rename is correct, GUI uses AppSettings, slice‑lookup paths are null‑guarded. Nicely structured contribution.


🤖 aethersdr-agent · cost: $9.7990 · model: claude-opus-4-7

@K5PTB

K5PTB commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

@ten9876 @AetherClaude I have the fixes ready for the issues that were identified. Do you want me to apply them to the PR?

@aethersdr-agent

Copy link
Copy Markdown
Contributor

@K5PTB Yes, please go ahead and push the fixes directly to this PR branch. Smaller, well-scoped follow-up commits are easier to review than a squashed force-push, so feel free to land them as individual commits with descriptive messages — happy to rebase/clean up at merge time if needed.

If any of the fixes touch the items called out in the PR body (the PTY symlink hardening, the UAF audit surface, or the ThemeManager migration), a one-line note in the commit message tying it back is appreciated. And if you'd like a follow-up issue opened for the color.background.success token proposal I mentioned, just say the word.

73, Jeremy KK7GWY & Claude (AI dev partner)


🤖 aethersdr-agent · cost: $0.7206 · model: claude-opus-4-7

K5PTB added a commit to K5PTB/AetherSDR that referenced this pull request May 25, 2026
Files were superseded by CatPort (per CatPort.h:25) and were never
added to CMakeLists.txt.  SmartCatServer.cpp:74 also called a 5-arg
SmartCatSession ctor that no longer exists (current sig has a
flexExtensions param between vfoB and parent), so the files would not
compile if anything linked them.

Flagged by aethersdr-agent review of aethersdr#3131.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
K5PTB added a commit to K5PTB/AetherSDR that referenced this pull request May 25, 2026
Track m_pttAssertedByMe in SmartCatProtocol — set in cmdTX(), cleared
in cmdRX(). SmartCatSession::onDisconnected() and ~SmartCatSession()
both call releasePtt(), which forces setTransmit(false, PttSource::Dax)
only if this protocol instance was the one that keyed the radio.

Guards against leaving the radio transmitting after an abrupt client
drop (crash, network blip, SIGKILL). Does not unconditionally force RX,
so PTT from the front panel, footswitch, or another CAT client is
unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
K5PTB added a commit to K5PTB/AetherSDR that referenced this pull request May 25, 2026
CatPort::onPtyData() calls ::write() on a non-blocking fd. Previously
the return value was silently discarded; now a qCWarning is emitted on
negative return so a full kernel buffer is diagnosable in the log.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ethersdr#2962)

CAT protocol coverage
─────────────────────
Full Kenwood TS-2000 command set, referencing the manual appendix, except
commands not applicable to a FlexRadio. FlexCAT (ZZFA/ZZFB/ZZMD/ZZFI/…)
extensions on a second port. Commands implemented across both dialects:

  • Frequency: FA/FB, ZZFA/ZZFB, IF/ZZIF, OI
  • Mode: MD/ZZMD, ZZME (extended mode IDs)
  • Filter: SL/SH, FW, ZZFI/ZZFJ
  • AGC: GT/ZZGT, ZZAR/ZZAS (per-slice fast/slow threshold)
  • Audio: AG/ZZAG, ZZLE/ZZLB/ZZLF (level/balance/AF), ZZMA/ZZAB (mute)
  • RF/Mic: PC/ZZPC, MG/ZZMC, RA/PA (attenuator/preamp)
  • Squelch: SQ
  • RIT: RC/ZZRC, RD/ZZRD, RU/ZZRU, RG/ZZRG, RT/ZZRT, ZZRW/ZZRY
  • XIT: XT/ZZXG, ZZXC/ZZXS
  • PTT / TX: TX (TX0 = PTT off per spec), RX, FT, ZZFT, FR, ZZSW
  • CW: KS/KY, PT (sidetone)
  • Noise: NB/NL, NR/NT, RL, ZZNL/ZZNR
  • Meter: SM/ZZSM
  • VFO step: UP/DN
  • Status: PS, ID, TY, BY, LK, RM
  • AI (async push): AI/ZZAI — frequency and mode push on change
  • FlexCAT-only: ZZBI, ZZDE, ZZFR

Why CAT in addition to rigctld
───────────────────────────────
Some external apps do not support rigctld, but expect to send TS-2000 CAT
commands, with extensions by FlexRadio. setups that currently use SmartCAT
can work directly with AetherSDR without change. TCI is preferred, but this
supports both legacy apps and legacy hams.

CAT applet redesign
────────────────────
The space-consuming configuration app only appears when popped out, avoiding
clutter of the right panel. The right-panel applet shows a compact per-port
status table (enabled, dialect, port, PTY path, client count). The pop-out
window adds full controls: enable toggle, dialect selector, port number,
symlink path. Right-click on the PTY path label → "Copy PTY Name".

Two TCP ports by default: 4532 (rigctld) and 5001 (Flex CAT). Up to 6
more ports can be configured/enabled. Each port gets a dedicated PTY
symlink in the per-user runtime directory (cat-A … cat-H).

Theme: all widget styles use ThemeManager token templates so the applet
tracks live theme changes (phase-5 switcher not yet built, but the
plumbing is in place per aethersdr#3028).

Test suites
────────────
Three C++ integration test executables (cmake targets):

  • rigctld_test   — 16 sections, TCP + PTY round-trip
  • CAT_TS-2000_test — 14 sections (TS-2000 dialect), TCP + PTY
  • CAT_Flex_test  — 15 sections (FlexCAT dialect), TCP + PTY

PTT (--ptt) and CW keyer (--cw) tests are disabled by default; enable
only with a dummy load connected. PTY defaults use QStandardPaths to
resolve the per-user symlink directory, matching CatPort::defaultSymlinkPath.

Integration testing
────────────────────
Tested end-to-end against:

  | App            | Dialect  | Transport |
  |----------------|----------|-----------|
  | N1MM Logger+   | TS-2000  | TCP       |
  | Not1MM         | rigctld  | TCP       |
  | WSJT-X         | rigctld  | TCP / PTY |
  | fldigi         | TS-2000  | TCP / PTY |
  | flrig          | TS-2000  | TCP       |
  | MacLoggerDX    | TS-2000  | PTY       |

Safety and security
────────────────────
  • TX safety: SmartCatProtocol tracks whether it asserted PTT
    (m_pttAssertedByMe); SmartCatSession::onDisconnected and ~dtor both
    call releasePtt() so an abrupt client drop cannot leave the radio keyed.
  • PTY symlinks: per-user directory (QStandardPaths::RuntimeLocation /
    CacheLocation), chmod 0700, atomic rename — no /tmp cross-user
    collisions, no TOCTOU window (GHSA-qxhr-cwrc-pvrm).
  • Dead code removed: SmartCatServer.{h,cpp} were unreachable (not in
    CMakeLists, broken 6-arg ctor); deleted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@K5PTB K5PTB force-pushed the feat/smartcat-tcp-server branch from dc0d18b to b801b63 Compare May 25, 2026 15:07
@K5PTB

K5PTB commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

@AetherClaude The fixes were added to this PR, along with some adjustments to the unit tests that I didn't catch earlier. However, it looks like my Claude squashed all of it together into the one feature, rather than a separate commit.

@aethersdr-agent

Copy link
Copy Markdown
Contributor

@K5PTB No worries at all — a single squashed feature commit is honestly fine here. Most projects (this one included) squash-merge PRs anyway, so the individual-commit granularity gets collapsed at merge time regardless. The only thing that's slightly harder is reviewing the fixes in isolation from the original work, but since you flagged what's in there (issue fixes + unit test adjustments), that gives me enough to navigate the diff.

Thanks for catching the test adjustments too — that's exactly the kind of thing that's easy to miss the first pass. I'll take a look through the updated diff and the CAT_TS-2000_test / CAT_Flex_test changes. If anything looks off I'll leave inline review comments rather than asking you to re-split history.

If you'd still like the color.background.success token follow-up issue opened, just say the word.

73, Jeremy KK7GWY & Claude (AI dev partner)


🤖 aethersdr-agent · cost: $0.4704 · model: claude-opus-4-7

@K5PTB

K5PTB commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

@AetherClaude:

If you'd still like the color.background.success token follow-up issue opened, just say the word.

I'll defer to whoever is doing the light/dark theme work to decide on that. It looks like that is still in progress. I just didn't want to impede them by replacing their rewrite of the Cat control applet.

@aethersdr-agent

Copy link
Copy Markdown
Contributor

@K5PTB Totally fair — and that's a considerate read of the situation. The theme work is mid-flight: Phase 4 (Default Light) just shipped in #3129, and Phase 5 PR 1 (Theme Editor dialog) is queued behind it, so new token proposals are best held until that work quiesces.

I'll leave the #006040 hardcode as-is in this PR with the existing comment marking it as intentional, and we can revisit the color.background.success token proposal once the Phase 5 dust settles. That way whoever picks up the next theme pass can decide whether to introduce it alongside other semantic-state tokens (success / warning / info), rather than as a one-off.

Thanks again for the fixes and the thoughtful coordination on this — the CAT applet replacement carrying forward the ThemeManager migration was exactly the right move.

73, Jeremy KK7GWY & Claude (AI dev partner)


🤖 aethersdr-agent · cost: $0.5661 · model: claude-opus-4-7

ten9876 added a commit that referenced this pull request May 25, 2026
Anticipated by the CAT-server PR (#3131): K5PTB had to hardcode
`#006040` for the CAT-active :checked button background because the
canonical taxonomy had `color.background.tx` (warm orange — TX
indicator) but no green sibling for the "success / active" state.

Adds the missing token in both bundled themes and the seedBuiltinDefaults
fallback so #3131 (and any future widget that needs a green-active
background tint) can use `{{color.background.success}}` instead of
hardcoding hex.

Values picked to match the existing accent-tier conventions:

  default-dark.json   → #006040 (matches K5PTB's hardcode + the
                        existing dim color.accent.success entry)
  default-light.json  → #c8e8d0 (pale green, light enough that dark
                        text reads on it — mirrors how background.tx
                        is a warm light cream in the light theme)
  seedBuiltinDefaults → #006040 (matches the dark theme so the seed
                        fallback path doesn't regress active widgets
                        if the JSON load somehow fails)

A follow-up tool-pass (tools/migrate_colours.py) can sweep
CatPopoutWindow.cpp and CatControlApplet.cpp once #3131 lands to
canonicalize the rest of the hardcoded styles in that PR (~8 more
hex literals, all already-canonical token matches).

73, Jeremy KK7GWY & Claude (AI dev partner)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ten9876 ten9876 merged commit 6ac93f3 into aethersdr:main May 25, 2026
5 checks passed
@K5PTB K5PTB deleted the feat/smartcat-tcp-server branch May 25, 2026 17:11
jensenpat pushed a commit that referenced this pull request Jun 5, 2026
## Summary

- Removes `src/core/RigctlServer.cpp/.h` and `src/core/RigctlPty.cpp/.h`
— all four were unwired by PR #3131 but not deleted
- Drops `src/core/RigctlPty.cpp` from `CORE_SOURCES` in `CMakeLists.txt`
(the only remaining build reference)
- Live-code grep confirms zero includes or symbol references remain;
GHSA comment citations retained as-is

## Test plan

- [x] `cmake --build` clean with `--target all` (RelWithDebInfo, macOS
M2)
- [x] Smoke test: AetherSDR launches, rigctld CAT control functional via
CatPort

Closes #3142

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…ethersdr#3131)

## CAT Protocol Coverage

- Full Kenwood TS-2000 command set, referencing the manual appendix,
except commands not applicable to a FlexRadio
- Full FlexCAT command set per SmartCAT User Guide v4.1.5, with the
exception of ZZPA/ZZPE (panadapter data streaming — out of scope for a
CAT server)
- Two TCP ports by default: 4532 (rigctld) and 5001 (FlexCAT). Up to 6
more ports can be configured/enabled; each 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 — handy when configuring external apps.

## Why CAT in addition to rigctld

Some external apps do not support rigctld, but expect to send TS-2000
CAT commands, with extensions by FlexRadio. AetherSDR now speaks both
protocols, giving operators the flexibility to use whichever their
software requires — setups that currently use SmartCAT can work directly
with AetherSDR without change. TCI is preferred, but this supports both
legacy apps and legacy hams.

## CAT applet redesign

Inspired by SmartSDR for macOS. The space-consuming configuration only
appears when popped out, avoiding clutter of the right panel.

## 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.

A firmware TX race was noted during CW testing: the radio briefly
reports TX=0 via CAT while the PA is still hot after CW completes.
Safety watches are added to the test suite; documented as known hardware
behaviour.

## Integration testing

Tested against real hardware (FlexRadio 6500, firmware 4.2.18). Primary
platform: macOS. Also tested on Raspberry Pi 5 / Raspberry Pi OS Trixie.

| 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) |

### 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.

## 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 — 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…sdr#3380)

## Summary

- Removes `src/core/RigctlServer.cpp/.h` and `src/core/RigctlPty.cpp/.h`
— all four were unwired by PR aethersdr#3131 but not deleted
- Drops `src/core/RigctlPty.cpp` from `CORE_SOURCES` in `CMakeLists.txt`
(the only remaining build reference)
- Live-code grep confirms zero includes or symbol references remain;
GHSA comment citations retained as-is

## Test plan

- [x] `cmake --build` clean with `--target all` (RelWithDebInfo, macOS
M2)
- [x] Smoke test: AetherSDR launches, rigctld CAT control functional via
CatPort

Closes aethersdr#3142

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <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.

Implement native SmartCAT TCP server — full FlexRadio and TS-2000 dialect support

2 participants