ATU pre-tune: add Auto-mode safety nets — class filter, point cap, power warning, audible cue (#2649). Principle IV.#3050
Merged
Conversation
…wer warning, audible cue (#2649). Principle IV. Lands the four enhancements approved for the first batch on issue #2649: - #4 Max-points soft cap (kMaxPointsSoftCap = 100): warns the operator before kicking off a sweep large enough to keep the radio transmitting unattended for tens of minutes. Catches the "select all bands on a wide regional plan" footgun. - #5 Power-level guardrail (kAutoModeTunePowerWarnW = 20): warns — never refuses — when tune power exceeds 20 W on entering Auto mode. Some amps legitimately need higher drive (Principle XIII: the operator outranks the agent); the gate exists only to force a deliberate choice. - #7 Audible cue: QApplication::beep() on per-point-timeout and on the consecutive-fail-bypass hard stop, so the operator notices terminal-fail events even when not watching the dialog. - #8 License-class filter: data-driven via a new optional license_classes block in the bandplan JSON. ARRL US declares Technician/General/Extra; IARU/RAC/SSA plans leave it absent, so the combobox stays hidden and the feature is inert for those operators. When a class is selected, segments whose non-empty license field doesn't include the code are dropped BEFORE the contiguous-region merge, so an allowed CW segment adjacent to a disallowed phone segment stays as two separate regions instead of merging and reintroducing the disallowed centers. Empty-license segments (BCN markers, general-purpose) always pass through. Stored class is preserved across plan switches; unknown stored codes default to "no filter" rather than auto-picking the first entry. Principle IV (Region-Aware Data Comes From BandPlanManager): the license-class filter is the principle's natural extension — what segments are reachable for a given operator is region- AND class-dependent, and the filter routes through BandPlanManager::contiguousRegionsForBand() (new three-arg overload) rather than hardcoding US-centric class tables in C++. The new license_classes JSON block lets future regional plans add their own class structure with zero C++ changes. Principle V (nested JSON): the operator's class is stored as a single new top-level key OperatorLicenseClass per the maintainer's call-out that no Operator block exists today to nest under; future Operator block (callsign + QTH + class) can fold this key in. Blast radius: risk_score=0.194, 4 high-risk affected (top: MainWindow, buildMenuBar, buildUI). All structural callers reach BandPlanManager via its header; changes are purely additive (new accessor, new overload, new optional JSON field) and the two-arg overload now delegates to the three-arg form with empty allowedClass — MainWindow::startSwrSweep behavior is byte-identical. No new test-target link dependencies introduced. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Contributor
Author
There was a problem hiding this comment.
Thanks for the well-scoped safety-nets PR. Read through all four guards — they hang together cleanly.
Looks good
BandPlanManager::contiguousRegionsForBand(..., allowedClass)filters before the sort+merge, so an allowed segment adjacent to a disallowed one correctly stays separate (no reintroduced-center bug). The empty-license passthrough for BCN/general segments matches the existing plan data.- Backwards-compatible overload + opt-in
allowedClasskeeps non-class-aware plans on the old behavior. - Class-filter UI is hidden entirely when the active plan has no
license_classesblock, andpopulateLicenseClassRow()defaults to "no filter" (not auto-pick) when the stored class isn't in the current plan — that's the right safety floor. - The point-cap and tune-power gates are warn-not-refuse with
QMessageBox::Cancelas the default button, which matches Principle XIII for the power gate. - Null/visibility guards on
m_radio,m_bandPlan,m_licenseClassRow, andm_licenseClassCombolook correct throughout.kSecondsPerPointEstimate = 15already existed in the file (verified), so the time estimate math is sound. - AppSettings usage is consistent with the rest of the repo (
setValue+save(); no QSettings).
Minor observations (non-blocking)
populateLicenseClassRow()is only called once in the ctor. If the active band plan changes while the dialog is open, the license-class row won't refresh. Probably fine since the dialog is typically short-lived, and it matches the existing pattern (noplanChangedwiring elsewhere in the dialog) — just flagging in case a future plan-switch entry-point opens this up.selectedLicenseClass()correctly returns empty when the row is hidden, so a class stored in AppSettings from a previous class-aware plan won't silently filter a non-class-aware plan. Nice.
No bugs, no scope creep, ship it.
🤖 aethersdr-agent · cost: $9.2487 · model: claude-opus-4-7
This was referenced May 24, 2026
aethersdr-agent Bot
added a commit
that referenced
this pull request
May 24, 2026
…. Principle XIII. After 3 consecutive TUNE_FAIL_BYPASS results, advance m_currentIndex past the remaining points on the dying band rather than aborting the whole sweep. Multi-band runs keep producing useful results when only one band's antenna is bad (e.g. broken feedline on 80m while 40m/20m/15m are fine). - Last-band / single-band failure: the skip loop body never executes, beginNextPoint() increments past m_points.size() and falls through to finishSweep() — matches the prior terminal-abort behaviour for those cases (covers the test-plan edge cases). - First point of a new band fails-bypasses: counter is reset to 0 before the recursion, so three fresh fails are required to trigger another per-band skip. - m_skipCount is incremented by (current - skipStart), not (+ 1): the triggering point already incremented m_failCount, so only the remaining points on the dying band count as skips. - Audible cue (QApplication::beep) preserved from PR #3050 item #7. - Message includes the next band name when one exists so the operator sees what is about to run. Principle XIII (The Operator Outranks Every Agent): the operator with one bad antenna outranks a defensive over-abort — give them the useful remainder of the sweep rather than the binary "every band aborted" verdict the previous safety net imposed. Blast radius: risk_score=0.0504, 5 high-risk affected (top: RadioModel::transmitModel, RadioModel::isConnected, SliceModel::sliceId). All five are downstream-transitive callees of onAtuStateChanged whose behaviour and call sites are unchanged by this patch — the edit modifies only the consecutive-fail-bypass branch's control flow.
4 tasks
aethersdr-agent Bot
added a commit
that referenced
this pull request
May 25, 2026
… Principle IV. Populates rac-canada.json with the license_classes block introduced in #3050 using Canada's ISED RIC-3 / RBR-4 frequency authorizations: - B = Basic (VHF/UHF only, >= 30 MHz) - BH = Basic with Honours (all amateur bands) - A = Advanced (all amateur bands) All HF segments (1.8-29.7 MHz) are annotated "BH,A"; all segments at 50 MHz and above are annotated "B,BH,A"; receive-only beacon markers keep `license: ""` so they pass through the filter unchanged. The description is updated to make clear the class overlay is sourced from ISED (the federal regulator), not from the RAC band-plan document, which itself does not define class-based sub-segments. For SSA Sweden, the description is updated to document why no license_classes block is added: PTSFS 2025:1 treats Swedish amateurs as a single qualification tier (the older CEPT Class 1 / Class 2 split was retired), so per-segment class filtering would be misleading. The ATU pre-tune dialog hides the class combobox when no license_classes block is present (AtuPreTuneDialog::populateLicenseClassRow, #2649), so this is correct no-op behavior — the description records the reasoning so a future contributor doesn't retry the addition. IARU Region 1/2/3 plans are intentionally left unchanged: those documents are mode-allocation-by-band, not class-allocation, and operators wanting class filtering should select their national plan (arrl-us, rac-canada) as the active plan. Pure data change — no .cpp files modified, no test-target link surface affected, no codegraph impact analysis applicable (the dialog code path is data-driven and already in place from #3050). Principle IV. Region-Aware Data Comes From BandPlanManager: the class overlay is added to the per-region JSON files that drive BandPlanManager, keeping the regional-data source authoritative rather than introducing a parallel class-rules table. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
aethersdr-agent Bot
added a commit
that referenced
this pull request
May 25, 2026
…gionsForBand (#3060). Principle IV. PR #3050 (closing #2649) added a license-class-aware overload of BandPlanManager::contiguousRegionsForBand that filters segments by the operator's class before merging contiguous regions. The filter-BEFORE-merge ordering is load-bearing: if an allowed segment is adjacent to a disallowed one, they must NOT merge — otherwise the merged region's center could fall on a disallowed frequency. The implementation comment calls this out, but no test pinned the invariant. A future refactor (STL-algorithm consolidation, parallelisation of the segment loop) could silently re-introduce the bug. This commit adds tests/band_plan_license_filter_test.cpp covering the five scenarios enumerated in the issue: 1. Empty class → no filter (three-arg with "" == two-arg). 2. Empty-license segments (BCN markers) always pass. 3. Class filter drops non-matching segments [E]/[G]/[E,G] under T/G/E. 4. Adjacent allowed/disallowed segments stay separate (THE invariant). 5. Trailing-whitespace allowedClass is trimmed; "GE" doesn't match "G,E" substring-style; lowercase doesn't match uppercase. To make scenario 5 pass and to keep the comparison symmetric, the allowedClass argument is now .trimmed() at the top of the filter — the segment-side code was already trimmed per element. This is a one-line forgiveness fix; existing callers that pass already-trimmed strings (MainWindow::startSwrSweep, AtuPreTuneDialog::populateBands) are unaffected. A small public test hook BandPlanManager::setSegmentsForTest is added so the test can inject synthetic segments without going through resource loading or AppSettings. Matches the *ForTest naming convention already used in PerfTelemetry::resetForTest and AetherAx25LibmodemShim::processRecoveredBitsForTest. Honors Principle IV (region-aware data comes from BandPlanManager, not BandDefs.h) by hardening the regression-guard around the manager's license-class filter — exactly the surface a refactor cleanup pass would be tempted to consolidate. Blast radius: risk_score=0.198, 4 high-risk affected (top: MainWindow::MainWindow, MainWindow::buildMenuBar, MainWindow::buildUI). Header change is purely additive (new public inline setSegmentsForTest) — no existing API touched; the high-risk callers don't reference the new symbol and aren't behaviorally affected, only a recompile cascade. The .cpp change adds .trimmed() to one argument — forgiveness-only (strings that previously failed strict comparison may now match); no caller currently passes whitespace. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
G6PWY-Chris
pushed a commit
to G6PWY-Chris/AetherSDR
that referenced
this pull request
Jun 22, 2026
…wer warning, audible cue (aethersdr#2649). Principle IV. (aethersdr#3050) ## Summary Fixes aethersdr#2649 ### What was changed ATU pre-tune: add Auto-mode safety nets — class filter, point cap, power warning, audible cue (aethersdr#2649). Principle IV. ### Files modified - `resources/bandplans/arrl-us.json` - `src/gui/AtuPreTuneDialog.cpp` - `src/gui/AtuPreTuneDialog.h` - `src/models/BandPlanManager.cpp` - `src/models/BandPlanManager.h` ``` src/gui/AtuPreTuneDialog.cpp | 145 ++++++++++++++++++++++++++++++++++++++- src/gui/AtuPreTuneDialog.h | 4 ++ src/models/BandPlanManager.cpp | 34 ++++++++- src/models/BandPlanManager.h | 20 ++++++ 5 files changed, 203 insertions(+), 5 deletions(-) ``` --- Generated by AetherClaude (automated agent for AetherSDR) --- <sub>🤖 aethersdr-agent · cost: $28.0998 · model: claude-opus-4-7</sub> Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #2649
What was changed
ATU pre-tune: add Auto-mode safety nets — class filter, point cap, power warning, audible cue (#2649). Principle IV.
Files modified
resources/bandplans/arrl-us.jsonsrc/gui/AtuPreTuneDialog.cppsrc/gui/AtuPreTuneDialog.hsrc/models/BandPlanManager.cppsrc/models/BandPlanManager.hGenerated by AetherClaude (automated agent for AetherSDR)
🤖 aethersdr-agent · cost: $28.0998 · model: claude-opus-4-7