feat(gui): bring Aether-gate's device controls into the app - #5372
Open
crypticpy wants to merge 3 commits into
Open
feat(gui): bring Aether-gate's device controls into the app#5372crypticpy wants to merge 3 commits into
crypticpy wants to merge 3 commits into
Conversation
Aether-gate presents non-Flex hardware to AetherSDR as a FLEX-6600, so everything it can say has to fit a Flex verb. Plenty does not: an RSPdx's antenna port, bias-T, MW/DAB notches, HDR mode and AGC setpoint have no Flex equivalent, and neither does the panadapter's bin count. Reaching them meant leaving the app for the gate's web panel. GATE is a normal applet tile that talks to the gate's control port. The controls are built from what the gate reports the attached device offers, not from a fixed list — an RSP1a, an RSPdx and an RTL stick share almost no settings, and hardcoding one device's is the same mistake as hardcoding its sample rates. Presence is the applet's own discovery: it probes the control port at the radio's address rather than sniffing the serial, which is operator-settable (--serial) and would make a renamed gate vanish from the UI. Because AppletPanel keeps the button out of the bar until presence is confirmed, the probe has to run while hidden — it stops itself once the question is answered either way, and showEvent restarts it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The GATE button never appeared. The applet reaches the gate at the RADIO's address, but ip() is filled in by the reply to "info", which RadioModel only sends after it emits connectionStateChanged(true) — so the probe fired at an empty address. A probe with nowhere to go returns without counting a failure, so the applet stopped its timer and gave up having never actually asked. Watch infoChanged instead, and re-probe on address change rather than on every emission. Clearing the remembered address on disconnect keeps a reconnect honest: the gate may have been restarted, or replaced by a real Flex at the same address. Same ordering trap the TX power-scale wiring hit in aethersdr#4813, documented a hundred lines above where this went in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nciple VIII.
The applet styled its five row labels with a literal
"QLabel { color: #8090a0; font-size: 10px; font-weight: bold; }" pushed through
setStyleSheet(). Two problems, both caught by running the repo's own gate rather
than assuming the file was clean:
* static-checks.yml's hardcoded-colour ratchet is a DELTA against the PR base,
and this applet raised it on two of three counters — total_references +1
(the literal) and setstylesheet +5 (the call sites). It would have failed CI.
* A bare setStyleSheet() resolves once. The colour could not be restyled by a
user theme and did not repaint on a live theme switch.
Route the labels through ThemeManager::applyStyleSheet() with a {{token}}
placeholder instead — the same shape MainWindow already uses. applyStyleSheet
resolves scope-aware against the widget's container chain and re-resolves every
widget it tracks when the theme changes, so this is a behaviour gain, not only
ratchet appeasement. Its own setStyleSheet call lives in ThemeManager.cpp, which
the ratchet allow-lists as the file that DEFINES the token values.
color.text.secondary (#8ea8c0), not color.text.label (#506070): #8090a0 is a
light mid-grey, so the semantically-named label token would have visibly
darkened these against every sibling applet. Preserving the designed appearance
wins over the tidier name here.
Verified with the exact command CI runs, against the merge base:
unique_colours 613 (+0), total_references 2724 (+0), setstylesheet 1090 (+0)
TunerApplet and ProfileSwitcherApplet still hold their own copies of the old
literal. Converging all three is a follow-up — out of scope for a PR that should
only carry the new applet.
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 #5371.
Aether-gate presents non-Flex hardware to AetherSDR as a FLEX-6600, so everything
it can say has to fit a Flex verb. Plenty does not: an RSPdx's antenna port,
bias-T, MW/DAB notches, HDR mode and AGC set-point have no Flex equivalent, and
neither does the panadapter's bin count. Reaching them meant leaving the app for
the gate's web panel — mid-operation.
GATE is a normal applet tile that talks to the gate's control port:
not from a fixed list. An RSP1a, an RSPdx and an RTL stick share almost no
settings, and hardcoding one device's set is the same mistake as hardcoding its
sample rates.
radio's address rather than sniffing the serial, which is operator-settable
(
--serial) and would make a renamed gate vanish from the UI. BecauseAppletPanelkeeps the button out of the bar until presence is confirmed, theprobe has to run while hidden — it stops itself once the question is answered
either way, and
showEventrestarts it.reaches the gate at the radio's address, but
ip()is filled in by thereply to
info, whichRadioModelonly sends after it emitsconnectionStateChanged(true). A probe with nowhere to go returns withoutcounting a failure, so the applet stopped its timer and gave up having never
actually asked. It now watches
infoChangedand re-probes on address change.This is the same ordering trap the TX power-scale wiring hit in Power Meter #4813.
The diff is purely additive (603 insertions, 0 deletions) across one new
applet plus its
AppletPanel/MainWindow_Wiringregistration.Relationship to #4944
#4944 asks the maintainer to settle where an undiscoverable peripheral gets
configured and when its applet button is visible, after #4905 and #4919 landed
with opposite answers. This is a third data point, not an attempt to pre-empt
that decision. It lands on the middle path #4944 itself proposes — hidden until
the device is confirmed present, shown thereafter — and it can do so because a
gate is discoverable: it answers on a known port at the radio's address, which
is precisely what #4944 observes was missing for Everyware. Happy to re-shape
this to whatever convention #4944 settles on.
On RFC scope
Per GOVERNANCE.md this needs no RFC: "new applets or dialogs that don't change
existing UX" is explicitly on the no-RFC list, no existing control, shortcut or
default changes behaviour, and nothing appears in the applet bar for users
without a gate. A waterfall colour-scheme commit that originally sat on this
branch has been deliberately split out and held back, because changing theme
colours is RFC-gated and "do not open a PR until the RFC issue is approved."
Constitution principle honored
Principle VIII — Evidence Over Assertion. The third commit exists because I
ran the repo's own gates locally instead of assuming the applet was clean: the
hardcoded-colour ratchet in
static-checks.ymlis a delta against the PR base,and this applet raised two of three counters (
total_references+1 from a#8090a0literal,setstylesheet+5 from the call sites) — it would have failedCI. Row labels now resolve through
ThemeManager::applyStyleSheet()with a{{color.text.secondary}}token, which also makes them restylable by a usertheme and repaint on a live theme switch, neither of which a bare
setStyleSheet()did. Re-verified with the exact command CI runs, against themerge base:
unique_colours 613 (+0), total_references 2724 (+0), setstylesheet 1090 (+0).Principle II — The Radio Is Authoritative On Live State. The control set is
whatever the gate reports its device offers; the applet asserts no device shape
of its own.
Test plan
cmake --build build) — clean, links exit 0live Aether-gate 0.4.3 fronting an SDRplay RSPdx-R2, exercising antenna
port, bias-T, notches, HDR, AGC set-point and the span/bins resolution
controls. The antenna-port control in particular was used to run an
A-vs-B disconnect measurement (25 dB difference between a connected and an
empty port), so the writes demonstrably reach the hardware
reproduce unchanged on clean
main(bridge_docs_check,hl2_state_restore_test). Repo gates run locally: colour ratchet +0 onall three counters;
check_engine_boundary.py→ 0 findings would block;check_a11y.py→ no findings in the new filewidget construction against a live gate, and there is no existing fixture
for a fake gate endpoint to build on. Flagging it rather than claiming
coverage — happy to add one if a reviewer wants a particular shape
Checklist
docs/COMMIT-SIGNING.md) — GPG, GitHub reportsverified: trueAppSettingscalls — the applet persists nothing; devicestate lives on the gate, which is authoritative for it
the gate's own documented control port (Principle IV)
MeterSmoother— N/A, the applet has no meter; itsreadouts are discrete device settings, not continuous levels
self-describing (every control is built from the gate's own reported
shape, with tooltips), and no existing document describes the applet bar's
contents as a fixed list.
CHANGELOG.mddeliberately untoucheda reviewer's eye regardless: the applet issues HTTP GETs to the radio's
address on a fixed control port, and renders the returned setting shape.
It stores no credentials and sends none
Note on the claim protocol (AGENTS.md §Issue / PR Claim Protocol): assignee
changes are rejected for an account without write access to this repo, so the
Fixes #5371link is the visible claim on the issue timeline instead.