feat(automation): Panadapter and Waterfall bridge surface — antennas, CWX, floors, pan mgmt (#3646)#3832
Conversation
… mgmt (aethersdr#3646) Expands the agent automation bridge with the verbs and read-fields a TX/spectrum behavioural test actually needs. All exercised live against a FLEX radio while reproducing/verifying the aethersdr#3804 FFT-floor work. New verbs: - slice txant <ANT> / rxant <ANT> — set TX/RX antenna deterministically. The GUI controls are blocking QMenu::exec() popups invoke() can't drive; these route through SliceModel::setTx/RxAntenna. Reading txAntenna back is what makes the dummy-load TX-safety gate enforceable at all. - floors — lightweight per-pan FFT noise-floor read (displayFloorDbm / noiseFloorDbm / panIndex). dumpTree serialises ~2600 widgets (~250 ms), far too slow to sample a sub-second post-TX transient; this touches only widgets exposing the floor Q_PROPERTY (~15 ms, pollable at 20+ Hz). - cwx send <text> | speed <wpm> | stop — drive the CWX keyer. CW's rapid TX→RX edges are the easy reproduction vehicle for post-TX floor recovery. send is gated by AETHER_AUTOMATION_ALLOW_TX and arms the force-unkey watchdog; speed/stop do not key. - txwaterfall on|off — toggle the radio's show_tx_in_waterfall flag so a test can confirm keyed-up TX paints the waterfall, not just the trace. - pan create | remove <id> | center <mhz> — add/remove a panadapter and recenter it (the band-change lever; plain tune uses autopan=0 so it clamps to the pan's RF range). Enables multi-band test coverage. New read-fields / properties: - txAntenna on the slice snapshot (get slice) — previously unreadable, so the live TX antenna couldn't be asserted before keying. - showTxInWaterfall on the transmit snapshot (get transmit). - noiseFloorDbm / displayFloorDbm / panIndex Q_PROPERTYs on SpectrumWidget, surfaced generically via QObject::property() in describeWidget (dumpTree) and the floors verb — no core->gui coupling. displayFloorDbm reflects the smoothed trace (m_smoothed); noiseFloorDbm tracks the raw frame. Known follow-ups (not in this PR): RadioModel::removePanadapter sends `display pan close` which doesn't close a panafall-created pan; and grab can't target a specific pan's SpectrumWidget in a multi-pan layout (returns the first) — a `grab pan <index>` selector would close that. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Reviewed the diff against the codebase — all referenced APIs resolve cleanly (SliceModel::txAntenna()/setTxAntenna()/setRxAntenna()/txAntennaList()/rxAntennaList(), TransmitModel::showTxInWaterfall(), RadioModel::createPanadapter()/removePanadapter()/setPanCenter()/maxPanadapters()/cwxModel(), CwxModel::send()/setSpeed()/clearBuffer(), and SpectrumWidget::panIndex()/m_smoothed/estimateNoiseFloorDbm). The generic QObject::property() read keeps core decoupled from the GUI class — nice approach. CI is all green and everything is correctly behind AETHER_AUTOMATION. Two notes, one worth addressing before merge.
1. The TX-safety watchdog can't actually stop a CWX transmission (worth a look).
doCwx("send", …) arms the watchdog by setting m_txKeyedSinceMs, matching txtest/key. But when the watchdog fires, forceUnkey() only does:
tx.stopTune();
tx.setMox(false);CWX keys the transmitter through the CW keyer buffer, not MOX or TUNE — so neither call cuts a CWX transmission in flight. A long cwx send string at low WPM can exceed m_txMaxKeyMs (default 20 s); the watchdog would log a force-unkey while the radio keeps keying until the buffer drains on its own. The PR body states cwx send "arms the force-unkey watchdog, same as txtest/key" — it arms it, but the backstop is a no-op for this keying path, which undercuts the safety claim.
Suggest making forceUnkey() also abort the keyer when a radio model is present:
void AutomationServer::forceUnkey(const char* reason)
{
if (!m_radioModel)
return;
auto& tx = m_radioModel->transmitModel();
tx.stopTune();
tx.setMox(false);
m_radioModel->cwxModel().clearBuffer(); // also cut a CWX transmission
m_txKeyedSinceMs = 0;
...
}(cwx stop already does clearBuffer(), so this is the same lever the manual abort uses.)
2. Minor: the doFloors() comment says the payload is "keyed by panIndex," but it builds a flat QJsonArray with panIndex as a field on each entry. Cosmetic — just align the comment with the shape.
Everything else looks good: null-guards on m_radioModel/slice resolution are in place, the antenna-list validation with an empty-list fallback is sensible, and the pan create/remove guards (limit check, refuse-last-pan) are the right shape. The two known follow-ups you documented (panafall-created pan teardown, grab pan <index>) are reasonable to defer.
Thanks for the thorough write-up and the honest bench-vs-live caveat on the companion #3831 — that kind of transparency about what the test does and doesn't prove is appreciated. Just the watchdog/CWX gap is worth closing before this lands.
🤖 aethersdr-agent · cost: $7.2988 · model: claude-opus-4-8
…unaway cwx send The TX-safety watchdog arms on `cwx send` (m_txKeyedSinceMs) and calls forceUnkey() past the limit, but forceUnkey only did stopTune() + setMox(false). CWX keying is driven by its own buffer, largely independent of MOX, so setMox(0) alone does not abort an in-flight `cwx send` — the watchdog would re-fire every tick with no effect until the buffer drained, defeating the 'can never leave a live transmitter on' guarantee. Add cwxModel().clearBuffer() (the same abort `cwx stop` uses) to forceUnkey so the all-stop actually covers CWX keying. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Pushed a small TX-safety fix on top ( The gap: The fix: Everything else in the PR is solid (non-keying verbs correctly ungated, |
ten9876
left a comment
There was a problem hiding this comment.
Reviewed in depth earlier — the bridge expansion is solid: non-keying verbs (slice txant/rxant, floors, txwaterfall, pan create/remove/center) correctly ungated; txAntenna/showTxInWaterfall and the noiseFloorDbm/displayFloorDbm/panIndex Q_PROPERTYs surfaced generically via QObject::property() (no core→gui coupling); cwx send properly ALLOW_TX-gated and arming the watchdog.
Transparency note: the one TX-safety gap I found in review — forceUnkey() (the watchdog all-stop) only did stopTune()+setMox(false), which can't abort an in-flight cwx send since CWX is buffer-driven independent of MOX — I closed by pushing f51f4b0, adding cwxModel().clearBuffer() to forceUnkey. So I co-authored that one commit; the rest is @jensenpat's work. CI is green on the fixed head.
Caveat for the record: the CWX-abort is logic/build-verified but not yet on-hardware-confirmed — worth a real-radio check that a long cwx send now force-unkeys at the watchdog limit. Not a blocker. Thanks @jensenpat.
## Release prep for v26.6.5 Docs + version-string updates for the **50 merges** since v26.6.4. No code change — safe to merge without a rebuild. ### Changes - **CHANGELOG.md** — new `[v26.6.5] — 2026-06-28` section, headlined by **KiwiSDR receive sync** (#3872), **SmartMTR TX meters** (#3776), the **PROF profile-switcher applet** (#3829), and a large **agent automation bridge** expansion (#3851/#3856/#3883/#3842/#3832/#3819); categorized Added/Changed/Fixed/Performance/Removed compiled from the full merge list. Folds in the prior `[Unreleased]` entries and resets `[Unreleased]` to empty. - **CMakeLists.txt / README.md / AGENTS.md** — `26.6.4` → `26.6.5`. - **ROADMAP.md** — current cycle → `post-v26.6.5`; four v26.6.5 highlights added atop *Recently shipped*. ### Not included - Tagging `v26.6.5` is a separate, explicit step (not done here). - The untracked `docs/aetherd-headless-engine-design.md` RFC and `prototypes/` are intentionally left out. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Expands the agent automation bridge with the verbs and read-fields a TX/spectrum behavioural test actually needs. Every one was exercised live against a FLEX radio while reproducing and verifying the #3804 FFT-floor work (companion fix PR #3831). No behaviour changes outside
AETHER_AUTOMATION.New verbs
slice txant <ANT>/slice rxant <ANT>QMenu::exec()popupsinvoke()can't drive. Routes throughSliceModel::setTx/RxAntenna.floorsdisplayFloorDbm/noiseFloorDbm/panIndex), ~15 ms vsdumpTree's ~250 ms — fast enough to sample a sub-second post-TX transient.cwx send <text>/cwx speed <wpm>/cwx stopsendonlytxwaterfall on|offshow_tx_in_waterfallso a test can confirm keyed TX paints the waterfall.pan create/pan remove <id>/pan center <mhz>tuneusesautopan=0and clamps to the pan's range). Multi-band coverage.New read-fields / properties
txAntennaon the slice snapshot (get slice) — previously unreadable, so the live TX antenna couldn't be asserted before keying. This is what makes a dummy-load TX-safety gate enforceable.showTxInWaterfallon the transmit snapshot (get transmit).noiseFloorDbm/displayFloorDbm/panIndexQ_PROPERTYs onSpectrumWidget, surfaced generically viaQObject::property()indescribeWidget/floors— no core→gui coupling.displayFloorDbmreflects the smoothed trace (m_smoothed);noiseFloorDbmtracks the raw frame.Safety
cwx sendrides the existingAETHER_AUTOMATION_ALLOW_TXrail and arms the force-unkey watchdog, same astxtest/key. Antenna/floor/pan verbs don't key.Known follow-ups (not in this PR)
RadioModel::removePanadaptersendsdisplay pan close, which doesn't close a panafall-created pan — the second pan had to be torn down via slice removal.grabreturns the firstSpectrumWidgetin a multi-pan layout — agrab pan <index>selector (keyed on the newpanIndex) would let a driver capture a specific pan.💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat