Skip to content

[tuning] Restore Flex band-stack selection keys#1887

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
jensenpat:aether/issue-1876-band-stack-restore
Apr 24, 2026
Merged

[tuning] Restore Flex band-stack selection keys#1887
ten9876 merged 2 commits into
aethersdr:mainfrom
jensenpat:aether/issue-1876-band-stack-restore

Conversation

@jensenpat

@jensenpat jensenpat commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1876.

This restores radio-authoritative Flex band-stack selection from the spectrum band menu. Native band buttons now send the Flex band-stack key the radio expects, transverter bands are mapped through the radio's XVTR setup order, and unsupported bands are refused instead of falling back to a direct slice tune.

The important behavior change is that AetherSDR again lets the radio own band-stack recall for frequency, mode, filters, pan center, bandwidth/zoom, and antenna state. We do not synthesize a local frequency/mode restore and we do not force a client-side recenter after the band switch.

Manual testing also confirmed that built-in Flex RX/TX antenna selections persist across band changes once Aether uses the correct radio-authoritative band= path. This does not add a separate Aether-side antenna memory layer; it restores the radio-owned behavior that was bypassed by the fallback tune path.

Root Cause

The band selector UI emits human-facing labels such as 20m, 40m, 630m, etc. The band-switching handler was checking those values against a native-band pass-through set containing bare Flex keys such as 20, 40, and 630.

That mismatch meant ordinary native bands missed the intended path:

display pan set <panId> band=20

and instead fell into the old unrecognized-band fallback. The fallback directly changed the slice mode and tuned to the static band-grid default, for example 14.225 USB on 20m. That bypassed the radio's band stack, so user state that should have been restored by the radio was replaced by Aether's hard-coded defaults.

This is why users saw symptoms like:

  • returning to 20m at 14.225 USB instead of their last CW frequency
  • returning to 40m at the band edge/default instead of the last used frequency
  • span/zoom and pan position appearing to reset across band changes
  • mode changing back to SSB when the previous per-band state was CW or another mode
  • built-in Flex RX/TX antenna selections appearing not to persist per band when the fallback path bypassed the radio's saved band-stack state

What Changed

  • Normalize native UI labels only when the stripped key is a known Flex native band:
    • 20m -> band=20
    • 630m -> band=630
    • 2200m -> band=2200
  • Preserve the existing WWV/GEN behavior:
    • WWV -> band=33
    • GEN -> band=34
  • Parse the radio's XVTR order status field into RadioModel::XvtrInfo.
  • Map configured transverter display names to Flex XVTR band selectors using X<order>:
    • configured 2m with order=0 -> band=X0
    • configured 70cm with order=1 -> band=X1
  • Remove the unsafe fallback that did slice set mode=... plus direct slice tune ....
  • Restore radio-owned antenna recall for band changes by ensuring Aether uses the Flex band-stack selector instead of bypassing it with direct slice tune commands.
  • If Aether cannot form a spec-correct band= key, log a warning, show a short status-bar message, and leave the slice/panadapter untouched.

Flex API Notes

The public Flex display pan set API exposes band selection as a panadapter property:

display pan set <panId> band=<bandKey>

For native bands, the key is the radio band-stack name, not the UI label with the meter suffix. For transverters, the selector is the radio's XVTR setup slot in X<n> form. The name field is the user's display label; the order field is the value that determines the X<n> selector.

This distinction matters because the XVTR status index is not necessarily the correct selector. This PR uses order, not the xvtr <index> map key.

Behavior Matrix

User selection Flex command behavior Notes
160m display pan set <panId> band=160 Native band stack
80m display pan set <panId> band=80 Native band stack
60m display pan set <panId> band=60 Native band stack
40m display pan set <panId> band=40 Native band stack
30m display pan set <panId> band=30 Native band stack
20m display pan set <panId> band=20 Native band stack
17m display pan set <panId> band=17 Native band stack
15m display pan set <panId> band=15 Native band stack
12m display pan set <panId> band=12 Native band stack
10m display pan set <panId> band=10 Native band stack
6m display pan set <panId> band=6 Native band stack
630m display pan set <panId> band=630 Native band stack
2200m display pan set <panId> band=2200 Native band stack
WWV display pan set <panId> band=33 Existing numeric slot retained
GEN display pan set <panId> band=34 Existing numeric slot retained
XVTR 2m, order=0 display pan set <panId> band=X0 Uses XVTR setup order
XVTR 70cm, order=1 display pan set <panId> band=X1 Uses XVTR setup order
XVTR with no order Refuse; no tune fallback Safer than tuning wrong band
Unknown band label Refuse; no tune fallback Safer than resetting mode/frequency

Issue History

This PR directly fixes the failure proven in #1876: native band changes were not using the radio band stack and instead reset VFO/span state to Aether's defaults.

It also appears to address the same regression pattern reported in:

Related prior history:

Validation

Automated/local checks:

  • git diff --check
  • cmake --build build -j10

Build succeeded. The compiler still reports unrelated pre-existing lambda-capture warnings in MainWindow.cpp.

Manual validation performed:

  • Native band switching restores last-used frequency and zoom/span instead of resetting to hard-coded defaults.
  • No client-side recenter override is applied after band switch; this matches SmartSDR behavior where the radio restores the band stack's pan center.
  • Built-in Flex RX/TX antenna selections persist across band changes when set per band and recalled through the restored display pan set ... band= path.
  • XVTR entries can be configured without physical transverter hardware and selected from the band menu.
  • Test XVTR profile:
    • 2m: RF 144.200, IF 28.200, LO 116.000, RX Only enabled, Max Power 0.0
    • 70cm: RF 432.100, IF 28.100, LO 404.000, RX Only enabled, Max Power 0.0
  • Switching across 2m, 70cm, and native HF bands preserves frequency and zoom/span state.

Suggested Test Plan

Native Band Stack

  1. Start on 20m.
  2. Tune to a non-default frequency, for example 14.360.000.
  3. Set a distinctive mode, for example CW.
  4. Adjust pan position so the VFO is visible but not necessarily centered.
  5. Set a distinctive panadapter bandwidth/zoom.
  6. Switch to 40m.
  7. Tune 40m to a different non-default frequency and use a different zoom/span.
  8. Switch back to 20m.
  9. Verify frequency, mode, pan center, and bandwidth/zoom match the prior 20m state.
  10. Repeat across several native bands, including 630m or 2200m if available.

Two Slice Regression

  1. Create Slice A and Slice B on different bands.
  2. Give each slice a different frequency, mode, pan position, and zoom/span.
  3. Change bands from each slice's panadapter.
  4. Return to the original bands.
  5. Verify each slice/panadapter returns to its own band-stack state and does not inherit the other slice's defaults.

WWV / GEN

  1. Select WWV.
  2. Verify the radio changes via the band stack and does not use direct slice tuning fallback.
  3. Select GEN.
  4. Verify the same.
  5. Confirm there are no status-bar warnings for these selections.

Built-in Flex Antenna Persistence

  1. Start on 20m.
  2. Select a distinctive built-in RX/TX antenna, for example ANT2.
  3. Switch to 40m.
  4. Select a different built-in RX/TX antenna, for example ANT1.
  5. Switch back to 20m.
  6. Verify the radio/Aether restore the prior 20m antenna selection.
  7. Switch back to 40m.
  8. Verify the prior 40m antenna selection is restored.
  9. Repeat with the opposite direction and, where available, with separate RX and TX antenna choices.

XVTR Without Physical Hardware

  1. Open Radio Setup -> XVTR.
  2. Add a 2m XVTR:
    • Name: 2m
    • RF Freq: 144.200
    • IF Freq: 28.200
    • LO Freq: 116.000
    • LO Error: 0
    • RX Gain: 0.0
    • RX Only: enabled
    • Max Power: 0.0
  3. Add a 70cm XVTR:
    • Name: 70cm
    • RF Freq: 432.100
    • IF Freq: 28.100
    • LO Freq: 404.000
    • LO Error: 0
    • RX Gain: 0.0
    • RX Only: enabled
    • Max Power: 0.0
  4. Close Radio Setup and open the band selector.
  5. Select 2m; verify the panadapter tunes to the XVTR band.
  6. Set a distinctive zoom/span and tune within the XVTR band.
  7. Switch to a native HF band, then back to 2m; verify frequency and zoom/span persist.
  8. Repeat with 70cm.

XVTR Order Regression

  1. With two XVTR entries configured, verify the debug log reports XVTR match: "2m" -> "X0" and XVTR match: "70cm" -> "X1" or the corresponding order values reported by the radio.
  2. If the radio UI allows reordering, reorder the XVTR entries.
  3. Reconnect or force status refresh.
  4. Verify Aether follows the radio's updated order value rather than the status object index.

Negative / Safety Behavior

  1. Attempt to select an invalid or unsupported band mapping, if one can be produced from a test build or malformed radio status.
  2. Verify Aether shows a status-bar warning and logs a warning.
  3. Verify Aether does not send a direct slice tune fallback and does not change the slice mode/frequency.

Risk Notes

  • This intentionally removes the direct-tune fallback. That is safer for preserving radio state, but it means a malformed or unsupported band entry now fails visibly instead of trying to guess a frequency/mode.
  • XVTR selection now depends on the radio reporting order. That matches the Flex API behavior we need for X<n> selectors; if a radio/firmware does not provide order, Aether refuses the XVTR selection rather than risking the wrong transverter.
  • No Aether-side recentering is applied after band switches. This is intentional because SmartSDR preserves the band stack's pan center when returning to a band.
  • Built-in Flex antenna persistence still relies on the radio band stack. This PR does not add a separate client-side rxant / txant memory layer in Aether. If a future radio/firmware path does not save antenna changes into the band stack, that would need a separate memory in the selection of Tx Rx antennas according to the band #1886-style follow-up.

Files Changed

  • src/gui/MainWindow.cpp
  • src/models/RadioModel.cpp
  • src/models/RadioModel.h

👨🏼‍💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat

@jensenpat jensenpat changed the title Restore Flex band-stack selection keys [tuning] Restore Flex band-stack selection keys Apr 24, 2026
@jensenpat jensenpat marked this pull request as ready for review April 24, 2026 01:14
@jensenpat jensenpat requested a review from ten9876 as a code owner April 24, 2026 01:14

@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 thorough PR, @jensenpat — the write-up, behavior matrix, and test plan are all excellent. The root cause analysis (UI labels carrying the m suffix while the pass-through set uses bare numbers) is spot-on, and this is clearly the right fix for the long-running band-stack regression.

I read through the diff carefully and have a few observations:

Looks Good

  • Label stripping logic is well-guarded: only strips m when the bare result is a known native band, so XVTR names like "2m" correctly fall through to the XVTR lookup. Clean approach.
  • Removing the direct-tune fallback is the right call. The old fallback silently overwrote radio state and was the source of the regression symptoms reported across many issues.
  • order vs map index for XVTR — using the Flex order field for X<n> selectors matches the API spec and is an important correctness fix over the prior it.key() approach.
  • m_bandSettings.setCurrentBand() moved inside the success branch — good, this avoids updating Aether's band tracking when the command is refused.
  • XvtrInfo::order defaults to -1 — clean sentinel for "radio didn't report this field."

Minor Points (non-blocking)

  1. order parse without validation: In RadioModel.cpp, kvs["order"].toInt() will silently return 0 for non-numeric strings, which is a valid order value. This could cause an XVTR entry to appear valid (order >= 0) when the radio sent garbage. A toInt(&ok) check would be slightly more defensive at this system boundary, though in practice the radio firmware is unlikely to send non-numeric order values.

  2. XVTR name collision: If two XVTR entries have the same name, the break after the first match means we always pick the first one. That's the existing behavior and is fine, but worth noting that the Flex API doesn't enforce unique XVTR names — an edge case that probably doesn't matter in practice.

  3. Status bar message lifetime: The 5-second showMessage timeout for refused bands is reasonable. Just flagging that if other status messages are being posted rapidly (e.g., during reconnect), this could get overwritten before the user sees it. The qWarning log is the durable record, so this is fine.

Relationship to #1880

This PR supersedes the draft #1880 (which only stripped the m suffix without removing the fallback or fixing XVTR selection). If this merges, #1880 can be closed.


Overall this is a clean, well-scoped fix. The changes stay within the stated scope (MainWindow band handler + RadioModel XVTR parsing), no new resource management concerns, no null pointer risks I can see. Nice work — and thank you for the detailed manual testing across native bands, XVTR, and the negative cases.

@ten9876 ten9876 merged commit 41c1699 into aethersdr:main Apr 24, 2026
5 checks passed
@ten9876

ten9876 commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Claude here on behalf of Jeremy — merged via `gh pr merge --squash` as `41c1699` (you preserved as git author on the squashed commit).

Nice writeup on the root cause and fallback-removal reasoning. The XVTR `order`-vs-index distinction and the pcap references made the protocol correctness easy to confirm. Looking forward to hearing how the band stack feels after this lands.

Thanks Pat!

73, Jeremy KK7GWY & Claude (AI dev partner)

@aethersdr-agent aethersdr-agent Bot mentioned this pull request May 4, 2026
ten9876 pushed a commit that referenced this pull request May 9, 2026
…#2511)

Flex's `display pan set band=X<n>` expects the xvtr status-object index
(the number from `xvtr <n>` messages, 0-based), not the radio's 1-based
`order` field.  Using `order` produced a +1 shift: pressing the 144 MHz
button sent `band=X1` instead of `band=X0`, tuning to 432 MHz instead.

Reverts the index→order swap from PR #1887 back to the pcap-verified
`X<index>` form from PR #1615.  Updates the test assertions and renames
the test function to reflect the correct semantics.

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.

The problem doesn't seem to be resolved. Span and VFO freeze when changing bands.

2 participants