Skip to content

fix(gui): route keyboard/space PTT through the Quindar coordinator (#3610)#3798

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/3610-quindar-keyboard-ptt
Jun 26, 2026
Merged

fix(gui): route keyboard/space PTT through the Quindar coordinator (#3610)#3798
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/3610-quindar-keyboard-ptt

Conversation

@jensenpat

Copy link
Copy Markdown
Collaborator

Issue

#3610 — Quindar tones fire for the GUI MOX button but not for space-bar PTT or the keyboard MOX/TX shortcut (T). The radio keys and mic audio is sent; only the Quindar intro/outro is missing. Confirmed by the reporter on a real station (QUIN enabled, USB/phone mode).

Root cause

There were two divergent PTT paths and only one drove the Quindar state machine:

  • GUI MOX button (TxApplet.cpp) and TCI hardware PTT (TciServer.cpp) call TransmitModel::requestPttOn/Off() — the single coordinator that sequences the tone around the MOX edge (startIntro() before key-up; startOutro() with the unkey deferred by the outro duration).
  • Space-bar PTT and the mox_toggle shortcut (MainWindow_Shortcuts.cpp) called RadioModel::setTransmit()TransmitModel::setTransmitting(), the raw state setter that emits xmit 0/1 and moxChanged but never touches the Quindar coordinator.

So with QUIN enabled on a phone mode, keyboard PTT keyed the radio with no tone, while the MOX button worked — exactly the reported behaviour. (PttSource::Footswitch was defined but never wired, consistent with the reporter's read that this routing was intended but unfinished.)

Fix

Point the space-bar event-filter path and the mox_toggle shortcut at requestPttOn/Off(PttSource::Mox) — the same coordinator the working MOX button uses. Minimal, signal-routing only; no FlexLib protocol change.

No gating is lost by the redirect: requestPttOn/Off still terminate in an xmit 0/1 command, and RadioModel's xmit handler applies the identical interlock-arm / m_txRequested / txAudioGate-teardown gating that setTransmit() did inline. The coordinator's preflight runs the same local interlock check (localPttInterlockMessage). The only added behaviour is the Quindar intro/outro.

-                    m_radioModel.setTransmit(true);
+                    m_radioModel.transmitModel().requestPttOn(TransmitModel::PttSource::Mox);
...
-                    m_radioModel.setTransmit(false);
+                    m_radioModel.transmitModel().requestPttOff(TransmitModel::PttSource::Mox);

How the agent automation bridge proved it

The fix makes the keyboard path call the identical requestPttOff(PttSource::Mox) the MOX button calls. The MOX button is reachable from the bridge, so I exercised that exact coordinator path on live hardware (FLEX-8400M, TX slice on LSB, TX antenna ANT1 dummy load — re-confirmed from live state before keying; force-unkeyed and verified transmitting == false after each cycle).

The coordinator's observable, bridge-readable effect is the deferred unkey: requestPttOff holds the radio keyed for the outro window before sending xmit 0, so get transmit → transmitting stays true after the unkey command. Driving invoke MOX transmit setChecked while polling transmitting:

Condition UNKEY command → transmitting:false
QUIN disabled (coordinator falls straight through) 23 ms — immediate
QUIN enabled (outro sequenced) 535 ms — deferred outro window

The ~0.5 s deferral with QUIN on vs. immediate with QUIN off confirms the Quindar outro sequencing runs on this build over real hardware. Since the patched space-bar / mox_toggle paths now call that same requestPttOff(PttSource::Mox), they inherit that behaviour — the previously-silent keyboard PTT now drives the full intro/outro.

Agent automation bridge — gaps found

A direct empirical "press the physical Space bar and watch Quindar" check was not possible through the bridge. Surfacing these so we can close them:

  • No key-event injection. The bridge invoke targets named widgets / visible menu actions; the space-bar PTT is an application-level QKeyEvent filter and mox_toggle is a global QShortcut — neither is reachable, so the bridge can't drive the exact code path this fix changes. (Drove the equivalent requestPttOff via the MOX button instead.)
  • Quindar firing isn't observable. No quindar phase/active field in any get snapshot and ClientQuindarTone has no log category. The only proxy is the deferred-unkey timing; the tone itself and the quindarActiveChanged chip flash can't be read. Proposed: add quindarPhase/quindarActive to the transmit snapshot, or an lcQuindar log line on intro/outro start.
  • QUIN enable chip not in dumpTree. The chip is an unnamed checkable QPushButton, so the bridge can't read or toggle Quindar state. Proposed: give it an accessibleName.
  • Concurrent automation instances collide. Multiple AETHER_AUTOMATION instances share one fixed QLocalServer name + one discovery file (<temp>/aethersdr-automation.json) and removeServer() each other's socket; they also share the settings file and log dir. Worked around with AETHER_AUTOMATION_SOCKET=<unique> and a direct socket connect. Proposed: make the discovery file / socket name per-PID, or index the discovery file by pid.

Scope note

Other client-handled PTT sources share this root cause (MIDI, RC-28, FlexControl MOX, the serial footswitch — PttSource::Footswitch's intended home). They're left as a deliberate follow-up: they need their respective hardware to verify, and CAT/rigctl/TCI-digital paths (PttSource::Dax) should not get the deferred unkey (programmatic, timing-sensitive). As the reporter noted, PTT commanded directly to the radio over FlexLib with no client involvement (external footswitch box) can't get a true client-generated outro — worth a line in the QUIN editor dialog.

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat

…ethersdr#3610)

Space-bar PTT and the keyboard MOX/TX toggle (T) keyed the radio via
RadioModel::setTransmit() -> TransmitModel::setTransmitting(), the raw
state setter that emits an `xmit` command but never touches the Quindar
intro/outro state machine. Only the GUI MOX button and TCI hardware PTT
went through TransmitModel::requestPttOn/Off(), the single coordinator
that sequences the tone around the MOX edge. So Quindar was silent for
keyboard PTT even with QUIN enabled on a phone mode, while the MOX
button worked — exactly as reported.

Point the space-bar event-filter path and the mox_toggle shortcut at
requestPttOn/Off(PttSource::Mox) instead, so the keyboard path now runs
the same coordinator the working MOX button uses. requestPttOn/Off still
terminate in an `xmit 0/1` command, which RadioModel's xmit handler
gates identically (interlock arm, m_txRequested, txAudioGate teardown),
and the coordinator's preflight applies the same local interlock check —
so no gating is lost; the only added behaviour is the Quindar sequence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jensenpat jensenpat marked this pull request as ready for review June 25, 2026 20:15
@jensenpat jensenpat requested a review from a team as a code owner June 25, 2026 20:15

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

Nice fix — clean, minimal, and the root-cause analysis is correct. I traced both PTT paths and the equivalence claim holds:

The redirect is safe. The new path emits commandReady("xmit 1/0") from TransmitModel::setMox(), and RadioModel's commandReady handler (RadioModel.cpp:494-511) applies the same armInterlockNotification / m_txRequested / txAudioGate-teardown gating that setTransmit() did inline. More to the point: the change makes keyboard/space PTT call the identical requestPttOn/Off(PttSource::Mox) entrypoint the already-working GUI MOX button uses (TxApplet.cpp:374-376), so it inherits exactly that button's interlock and gating behavior — there's no new code path to get wrong.

It also matches documented intent. TransmitModel.h:148-150 states "All UI and TCI hardware-PTT callers should use this path so Quindar tones happen consistently regardless of source." The space-bar filter and mox_toggle shortcut were the outliers; this aligns them.

Edge cases I checked, all fine:

  • Interlock block on key-down: m_spacePttActive is set true before the (now-blocked) requestPttOn; the matching key-up still calls requestPttOff, which short-circuits (phase == Idle) to a harmless xmit 0. Same net effect as before.
  • Re-key during the outro window: a fast space re-press lands in requestPttOn's coalesceReEngage path (phase Disengaging), cancelling the pending unkey — the intended coalescing, not a stuck state.
  • Non-phone / CW: isPhoneModeForQuindar() gates the tone, so CW PTT via space is unchanged — no deferred unkey, no regression.
  • RADE EOO: unkey now routes through dispatchMoxOff()'s pttOffHook, same as the MOX button — a consistency improvement, and a path the button already exercises.

One minor semantic note (non-blocking): both space-bar (momentary, footswitch-like) and T (toggle, MOX-like) are tagged PttSource::Mox. That's the pragmatic choice to match the working button, and you've already called out PttSource::Footswitch wiring as deliberate follow-up in the scope note — just flagging that the source label on interlock notifications for space PTT will read as MOX. Fine to leave as-is.

Thanks for the thorough writeup and the bridge-gap reporting — the deferred-unkey timing table is a clever way to prove the coordinator path runs end-to-end. LGTM.


🤖 aethersdr-agent · cost: $4.2290 · model: claude-opus-4-8

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

Reviewed the change against the surrounding TX path — this is correct, minimal, and well-scoped. Thanks for the careful writeup, @jensenpat.

Correctness — verified against the codebase

The strongest argument for this fix is that the keyboard paths now call byte-identical code to the already-shipping GUI MOX button (TxApplet.cpp:374-376requestPttOn/Off(PttSource::Mox)). I traced the gating claims and they hold:

  • Preflight interlock preserved — the coordinator's preflight is wired to localPttInterlockMessage (RadioModel.cpp:457), the same check the old inline setTransmit() ran (RadioModel.cpp:1445).
  • xmit gating preservedrequestPttOn/Off terminate in setMox()/dispatchMoxOff()commandReady("xmit 0/1"), and the commandReady handler (RadioModel.cpp:492-511) applies the same armInterlockNotification / m_txRequested / txAudioGate-teardown gating. It additionally runs transmitStartBlockedByInhibit, which the old inline keyboard path skipped — so this is a small consistency gain with the MOX button, not a regression.
  • Toggle logic equivalentmox_toggle's isTransmitting() ? Off : On matches the prior setTransmit(!isTransmitting()).
  • No null-pointer / RAII / settings concerns; m_quindarTone is null-checked inside the coordinator, and transmitModel() returns a member reference.

Conventions / scope

Single file, signal-routing only, no FlexLib/protocol change — matches the stated scope. Comments are clear and reference the issue.

One behavioral note (not blocking)

With QUIN enabled on a phone mode, requestPttOff now defers the unkey by the outro window (~0.5 s) for space-bar PTT too. That's intended and matches the MOX button, but it does mean releasing Space no longer unkeys instantly in that one configuration. Given the deferred-unkey is exactly the Quindar behavior the issue asks for — and is consistent across all GUI PTT sources now — I think that's the right call; just flagging it explicitly for the record.

The deliberate follow-up scoping (MIDI/RC-28/FlexControl/footswitch, and keeping PttSource::Dax off the deferred unkey) is the right boundary. LGTM.


🤖 aethersdr-agent · cost: $2.2069 · model: claude-opus-4-8

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

Traced the full TX-keying path and verified the redirect is safe by equivalence — space-bar/mox_toggle now call the exact requestPttOn/Off(PttSource::Mox) the production MOX button uses.

Confirmed no gating is lost: requestPttOn → setMox → emit commandReady("xmit") → RadioModel's commandReady handler (RadioModel.cpp:472), which applies the same interlock-arm / m_txRequested / txAudioGate-teardown / sendCmd as the old inline setTransmit, plus an extra transmitStartBlockedByInhibit gate, plus the local interlock preflight (runPttPreflight). With QUIN off, requestPttOff falls straight through to an immediate dispatchMoxOff() — zero behavior change when Quindar is disabled; the tones/deferral only run with QUIN on, which is the fix.

Nice empirical proof of the coordinator over real hardware (deferred unkey 23ms QUIN-off vs 535ms QUIN-on via the MOX button, since the bridge can't inject a raw Space key event). Good scoping: other client PTT sources deferred (need hardware), and PttSource::Dax correctly excluded from the deferred unkey. Thanks @jensenpat.

@ten9876 ten9876 merged commit 85d53d9 into aethersdr:main Jun 26, 2026
6 checks passed
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.

2 participants