Skip to content

fix(cat): bare ZZTX; is a read, not a key — uncommanded TX in Flex dialect (#3625)#3629

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
K5PTB:fix/cat-zztx-read
Jun 16, 2026
Merged

fix(cat): bare ZZTX; is a read, not a key — uncommanded TX in Flex dialect (#3625)#3629
ten9876 merged 2 commits into
aethersdr:mainfrom
K5PTB:fix/cat-zztx-read

Conversation

@K5PTB

@K5PTB K5PTB commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Resolves #3625

Summary

In the Flex (ZZ-extension) CAT dialect, a parameter-less ZZTX; — which is a read of the transmit state — was parsed as a set and keyed the transmitter. External applications poll TX status with ZZTX; roughly once per second, so every poll caused an uncommanded transmission.

Root cause

ZZTX dispatched to the shared cmdTX(), which keys on any argument except 0. A bare ZZTX; arrives with an empty argument, doesn't match "0", and falls through to setTransmit(true)xmit 1.

Per the Kenwood/Flex GET/SET convention, ZZTX; (no parameter) is a read that must return ZZTX0;/ZZTX1;; only ZZTX1/ZZTX2 should key.

Evidence (from reporter debug logs, Flex vs TS-2000)

aether.cat:        CAT ← ZZTX
aether.cat:        SmartCAT "ZZTX" (set)      ← misclassified as a SET
aether.connection: TX: "Cnn|xmit 1"           ← keys the transmitter
  • Flex: 30 ZZTX polls → 30 xmit 1, exact 1:1.
  • TS-2000: the same app sent ZZTX 34 times → answered ?; (the dialect doesn't implement the ZZ extension) → 0 xmit. The bug is specific to the Flex dialect's ZZTX handler.

Severity: high — any Flex-dialect client that polls TX status continuously keys the transmitter (unintended emissions; possible interference / PA stress).

Fix (commit 1)

Handle the bare/? read in the ZZTX dispatch, returning the same authoritative state IF/ZZIF report (isRadioTransmitting()); only ZZTX0/1/2 reach cmdTX(). Plain Kenwood TX; keying is deliberately untouched (it is a set command in TS-2000).

Audit (commit 2 — kept separate)

Finding a defect like this, the responsible step is to check for siblings. I audited every handler in SmartCatProtocol (both the Flex/ZZ and TS-2000/base dispatch tables) for the same class — a bare/parameter-less read falling through to a state change:

  • Found two more, both in the Flex (ZZ-extension) dialect:

    • ZZFR; — unconditionally swapped VFO A↔B on every call.
    • ZZFT; — unconditionally toggled split on every call.

    Both now follow the GET/SET convention used by their twins (cmdFT, cmdZZSW): bare/? reads, explicit 0/1 sets. ZZFR gains an idempotent RX-VFO selector.

  • TS-2000 dialect: none. Read guards live inside the shared handlers, so they protect both dialects; the only intentional divergence is TX; keying, which is correct Kenwood behavior.

This is the second commit so a reviewer who prefers to scope the PR to the reported bug can drop the audit commit independently.

Testing

Verified live against a FlexRadio with CAT_Flex_test --ptt (keys the rig into a dummy load):

  • New regression checks: 10.0r/10.0r2/10.1r (ZZTX read vs key), 13.34r/13.34 (ZZFR read vs select), 8.78.9 (ZZFT read vs set).
  • 120/120 passed.

Built and tested on MacBook Air M2 (macOS) only. Not built on Linux/Windows for this change — it's a small, platform-agnostic CAT parsing fix.

🤖 Generated with Claude Code

K5PTB and others added 2 commits June 16, 2026 16:09
…lect)

In the Flex (ZZ-extension) dialect, the ZZTX handler routed every form
except ZZTX0 to cmdTX(), so a parameter-less "ZZTX;" — which is a READ of
the transmit state — fell through to setTransmit(true) and keyed the radio.
External apps poll TX status with bare ZZTX; once per second, so each poll
caused an uncommanded transmit (logs: 30 ZZTX polls -> 30 xmit 1, 1:1).
TS-2000 dialect is unaffected because it does not implement ZZTX (returns ?;).

Handle the bare/"?" read in the ZZTX dispatch, returning ZZTX0;/ZZTX1; from
the same authoritative state IF/ZZIF report (isRadioTransmitting()); only
ZZTX0/1/2 reach cmdTX() to change state. Plain Kenwood TX; keying is untouched.

Reworks CAT_Flex_test section 10: adds regression checks that bare ZZTX; reads
without keying (10.0r/10.0r2) and reflects the keyed state (10.1r); keying now
uses the explicit ZZTX1. Verified live: 116/116 with --ptt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audit of every SmartCatProtocol handler for the same defect class as the
ZZTX bug — a bare/parameter-less READ falling through to a state change —
turned up two more, neither exercised by the reporting app but reachable by
any client that polls them:

  - ZZFR: bare "ZZFR;" unconditionally swapped VFO A<->B every call.
  - ZZFT: bare "ZZFT;" unconditionally toggled split every call.

Both now follow the Kenwood/Flex GET/SET convention used by their twins
(cmdFT, cmdZZSW): a bare or "?" arg reads current state; an explicit 0/1
sets it. ZZFR gains an m_rxVfoB selector so RX-VFO select is idempotent and
the read is truthful (swaps the slice mapping only when the selection
changes). All other handlers were verified to already guard the read path.

Reworks CAT_Flex_test: 13.34r (bare ZZFR; reads without swapping) + reworked
13.34 (explicit ZZFR1/ZZFR0 select); 8.7-8.9 (bare ZZFT; reads without
toggling; ZZFT1/ZZFT0 set, consistent with ZZSW). Verified live: 120/120 --ptt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@K5PTB K5PTB requested review from a team as code owners June 16, 2026 21:34
@K5PTB K5PTB mentioned this pull request Jun 16, 2026
2 tasks

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving — this is a correct, well-scoped, and genuinely well-tested fix for a real and serious bug. Thanks for the thorough writeup and especially the audit. Notes below are minor / non-blocking.

The bug and fix check out

Confirmed cmdTX() keys on any argument except "0" — including an empty one — so a bare ZZTX; poll really did setTransmit(true), exactly as your logs show. Intercepting the empty/? read before it reaches cmdTX() is the right fix, and returning state from isRadioTransmitting() keeps the ZZTX0/1 reply consistent with the same source the IF/ZZIF report uses. ✅

The part I most wanted to verify — and it's safe

The substantive change is cmdZZFR adding m_rxVfoB next to the existing std::swap(m_vfoA, m_vfoB). New parallel state beside existing state is the classic place a desync bug hides, so I checked: m_vfoA/m_vfoB are swapped in exactly one place in the file — cmdZZFR itself — and read-only everywhere else; cmdZZSW doesn't touch the mapping. Because m_rxVfoB is written in the same function as the only swap, the two can't drift out of sync, and the if (wantB != m_rxVfoB) idempotency guard is correct. 👍

Minor notes (non-blocking)

  1. ZZFT set-path doesn't validatem_splitEnabled = (arg == "1") silently treats any non-1 value (e.g. ZZFT2) as "off", while your new ZZFR is stricter and returns ?; for invalid input. It's defensible as-is because it matches the twins cmdFT and cmdZZSW, so this is really just an observation about the asymmetry with ZZFR rather than a request to change anything.
  2. Built on macOS only — noted in the PR, and it's pure-QString CAT parsing with no platform specifics (I rebuilt main on Linux clean), so CI covers the rest. Just flagging since the project bar is cross-platform.

What's especially good

  • The audit commit is the right instinct done well: finding ZZTX and immediately sweeping both dialects' dispatch tables for the same defect class, catching ZZFR and ZZFT, and keeping it as a droppable separate commit.
  • The regression tests assert both the read result and the absence of the side effect (10.0r2 re-checks ZZIF TX='0' after a delay to prove no key; 13.34r confirms ZZFA is unchanged after a bare ZZFR). That's exactly how you pin a "read must not mutate" contract.
  • Correctly leaves plain Kenwood TX; keying untouched.

High-severity fix (uncommanded emissions), correctly resolved. 🚀

@ten9876 ten9876 merged commit 8e52db6 into aethersdr:main Jun 16, 2026
6 checks passed
@K5PTB K5PTB deleted the fix/cat-zztx-read branch June 16, 2026 22:25
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.

CAT TCP - Flex and TS-2000 - Bugs

2 participants