Skip to content

feat(applet): SSDR-parity PWR/SWR metering for PGXL and TGXL applets#3277

Merged
jensenpat merged 11 commits into
aethersdr:mainfrom
chibondking:feat/applet-meter-parity
May 31, 2026
Merged

feat(applet): SSDR-parity PWR/SWR metering for PGXL and TGXL applets#3277
jensenpat merged 11 commits into
aethersdr:mainfrom
chibondking:feat/applet-meter-parity

Conversation

@chibondking

@chibondking chibondking commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Consolidates PGXL and TGXL applet metering improvements into one PR. Closes #3276 and the PGXL applet PR.

PGXL (Amplifier applet)

  • External row labels — PWR, SWR, and Id labels left-aligned at 72 px carry live numeric values (PWR 933, SWR 1.2:1, Id 25); internal gauge labels cleared
  • Id bargraph — new drain-current gauge (0–70 A, red > 60 A) with label row matching SSDR layout
  • Bottom info row — temperature, Vdd, and Vac values displayed in the empty space left of the OPERATE button
  • Slow-release fwd-power ballistics{30 ms attack / 800 ms release} on fwd gauge so brief TX bursts stay visible
  • 100 ms label throttle — value labels update at 10 Hz via QTimer; gauge fill animates at full rate
  • S-push parser fixPgxlConnection was silently dropping S-push messages whose KV string began directly with key=value (no prefix word); fixed with a conditional instead of an early return
  • Telemetry field fixestempb, vdd, and compound temp ("34.5/28.1") now handled in both direct-TCP and radio-proxy data paths

TGXL (Tuner applet)

  • External row labels — same 72 px PWR/SWR label pattern as PGXL
  • Slow-release fwd-power ballistics — same {30 ms / 800 ms} profile
  • Label update strategy — synchronous update on each meter event (no polling timer) with an 800 ms single-shot hold so inter-packet noise below 5 W doesn't cause flicker

Shared

  • HGauge::setBallistics() — added as public API so individual gauges can opt into custom attack/release curves

Deferred

  • Id drain-current field name requires pcap analysis to confirm (PGXL protocol not fully documented); field name placeholder in place

Test plan

  • PGXL connected: PWR, SWR, Id bars and labels update during TX; temp/Vdd/Vac show in bottom row
  • PGXL: fwd gauge decays slowly (~800 ms) after TX ends
  • TGXL connected: PWR and SWR labels appear during TX, hold 800 ms after, no flicker at threshold
  • No regression on relay bars, TUNE button, OPERATE/BYPASS/STANDBY cycle, antenna switch

🤖 Generated with Claude Code

chibondking and others added 5 commits May 29, 2026 13:43
Adds the numeric readouts and bargraph missing from the PGXL amplifier
applet compared to SmartSDR's reference layout. Principle I (FlexLib
authority / display parity).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the PGXL applet improvements for the TGXL tuner:
- External "PWR" / "SWR" row labels with live numeric values
  ("PWR 987", "SWR 1.2:1") replacing the centred internal gauge labels
- Values suppressed at idle (< 5 W) to avoid noise-floor clutter
- Label text throttled to 10 Hz via QTimer (kMeterReadoutUpdateMs);
  bargraph fill continues to animate at full frame rate
- Fwd-power gauge uses 30 ms attack / 800 ms release ballistics so
  brief RF bursts remain visible for ~0.8 s after key-up
- HGauge::setBallistics() added (public setter for MeterSmoother
  ballistics, same change present in feat/pgxl-parity-telemetry)

Principle I (display parity with SmartSDR reference).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hold

The per-tick timer was causing PWR/SWR text to blink visibly because
TGXL meter updates arrive at a slower, irregular cadence — values
oscillated around the 5 W gate on nearly every timer firing.

Replace the polling timer with a synchronous call to updateValueLabels()
directly from updateMeters().  Add an 800 ms single-shot hold timer so
the label stays visible after power drops below threshold; inter-packet
noise below 5 W no longer causes flicker.  Principle XI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chibondking chibondking requested a review from a team as a code owner May 29, 2026 19:20
@chibondking chibondking self-assigned this May 29, 2026
@chibondking chibondking added the aetherclaude-eligible Issue approved for AetherClaude automated agent label May 29, 2026
@chibondking

Copy link
Copy Markdown
Collaborator Author

@AetherClaude ok, let's try this. These changes need to be done as one in preparation for a contest, where the tuner and amp controls need to be displaying the proper information during a major contest.

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Acknowledged — contest prep makes the combined PR the right call; doing PGXL and TGXL together avoids a half-instrumented station during the event.

I've confirmed the scope maps to files in the tree:

  • src/gui/AmpApplet.{h,cpp} — PGXL applet (external PWR/SWR/Id labels, Id bargraph, bottom info row, fwd ballistics, 100 ms label throttle)
  • src/gui/TunerApplet.{h,cpp} — TGXL applet (external PWR/SWR labels, fwd ballistics, 800 ms single-shot hold)
  • src/gui/HGauge.h — new setBallistics() public API for per-gauge attack/release
  • src/core/PgxlConnection.{h,cpp} — S-push parser fix (conditional vs early-return), tempb/vdd/compound temp parsing in both direct-TCP and radio-proxy paths
  • src/core/TgxlConnection.{h,cpp} — synchronous label updates + hold timer

A couple of notes I'll carry into the implementation, mostly to flag to you up front so nothing surprises during the contest:

  1. Id field name placeholder — per the "Deferred" item, the drain-current field name from PGXL telemetry isn't pcap-confirmed. I'll wire the gauge against a clearly-named placeholder constant in one spot so swapping it after a pcap capture is a one-line change, and I'll add a brief // TODO(pgxl-pcap) marker on that constant. If you have a recent PGXL pcap on hand, drop it in the issue and I'll use the real key from the start.
  2. 800 ms release on fwd gauge — for SSB voice peaks that's the right ballistic, but for a CW contest with fast keying it can look "sluggish" at high WPM. I'll keep the 30/800 ms profile per the spec (matches SSDR), but mention it here so if a contester complains during the event we know exactly which knob to turn (the setBallistics() call site in AmpApplet / TunerApplet).
  3. No regression on TUNE/OPERATE/BYPASS/STANDBY is on the test plan — I'll explicitly exercise that the new label-throttle QTimer in AmpApplet is stopped on applet hide / amp-disconnect so it doesn't sit firing against a deleted widget.

The aetherclaude-eligible label is already on this issue, so the orchestrator will pick it up on the next webhook cycle and open the consolidated PR. No further action needed on your end.

73, Jeremy KK7GWY & Claude (AI dev partner)


🤖 aethersdr-agent · cost: $2.1643 · model: claude-opus-4-7

@chibondking

Copy link
Copy Markdown
Collaborator Author

@AetherClaude confirmed this is working on Windows, locally. Should be the same on Linux and Mac.
Screenshot 2026-05-29 142639

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Excellent — that's exactly the layout we were targeting. Screenshot confirms all the SSDR-parity pieces are landing correctly:

  • External row labels at 72 px: PWR 0, SWR 1.0:1, Id 0 left-aligned, internal gauge labels cleared ✓
  • Id bargraph sitting under SWR with matching label-row layout ✓
  • Bottom info row showing Temp 29.1, Vdd 56.6, Vac 243 to the left of OPERATE ✓
  • OPERATE active, antenna routing (ANT1 → ANT1) visible, STANDBY/BYPASS/TUNE controls in their expected slots ✓

Qt's widget stack is platform-agnostic for this kind of layout work (QHBoxLayout / QLabel / custom paint in HGauge), and the new setBallistics() API plus the QTimer-based label throttle are all stock Qt — no platform-specific calls — so Linux and macOS should render identically. The only thing that ever varies is font metrics: if the 72 px label column looks tight on macOS (where the default system font is a hair wider), the fix is a one-line bump on the label width constant in AmpApplet.cpp / TunerApplet.cpp. Worth a quick visual check next time you're on the Mac build, but no code change anticipated.

A few things I'd suggest verifying once you can put it under load during the contest:

  1. Sustained TX: the 30 ms attack / 800 ms release on the fwd gauge should give you a clean held-peak look on SSB voice. If CW keying at high WPM ever looks sluggish, the knob is the setBallistics(30, 800) call site — easy to retune live without a rebuild gate.
  2. Id gauge field name: since the drain-current key is still on a placeholder pending pcap, you'll see Id 0 until the real PGXL field is wired. If a pcap from the contest station is easy to grab (even 10 seconds of TX), I can swap the placeholder for the confirmed key and that gauge goes live.
  3. Hide/disconnect lifecycle: the 100 ms label-throttle QTimer should be stopped on applet hide and amp disconnect — worth a quick log check that we're not seeing timer-fire warnings against a torn-down widget when you swap between applets.

Glad this came together in time for the contest prep. Good luck with the operating event!

73, Jeremy KK7GWY & Claude (AI dev partner)


🤖 aethersdr-agent · cost: $0.7608 · model: claude-opus-4-7

@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 @chibondking — nice consolidation, and the PR description does a great job tracing the SSDR-parity reasoning. A few notes:

Things that look good

  • PgxlConnection S-push parser fix (src/core/PgxlConnection.cpp:117-123) is a real bug fix. The original if (lastSpaceBeforeEq < 0) return; silently dropped the bare-KV form. The new branch handles both prefixed (S0|TRANSMIT_A id=39 …) and bare (S0|id=39 …) bodies correctly, and the inner split-on-space + indexOf('=') still handles key=value=with=equals since indexOf returns the first =.
  • HGauge::setBallistics() is a clean addition — sits on the existing MeterSmoother plumbing and doesn't disturb the default ballistics for other gauges.
  • Compound temp = "A/B" parsing in MainWindow.cpp is duplicated across the direct-TCP and radio-proxy paths but that mirrors the existing structure of those two handlers, so I wouldn't refactor it as part of this PR.

Suggestions

  1. m_labelTimer runs at 10 Hz unconditionally in AmpApplet (src/gui/AmpApplet.cpp constructor) — it ticks even when the applet is hidden or the PGXL is disconnected. Cheap, but consider stopping/starting it in showEvent/hideEvent (or just update*()-on-write like TunerApplet does) to keep idle wakeups down. Not blocking.

  2. Two different throttling strategies for nominally the same problem (PGXL = 10 Hz QTimer, TGXL = synchronous + 800 ms single-shot hold). The PR description acknowledges this. Worth a follow-up to converge on one — the synchronous-with-hold pattern in TunerApplet is the simpler of the two and doesn't waste timer ticks at idle.

  3. m_labelClearTimer lambda in TunerApplet is wired up before buildUI() creates m_pwrLabel/m_swrLabel. It's safe in practice because the timer is only started from updateValueLabels() (post-construction), but a one-line comment noting the ordering invariant would help — the next reader will be tempted to move things around.

  4. Id field name is acknowledged as a placeholder in the PR description pending pcap analysis. Worth opening a tracking issue so it doesn't fall off — if the real field name turns out to be e.g. drain_i rather than id, the gauge will appear permanently stuck at zero with no compile-time hint that anything is wrong.

  5. Minor: AmpApplet::setMeff() now silently discards its argument. If it's truly dead, the slot could be removed (and its callsite in MainWindow) rather than left as a no-op; otherwise a one-line comment in the header next to the declaration would explain that it's intentionally a stub for now.

Nothing here blocks — the parser fix alone is worth landing. LGTM with the above as cleanups.


🤖 aethersdr-agent · cost: $8.4931 · model: claude-opus-4-7

…abels

- setPowerScale: add 1K tick and yellowStart to all three scale modes so
  the green/yellow/red zones render correctly (setRange without yellowStart
  collapsed the zone to zero-width, showing only green/red)
- Amplifier mode ticks now match PGXL exactly: 0 500 1K 1.5K 2K
- PWR/SWR row labels bumped from 10px to 11px in both AmpApplet and
  TunerApplet for better readability. Principle I.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chibondking

Copy link
Copy Markdown
Collaborator Author

@AetherClaude updated with feedback from the contest

chibondking and others added 5 commits May 30, 2026 20:17
…ields

id= in the PGXL protocol is the device/port identifier (always 2), not
drain current. Remove both path handlers to stop showing a static "2".

The correct field name for drain current requires pcap analysis of the
direct PGXL TCP protocol — deferred post-contest. Vdd/Vac only arrive
via direct TCP; the radio proxy path does not forward them, so no change
needed there.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
id= is the PGXL drain current field, confirmed working via S-push after
the parser fix in PgxlConnection. The idle reading of ~2 A is correct
at no-TX; it tracks real current during transmit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a small "● DIRECT" (cyan) / "● RADIO" (grey) label in the bottom
info panel so the user can see which telemetry path is active. Direct
TCP gives full telemetry (id, vdd, vac, temp, state); the radio proxy
fallback provides a subset. Indicator updates on connect/disconnect of
the direct PGXL TCP connection. Principle I.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches the TX Controls panel behaviour. HGauge gains clearPeak() to
disable the marker. Both AmpApplet and TunerApplet track the running
peak and restart a 2.5 s single-shot timer on each new high-water mark;
the tick clears automatically when no higher value arrives within the
hold window. Principle I.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the radio proxy path is active, Vdd and Vac are not forwarded by
the radio. setDirectConnected(false) now dims both labels to #505050
and resets their text to —, preventing stale values from misleading
the user. setDrainVoltage/setMainsVoltage are no-ops while not directly
connected so a future field-name match can't silently overwrite them.
Principle I.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

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

Thanks for the PGXL / TGXL meter improvements CJ.

@jensenpat jensenpat merged commit 80fc524 into aethersdr:main May 31, 2026
5 checks passed
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…ethersdr#3277)

## Summary

Consolidates PGXL and TGXL applet metering improvements into one PR.
Closes aethersdr#3276 and the PGXL applet PR.

### PGXL (Amplifier applet)

- **External row labels** — PWR, SWR, and Id labels left-aligned at 72
px carry live numeric values (`PWR 933`, `SWR 1.2:1`, `Id 25`); internal
gauge labels cleared
- **Id bargraph** — new drain-current gauge (0–70 A, red > 60 A) with
label row matching SSDR layout
- **Bottom info row** — temperature, Vdd, and Vac values displayed in
the empty space left of the OPERATE button
- **Slow-release fwd-power ballistics** — `{30 ms attack / 800 ms
release}` on fwd gauge so brief TX bursts stay visible
- **100 ms label throttle** — value labels update at 10 Hz via QTimer;
gauge fill animates at full rate
- **S-push parser fix** — `PgxlConnection` was silently dropping S-push
messages whose KV string began directly with `key=value` (no prefix
word); fixed with a conditional instead of an early return
- **Telemetry field fixes** — `tempb`, `vdd`, and compound `temp`
(`"34.5/28.1"`) now handled in both direct-TCP and radio-proxy data
paths

### TGXL (Tuner applet)

- **External row labels** — same 72 px PWR/SWR label pattern as PGXL
- **Slow-release fwd-power ballistics** — same `{30 ms / 800 ms}`
profile
- **Label update strategy** — synchronous update on each meter event (no
polling timer) with an 800 ms single-shot hold so inter-packet noise
below 5 W doesn't cause flicker

### Shared

- **`HGauge::setBallistics()`** — added as public API so individual
gauges can opt into custom attack/release curves

### Deferred

- Id drain-current field name requires pcap analysis to confirm (PGXL
protocol not fully documented); field name placeholder in place

## Test plan

- [x] PGXL connected: PWR, SWR, Id bars and labels update during TX;
temp/Vdd/Vac show in bottom row
- [x] PGXL: fwd gauge decays slowly (~800 ms) after TX ends
- [x] TGXL connected: PWR and SWR labels appear during TX, hold 800 ms
after, no flicker at threshold
- [x] No regression on relay bars, TUNE button, OPERATE/BYPASS/STANDBY
cycle, antenna switch

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

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

aetherclaude-eligible Issue approved for AetherClaude automated agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants