Skip to content

feat(kpa500): add serial/network applet and Advanced maintenance - #5991

Merged
jensenpat merged 25 commits into
aethersdr:mainfrom
WA8PAM:feat/kpa500-applet
Oct 17, 2026
Merged

jensenpat merged 25 commits into
aethersdr:mainfrom
WA8PAM:feat/kpa500-applet

Conversation

@WA8PAM

@WA8PAM WA8PAM commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Tracking: Refs #4953 (KPA500 portion). #6452 delivers the KAT500 portion and carries the closing reference, so #4953 remains open until both device implementations land.

Adds a complete KPA500 peripheral with local serial or raw-TCP network access, a resizable applet, and Peripherals → Advanced maintenance. The applet displays confirmed amplifier state, keeps stale measurements out of the UI and main power meter, and distinguishes amplifier RF output from the radio's TX flag.

This PR delivers the KPA500 only. The KAT500 implementation is tracked separately in #6452.

Credits

Bob Holzer / @WA8PAM contributed the original KPA500 protocol/parser and command builders, serial connection and polling, first applet, Peripherals setup, main power-meter integration, initial tests, and subsequent fixes to the original review findings. His original commits remain in this branch.

@Ozy311 extended that foundation with the redesigned full/Mini applet, history and debug views, the radio-sourced DRV meter, confirmed power handling, Follow TX band selection, raw-TCP transport, Advanced maintenance, native-dialog crash fix, additional regression coverage, and Windows/Mac hardware validation. This is a continuation of WA8PAM's contribution, with explicit credit to both contributors.

TX Controls and meter separation

Following Jeremy’s direction recorded in #6477, the KPA500 affects the VU/PWR meter source and scale, not the radio-side TX Controls gauge. TX Controls continues to display exciter watts and SWR; RF Power and Tune Pwr values include %. Existing radio-integrated amplifier priority is retained. The dedicated KPA500 applet keeps amplifier readback and its Full-mode radio-sourced DRV row.

Applet and connection behavior

  • KP500 appears after a valid amplifier reply and remains available while that transport connection stays open, including when the amplifier is off. Full model names remain in titles/settings.
  • The power button reads ON when confirmed on and OFF when confirmed off. Standby/Operate, manual band selection, fan minimum, speaker alarm and fault clear follow amplifier readback.
  • Follow TX flag defaults on and follows the TX-assigned slice's transmit band. Disable it to park the amplifier on a manually selected band. It does not key the radio or amplifier.
  • RF ACTIVE derives from fresh amplifier forward-power readback, not AetherSDR's TX flag. The serial protocol does not report the RCA KEY IN/PTT contact. Radio TX separately interlocks power, mode and band changes.
  • Full uses compact 22 px controls, themed meter-readout pills and major/minor tick marks; SWR stays at 1–5 to match the amplifier face. Mini hides the lower drawers and mode controls, leaving power, band/status, power and SWR in a thin strip. Follow TX moves into the header pill. Full and Mini modes share confirmed state. Expanded history plots power, SWR, temperature, voltage and current; the popout resizes without scattering controls and scrolls when expanded content needs more room.
  • DRV, Full only, shows the connected radio's measured forward power on a 0–100 W scale, independently of shared AMP meter selection. It is before cable loss, not a KPA500 input-sensor measurement or the RF-power setting. Missing, disconnected or over-two-second-old FWDPWR clears the bar and shows a dash; a measured zero remains valid. Mini is unchanged.
  • Settings provides identity, reply age, transport details, history explanations, temperature units and an Advanced… shortcut. Debug supports pause/copy/save and opt-in verbose polling output.
  • Confirmed power-off stops normal telemetry polling; a five-second power probe detects power-on. Stale data is invalidated, commands are guarded, and serial/TCP reconnect does not replay writes.

Remote access and aether-serial-bridge

Network mode supports remote KPA500 access through a raw TCP serial bridge. The companion aether-serial-bridge project is coming soon: it runs on the computer connected to the equipment's serial port and makes that port accessible remotely to AetherSDR. Compatible raw-TCP ser2net endpoints also work. Local serial access remains available without the companion project.

The bridge owns serial baud/framing settings. This PR does not implement Telnet/RFC2217 negotiation, Elecraft Remote Server framing, or a split GUI/AetherD peripheral connection. Peripherals labels local serial as DIRECT and TCP as NETWORK.

Advanced maintenance

Peripherals → Elecraft KPA500 → Advanced adds configuration backup/restore, stored fault history with CSV export, and power-on banner read/edit. Device information also appears in applet Settings.

Maintenance is gated to the observed firmware 01.54. Restore and banner writes require explicit confirmation, identity checks, fresh Standby/zero-RF readback, bounded transfers and matching readback before success. Polling and ordinary controls pause during maintenance; writes are not retried or resumed after reconnect. Cancellation, TX, malformed replies and transport failure abort the operation and report possible partial completion.

The native file chooser is asynchronous, parent-owned and limited to one open chooser. This fixes the reproduced Windows COM/Qt crash caused by duplicate blocking file dialogs.

Screenshots

Full below shows the current DRV layout using explicitly replayed telemetry, not a hardware power measurement. Mini, History and Advanced retain earlier native Mac captures from the real KPA500 with zero RF; station identifiers are redacted. The History capture predates the added DRV row and remains a screenshot-refresh follow-up when the bridge is available. The images are included in the amplifier user guide.

Full applet

KPA500 Full with DRV; replayed illustrative values

Mini applet

KPA500 Mini applet

Expanded history

KPA500 telemetry history

Peripherals and Advanced

KPA500 NETWORK connection and Advanced maintenance

Current radio-side TX Controls, captured on the Mac while disconnected with no RF:

Radio TX Controls with ATU/MEM, radio scales and percentage power labels

Constitution principles honored

  • II — Radio-authoritative live state: confirmed amplifier readback drives status, controls and power-meter selection; missing/stale values remain unknown.
  • IV — Clean-room contributions: standard commands use the public programmer's reference. Maintenance framing and file format were independently implemented from observed Utility I/O using an isolated virtual COM pair and artificial device; no proprietary code was disassembled or copied. The design note records the observations and firmware boundary.
  • V — Configuration ownership: connection/UI preferences use the existing nested Peripherals/Kpa500 settings; live hardware identity is not persisted as authoritative state.
  • VI — Transmit on intent / VII — Boundary validation: no automatic keying; bounded frame/file parsing, explicit maintenance-write intent and fail-closed guards.
  • VIII — Evidence over assertion / XI — Fixes are demonstrated: tests cover transport, stale-state, maintenance and UI behavior; the native-dialog regression failed before the fix and passed afterward.

Validation

Reviewed and rebased onto main 7864ef544; current head is 874cf214e19aae344682b57996f8a77a76d1e2b1.
The rebase preserves the original contributor commits. Its generated-index conflict was resolved by regeneration, and the rebased tree matched a clean merge result before the landing fix.

The landing review found and fixed an additional partial-telemetry failure: expired ^WS measurements could leave the shared meters suppressing radio readings while other amplifier replies remained live. Shared meter routing, suppression and scale now use the same current power-on/Operate/forward-power predicate. Fresh zero watts remains valid.

  • Local macOS / Qt 6.12: all 11/11 selected headless CTests pass: six KPA500 suites, MeterModel, TX Controls power reconciliation, both HGauge suites and the Peripherals automatic-connection case. The connection target compiles without HAVE_SERIALPORT and uses only in-memory transport injection.
  • Mutation: removing the forward-power freshness guard causes the two new stale-selection assertions to fail; restoring the guard passes.
  • Static/docs: base/head engine findings are identical after ignoring source-line shifts (102 tracked warnings, zero blockers); capability and command-plane counts do not grow. Test registration, frozen CI gate, generated touchpoints/references, colour ratchet and focused accessibility checks pass. Log rules pass 707 checks; their existing moved-line advisory remains unrelated. npm ci and the user-doc production build pass.
  • Demo bridge: the clean 874cf214e native bundle passed 10 assertions in an offscreen, isolated DEMO-0001 session: unknown amplifier readings, no TX-band target, Network setup, and disabled backup/restore/banner writes without hardware. TX/MOX/Tune stayed off; the owned instance was disconnected and closed. External-amplifier telemetry remains socket-free injection/source-review evidence, not demo or hardware proof.
  • Current-head CI: Linux, Windows and macOS, Static checks and sanitizer configuration, and Docs all passed. These runs were created at 2026-10-17T15:16:10Z after current main 7864ef5; post-merge main verification remains separate.

Earlier contributor hardware evidence covers firmware 01.54 identity/telemetry, configuration backup, banner reads and stored faults on Windows serial and Mac raw TCP. It is separate from this landing pass. No hardware was contacted or changed in this review.

Not validated on this final revision: physical restore/banner-write persistence, higher-power RF accuracy, or end-to-end transmitted-power behavior. No transmission, Tune, MOX/PTT, restore or banner writes were performed for this pass. The demo backend cannot validate external amplifier firmware.

Review scope

KPA500 protocol/transport, its applet and Advanced panel, Peripherals setup, meter integration, source registration, tests and user documentation are included. KAT500 remains separate in #6452. Unrelated WSJT-X/a11y edits and the shared ACOM/SPE Standby recolouring from earlier revisions are absent. Shared clear-fault styling is factored without changing ACOM behavior. No CHANGELOG entry, dependency, workflow or gate weakening is included.

The radio-only TX Controls direction and percentage labels follow #6477. Existing radio-integrated amplifier priority remains first. The operator requested the review, rebase, blocker repair and guarded merge of this proposed feature.

Generated with OpenAI Codex (GPT-6 Astra)

@WA8PAM
WA8PAM requested review from a team as code owners September 27, 2026 23:56
@aethersdr-agent

Copy link
Copy Markdown
Contributor

Thanks for this — a complete serial-amp applet with protocol, transport, UI, and live-hardware verification is a lot of work, and the Principle II discipline (no button latches its own state) is exactly right.

Only one job is red, and it's Static checks, not a compile error: run 36360337692. build, check-macos and check-windows were still in progress when I looked. Two steps failed, both mechanical gates on new files rather than anything wrong with the KPA500 logic. I reproduced both locally on 953b83d and confirmed fixes for each.


1. aetherd touchpoint manifest

::error::aetherd touchpoints require a valid semantic tag: core/Kpa500Connection.h, core/Kpa500Protocol.h

src/gui/Kpa500Applet.cpp includes the two new src/core/ headers, which makes them aetherd touchpoints — UI→engine include edges tracked for the headless-engine migration. Every touchpoint needs a semantic tag, and tools/gen_touchpoint_manifest.py won't invent one.

Fix: add both to docs/architecture/aetherd-touchpoint-tags.json, then regenerate the manifest. The right tag is peripheral(elecraft) — same class as core/AcomConnection.h (peripheral(acom)), core/PgxlConnection.h and core/TgxlConnection.h: a standalone accessory transport, not radio-family wire, not behind the IRadioBackend seam.

 "core/Kpa500Connection.h": {
  "tag": "peripheral(elecraft)",
  "note": "Direct RS-232/USB-serial client for the Elecraft KPA500 amplifier — a standalone accessory with no FlexRadio awareness, same precedent as core/AcomConnection.h. Not radio-family wire; NOT behind the IRadioBackend radio seam.",
  "split": "",
  "confidence": "high"
 },
 "core/Kpa500Protocol.h": {
  "tag": "peripheral(elecraft)",
  "note": "Frame parser, status snapshot and command builders for the KPA500 '^CMD;' text protocol.",
  "split": "",
  "confidence": "high"
 },

Then:

python tools/gen_touchpoint_manifest.py     # regenerates docs/architecture/aetherd-touchpoints.md
python tools/gen_touchpoint_manifest.py --check

Commit both the tags JSON and the regenerated .md — the table is generated, so don't hand-edit it. Keep the JSON keys in their existing sort position; with those two entries added I got manifest up to date and the step green. Valid tags are universal, ui-support, vendor(x), mixed(x), peripheral(x) (TAG_RE, tools/gen_touchpoint_manifest.py:51).

2. Hardcoded-colour ratchet

The ratchet (tools/audit_colours.py, #4569) compares against the PR's merge base and fails on any rise. On this commit:

OVER unique_colours       607  (base 604, +3)
OVER total_references    2628  (base 2611, +17)
OVER setstylesheet        954  (base 944, +10)

Colours introduced by this PR (4):
  #1a2030  src/gui/Kpa500Applet.cpp:157
  #1e2530  src/gui/Kpa500Applet.cpp:160
  #2a3545  src/gui/Kpa500Applet.cpp:157
  #5a3a0a  src/gui/AmpAppletStyles.h:34

Three separate things to fix:

a. setstylesheet +10 — Kpa500Applet.cpp has exactly 10 raw widget->setStyleSheet(...) calls (lines 40, 66, 96, 99, 102, 105, 108, 144, 176, 329). The counter matches \bsetStyleSheet\s*\(, so ThemeManager::instance().applyStyleSheet(w, ...) is not counted — and it's also the pattern that makes {{color.*}} re-resolve on theme change. AcomApplet.cpp routes all nine of its styles that way (AcomApplet.cpp:190 onward); your fan combo at line 156 already does. Converting the other ten took the delta to +0.

b. #5a3a0a in AmpAppletStyles.h — the checklist says the standby colour came from the color.accessory.key.standby.* tokens, but it landed as the literal hex. Since these strings are applyStyleSheet templates, use the tokens directly:

case AmpPillState::Standby:
    return QStringLiteral(
        "QLabel { background: {{color.accessory.key.standby.background}}; "
        "color: {{color.accessory.key.standby.foreground}}; "
        "border: 1px solid {{color.accessory.key.standby.foreground}}; "
        "border-radius: 3px; font-size: 9px; font-weight: bold; padding: 2px 6px; }");

c. #1a2030 / #2a3545 / #1e2530 and the repeated #c8d8e8 / #ff8080 — these have no exact token, but the nearby ones do: color.background.1, color.background.2, color.text.primary, color.text.secondary, color.accent.error. Note SpeApplet.cpp:147 and VkampApplet.cpp:180 already write their kTelStyle as "QLabel { color: {{color.text.primary}}; font-size: 10px; }" — Kpa500Applet.cpp:92 is the only copy still on the literal.

One more: your CLEAR button style at lines 115–118 is a verbatim copy of AcomApplet.cpp:218–221, and total_references counts every occurrence, so the copy alone costs 6. AmpAppletStyles.h exists for exactly this ("These were per-applet copies until the colour ratchet made the duplication visible") — hoisting it into an ampClearFaultBtnStyle() there and calling it from both applets removes a reference rather than adding one.

With (a), (b), (c) and the hoist applied locally I get:

OK   unique_colours       602  (base 604, -2)
OK   total_references    2609  (base 2611, -2)
OK   setstylesheet        944  (base 944, +0)

You can run the whole gate before pushing:

BASE=$(git merge-base HEAD origin/main)
git worktree add /tmp/colour-base "$BASE"
python tools/audit_colours.py --src src --compare-src /tmp/colour-base/src --summary-only --strict

Two notes unrelated to the red X:

  • "Commits are signed" is unchecked — worth confirming against docs/COMMIT-SIGNING.md before review.
  • The updatePowerScale reasoning for maxW = 500 with hasAmplifier left false reads correctly to me, and build(deps): Bump actions/checkout from 4 to 6 #3.2's exciter gate matching the PGXL precedent is the right call.

No Copilot review comments were present on the commit, so nothing there to fold in. Neither failure touches the protocol or the applet behaviour you verified on the live amp — it's tags-and-tokens bookkeeping, and both steps should go green in one follow-up commit. Happy to re-check once you push.


🤖 aethersdr-agent · cost: $5.2322 · model: claude-opus-5

@WA8PAM

WA8PAM commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

CI fix (amend): Added peripheral(kpa500) semantic tags to aetherd-touchpoint-tags.json for both new headers; added setAccessibleDescription/setStatusTip to pre-existing disabled controls in RadioSetupDialog.cpp and MainWindow.cpp that the a11y ratchet flagged when those files appeared in the diff.

@WA8PAM

WA8PAM commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Fixed both static-check failures in the follow-up amend (6c7f825):

aetherd-touchpoint-tags.json: tag changed to peripheral(elecraft), manifest regenerated
Colour ratchet: 10 setStyleSheet → applyStyleSheet, Standby pill using {{color.accessory.key.standby.*}} tokens, fan combo on theme tokens, fault label on {{color.accent.error}}, CLEAR style hoisted to ampClearFaultBtnStyle() in AmpAppletStyles.h (AcomApplet updated to match). Ratchet now -4 unique / -18 refs / -10 setStyleSheet vs base.

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

1. Issue fit

Partially. The KPA500 commit is a faithful third instance of the ACOM/SPE peripheral pattern the #4953 triage prescribed, and it honours the two hard rulings in that triage: no shared ElecraftStationController, and no band-push to the amp (display + explicit command only). Protocol, transport, applet, settings row, wiring, touchpoint tags and a socket-free codec test are all present and land on the named files. #4953 correctly stays open for the KAT500 half.

Two requirements from the triage are not met. It asked for docs/architecture/elecraft-kpa500-kat500-design.md; no design note is added, yet four places in this PR cite a docs/architecture/kpa500-amplifier-design.md that does not exist in the tree (nit 1). More seriously, the triage's safety-labelled ruling — "if the link drops mid-QSO, the applet must show stale/unknown, not the last good SWR" — is only half implemented (blocker 3).

And four of the five commits on this branch are not this feature at all.

2. Scope

File / group What it changes Claimed by title/issue? Verdict
src/core/Kpa500{Protocol,Connection}.{h,cpp} New codec + serial transport Yes (#4953) In scope
src/gui/Kpa500Applet.{h,cpp}, AppletPanel.{h,cpp} New applet + registration Yes In scope
RadioSetupDialog KPA500 row, MainWindow_Wiring KPA500 block, MainWindow.h, CMakeLists.txt Settings + wiring Yes In scope
tests/kpa500_protocol_test.cpp + tests.cmake Codec test, registered Yes In scope
docs/architecture/aetherd-touchpoint-* peripheral(elecraft) tags Yes In scope (cites a missing doc — nit 1)
Fan minimum-speed control (^FC, applet combo) New user-facing control In the #4953 thread (your own comment, 2026-09-16), not the triage change list Name it in the PR body
AmpAppletStyles.h Standby pill → theme token Re-colours the existing ACOM/SPE STANDBY pill blue→amber No Needs maintainer decision (§6)
src/core/Wsjtx{Client,StatusFreq,WireHelpers}, SpectrumWidget, MainWindow_Spots, DxClusterDialog, MainWindow.cpp listener, tests/wsjtx_status_freq_test.cpp WSJT-X Rx/Tx frequency overlay (#4526) — ~800 lines, 4 commits, a second new test target No Blocker — unbundle
MainWindow.cpp updateToolsMenuState setStatusTip ×4; RadioSetupDialog setAccessibleDescription ×5 a11y/status-tip improvements on unrelated controls No Out of scope (good changes, own PR)

git log on the head checkout: 706a422a, e2a0e281, 795a5fee, 6f4abb8d are all #4526 WSJT-X overlay work; only 6c7f825f is the KPA500. Two different feature reviews are stacked in one PR.

3. Blockers

  1. Compile break in the build configuration where Qt6 SerialPort is absent. Kpa500Connection.cpp:147 dereferences m_serial outside any guard, but the member is declared only under #ifdef HAVE_SERIALPORT (Kpa500Connection.h). CMakeLists.txt:328-336 finds SerialPort with find_package(... QUIET ...) unless REQUIRE_SERIALPORT=ON, and HAVE_SERIALPORT is defined only if(Qt6SerialPort_FOUND) (:2351), while Kpa500Connection.cpp sits unconditionally in CORE_SOURCES. The file's own sendRaw() #else Q_UNUSED(bytes) arm shows the no-serialport build is meant to compile. CI is green here only because its runners have Qt6::SerialPort — that green does not cover this. Fix in the inline suggestion. (Reasoned from the source and the CMake conditional; I did not build it.)

  2. The WSJT-X Rx/Tx overlay (#4526) is bundled into a KPA500 PR. Four commits, a new public signal shape on WsjtxClient::statusReceived, three new headers, a new registered test target, and rendering changes in SpectrumWidget. Nothing in the title, in #4953, or in the KPA500 commit explains any of it, and it carries its own RFC-review history that deserves its own thread. Please split it out — I've deliberately not reviewed it on its merits here beyond noting it exists.

  3. The amp going silent leaves both the applet readouts and the station TX meters stale — the exact case #4953's triage flagged under safety. Kpa500Connection already models this correctly: respondingChanged() exists precisely because "a USB-serial link can outlive the amp being switched off". Neither consumer uses it for anything but a pill caption:

    • Kpa500Applet::setResponding() (Kpa500Applet.cpp:312-316) repaints the status pill only. PWR/SWR/TMP/V/I keep their last-good text, so the panel reads NO RESPONSE next to a confident SWR 1.2:1 — the literal thing the triage said must not happen. setConnected(false) clears everything; setResponding(false) should clear the same live readouts.
    • MainWindow_Wiring.cpp:6363 and :6387 suppress the radio's own txMetersChanged / directionalPowerMetersChanged whenever m_kpa500Conn.isConnected() && lastStatus().operate, with no responding term, and :7179 pins the gauge to 500 W on the same condition. Switch the KPA500 off at its front panel while the USB-serial adapter stays enumerated and isConnected() stays true, lastStatus().operate stays true forever (m_status is only reset in onTransportUp()), and no replacement telemetry ever arrives — so every subsequent barefoot transmission shows a frozen or dead forward-power/SWR meter, on a 500 W scale, with no indication why. Gating these on responding needs a public accessor; Kpa500Connection currently exposes only the signal.

4. Nits (non-blocking)

  • Missing design note. docs/architecture/kpa500-amplifier-design.md is cited in aetherd-touchpoint-tags.json, aetherd-touchpoints.md, Kpa500Applet.h:18 and AppletPanel.cpp:877, and does not exist. ACOM, SPE and the LP-100A each have one; the triage named elecraft-kpa500-kat500-design.md.
  • static int s_fastIndex (Kpa500Connection.cpp:204) is process-wide, shared by every instance, and survives disconnect/reconnect — while m_slowIndex next to it is a member and is reset in onTransportUp(). Make it a member for consistency.
  • buildPowerOff() has no caller outside the test (^ON0;). #4953 lists "Power ON/OFF" as a requirement; either wire it to a control or say in the body that power-off is deferred (power-on genuinely can't be done — it's a boot-loader command, as your header notes).
  • Test gaps. buildSetFanSpeed() is the only builder with logic (qBound(0, n, 6)) and has no test; neither kMaxFrameBytes cap is exercised; and the applyMessage("FL", "B0", s) case at the end of the fault block runs with no report() at all, so it asserts nothing. All cheap to add in the existing socket-free target.
  • FrameParser::feed() (Kpa500Protocol.cpp:27-29) clears the buffer before appending when m_buf.size() + bytes.size() > 256, which discards a buffered partial-frame prefix that the incoming chunk would have completed. Unlikely at 4800 baud with 200 ms polls, but clearing after append — or only clearing the un-framed tail — avoids it entirely.
  • Contradictory comments at RadioSetupDialog.cpp:9563: "it is not user-configurable here because the spec documents no alternative rate negotiation" sits directly above a four-rate baud combo whose own comment says the KPA500 supports 4800/9600/19200/38400 via ^BRP.
  • isalpha() is used in Kpa500Protocol.cpp without including <cctype> (currently transitive) and is locale-sensitive; an explicit c >= 'A' && c <= 'Z' range test would be both.
  • I could not verify the ^WS/^VI/^FC/^FL scalings against the KPA500 Programmer's Reference Rev. A2 — I have no copy. They are internally consistent between header comment, code and test, and the commentary cites the section for each, which is the right shape under Principle I. Confirming them against the PDF is a maintainer/hardware check.

5. Socket tests

tests/kpa500_protocol_test.cpp is socket-free — it drives FrameParser/applyMessage over hand-built QByteArrays with no transport at all — and is registered in tests.cmake linking only Qt6::Core. Correct layer for wire parsing per AGENTS.md, and no fake KPA500 peer anywhere, which is right. tests/wsjtx_status_freq_test.cpp (from the bundled #4526 commits) is likewise socket-free; its only mention of QUdpSocket is a comment explaining what it avoids. Nothing here binds, listens or connects. Neither is in the per-PR ctest -R gate.

6. What I tried to break, and could not

  • Principle VI / TX safety. Nothing in this diff keys a transmitter. setOperate() fires only from the two applet buttons, there is no auto-OPERATE on connect or reconnect, and the poll loop is pure GET traffic. The Constitution II claim in the header holds: no control method latches — setOperate() sends ^OS1;/^OS0; and the pill only moves when the ^OS reply lands.
  • Lifetime and teardown. All RadioSetupDialog connections to the longer-lived m_kpa500Conn pass this as receiver context, so they die with the dialog; the reseed lambdas hold QPointers. onTransportDown() calls m_serial.disconnect(this) before close(), so the error-then-close path can't re-enter. m_reconnectTimer is stopped on deliberate disconnect and armReconnect() re-checks both flags. I found no dangling-pointer or double-fire path.
  • Reconnect and repeat-click. connectSerial() no-ops while connected; onTransportDown() returns early when already down; clicking Connect twice, or Disconnect during a pending reconnect, both resolve cleanly.
  • Parser hostile input. Garbage before ^, a bare ; echo, a frame split across feeds, ^;, a >3-letter token, a body of only digits — all either resync or are dropped without a callback. The 64/256-byte caps do bound growth. The one hole is the pre-append clear noted above.
  • toLatin1().constData() in the poll ticks — I checked for a dangling temporary; the QByteArray lives to the end of the full expression, so the pointer is valid for the buildQuery() call.
  • Applet signal loops. m_fanCombo's poll-driven setCurrentIndex() is wrapped in a QSignalBlocker, and the currentIndexChanged connection is made after the items are added, so neither construction nor a poll reply can echo a ^FC back to the amp.
  • Accessibility and the colour ratchet. tools/check_a11y.py src/gui/Kpa500Applet.cpp → 0 findings. tools/audit_colours.py --strict --compare-src <base> → all three counts down or flat (unique −1, refs −1, setStyleSheet ±0), so the new hardcoded hexes in updateStatusPill() do not trip the gate.
  • CodeGuard's five CG-PATH-001 hits — all refuted. MainWindow.cpp:9551 is a QLabel string in the FFTW-wisdom dialog, :9663 a progress-label update, :9782-9784 the macOS bundle-relaunch walk up from QCoreApplication::applicationFilePath(). None takes external input and none is touched by this PR.
  • Could not check: the PR body (I have no read path to it from here, so I could not test its claims or its checklist), any on-hardware behaviour, and the Elecraft PDF. Every finding above is reasoned from the head checkout at 6c7f825f, not reproduced at runtime.

7. Recommendation

Request changes, and the first request is structural: split the four #4526 WSJT-X commits into their own PR so each feature gets a real review. On the KPA500 half, the architecture is right and the pattern-matching to ACOM/SPE is genuinely well done — blocker 1 is a one-line guard, and blocker 3 is a small accessor plus wiring the respondingChanged signal you already built through to the readouts and the meter gates. With those two and the design note, this is close. Given #4953 is labelled safety and maintainer-review, and that the triage explicitly wanted a hardware cross-check before an amplifier control path lands, the merge call and the bench validation both sit with the maintainer.

Thanks for taking this on — the protocol layer in particular is carefully sourced and the poll-silence model is more thoughtful than either sibling integration's.

One more thing: commit signing

main requires verified signatures, and 4 of the 5 commits on this branch are unsigned (only 6c7f825f, the KPA500 commit, is signed). Quickest setup is SSH key signing, no GPG needed:

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true

(If you have no SSH key: ssh-keygen -t ed25519 first.) Then on GitHub: Settings → SSH and GPG keys → New SSH key → set the key type dropdown to Signing Key → paste the .pub.

To re-sign what's already on the branch:

git rebase main --exec "git commit --amend --no-edit -n -S"
git push --force-with-lease

If you unbundle the WSJT-X commits first, that rebase covers whatever is left here. Full reference, including GPG and troubleshooting: https://docs.github.com/authentication/managing-commit-signature-verification


🤖 aethersdr-agent · cost: $8.7820 · model: claude-opus-5

Comment thread src/core/Kpa500Connection.cpp Outdated
Comment thread src/gui/MainWindow_Wiring.cpp Outdated
Comment thread src/gui/Kpa500Applet.cpp
Comment thread src/gui/AmpAppletStyles.h Outdated
Comment thread src/core/Kpa500Connection.cpp Outdated
Comment thread src/core/Kpa500Protocol.cpp Outdated
Comment thread src/gui/Kpa500Applet.h Outdated
Comment thread src/gui/RadioSetupDialog.cpp Outdated
@aethersdr-agent

Copy link
Copy Markdown
Contributor

Cisco CodeGuard — static analysis of this PR (5 finding(s))

  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-5991/src/gui/MainWindow.cpp:9551
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-5991/src/gui/MainWindow.cpp:9663
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-5991/src/gui/MainWindow.cpp:9782
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-5991/src/gui/MainWindow.cpp:9783
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-5991/src/gui/MainWindow.cpp:9784

Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them.


🤖 aethersdr-agent · cost: $8.9670 · model: claude-opus-5

…yle token

Revert AmpAppletStyles.h Standby pill to blue hex (ACOM/SPE unaffected).
Add ampStandbyBtnStyle() using {{color.accessory.key.standby.*}} amber
tokens, matching AmpApplet's kPanelKeyStandbyStyle. KPA500 standby button
now uses it instead of hardcoded #12222e/#2a5a70.
…LPORT

Fixes compile break when Qt6 SerialPort is absent. m_serial is declared
only under #ifdef HAVE_SERIALPORT but the guard clause read it
unconditionally. No-serialport build now falls back to checking
m_connected only, matching the intent of the sendRaw() #else arm.
…aethersdr#4953)

Add isResponding() accessor to Kpa500Connection (beside isConnected()).

setResponding(false) in Kpa500Applet now clears PWR/SWR/TMP/V/I labels
and gauge immediately, matching setConnected(false). Band, fault banner
and firmware tooltip are kept — they describe conditions, not live values.
Prevents 'NO RESPONSE' pill next to a confident SWR 1.2:1 (aethersdr#4953 triage).

MainWindow_Wiring: gate all three KPA500 meter-suppression paths on
isResponding() as well as isConnected(). Without this, switching the amp
off at the front panel while USB-serial stays enumerated leaves the radio's
TX meters frozen on the 500W scale indefinitely. Also connect
respondingChanged to updatePowerScale so the scale restores immediately.
… comment)

- s_fastIndex: promote to member m_fastIndex beside m_slowIndex; reset
  in onTransportUp() so reconnect restarts the rotation and multiple
  instances do not share state.
- Kpa500Protocol.cpp: add <cctype> include for isalpha(); append before
  overflow-guard clear in FrameParser::feed() so a chunk that would
  complete a buffered partial frame is not discarded first.
- RadioSetupDialog.cpp: remove contradictory 'not user-configurable'
  sentence that sat directly above the four-rate baud combo.
Tests: add buildSetFanSpeed bounds (clamp to 0/6), kMaxFrameBytes overflow
recovery (buffer cleared, next valid frame still decodes), and a proper
report() for the ^FL 'B0' hex-notation case (toInt decimal fails →
field unchanged).

Design doc: add docs/architecture/elecraft-kpa500-kat500-design.md
covering peripheral rationale, protocol summary, command scope, TX safety,
KAT500 deferral, touchpoint tagging, and resolved decisions (stale-readout
safety, standby pill/button, no band push, no shared controller).

Citation rename: four source files and two doc files referenced the
wrong filename (kpa500-amplifier-design.md); updated to the correct
elecraft-kpa500-kat500-design.md.
The header comment incorrectly described ^ON1 as a bootloader-only
command. Both ^ON0 and ^ON1 work over the normal serial port, confirmed
by KPA500 Remote and third-party applications (PGTG). Add buildPowerOn()
alongside buildPowerOff(); both tested. No UI wired for either in this
PR — power-off needs a confirmation dialog, power-on needs a hardware
round-trip test. Design doc updated to match.
@WA8PAM

WA8PAM commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. I've pushed a series of follow-up commits addressing everything flagged. Summary:

Blockers

Split: The four #4526 WSJT-X overlay commits have been removed from this branch via git rebase --onto. The branch now contains only the KPA500 work. The overlay commits live on wsjtx-freq-overlay and will come through as a separate PR once RFC #5586 gets maintainer sign-off.

Compile break (no SerialPort): onTransportDown() now wraps the m_serial.isOpen() check under #ifdef HAVE_SERIALPORT exactly as suggested, with if (!m_connected) return; in the #else arm.

Safety (stale readouts + frozen meters): Added bool isResponding() const to Kpa500Connection alongside isConnected(). setResponding(false) in Kpa500Applet now clears PWR/SWR/TMP/V/I labels and gauge immediately — band, fault banner, and firmware tooltip are kept. All three meter-suppression gates in MainWindow_Wiring now check isResponding() as well as isConnected(), and respondingChanged is connected to updatePowerScale so the 500 W scale restores as soon as the amp goes silent.

Style alignment (AmpAppletStyles.h)

Reverted the Standby pill to the original blue hex — ACOM and SPE pills are unchanged. Added ampStandbyBtnStyle() using {{color.accessory.key.standby.*}} amber tokens, matching AmpApplet's own kPanelKeyStandbyStyle. The KPA500 standby button now uses it; the pill stays in the family blue.

Nits

s_fastIndex → member m_fastIndex, reset in onTransportUp()
added to Kpa500Protocol.cpp for isalpha()
FrameParser::feed() now appends before the overflow-guard clear
Contradictory baud-rate comment in RadioSetupDialog.cpp removed
Tests added: buildSetFanSpeed bounds (clamp to 0/6), kMaxFrameBytes overflow recovery, and a proper report() for the ^FL "B0" case (toInt decimal fails → field unchanged, confirmed)

Design doc

Added docs/architecture/elecraft-kpa500-kat500-design.md covering peripheral rationale, protocol summary, command scope, TX safety, KAT500 deferral, touchpoint tagging, and resolved decisions. All four dangling citations to the wrong filename are fixed.

Power on/off correction

The header comment incorrectly described ^ON1 as a bootloader-only command. Both ^ON0 and ^ON1 work over the normal serial port (confirmed by KPA500 Remote and at least one other third-party application). Added buildPowerOn() alongside buildPowerOff(), both tested. Neither is wired to a UI control in this PR — power-off needs a confirmation dialog, power-on needs a hardware round-trip test. Both are documented as deferred in the design doc.

Build + test

Full build clean. ctest -R kpa500_protocol_test passes (all cases including the new ones). Verified on hardware: standby button renders amber in standby, readouts clear correctly when the amp is switched off at the front panel, TX meters restore immediately.

The remaining hardware cross-check of ^WS/^VI/^FC/^FL scalings against Rev. A2 is a maintainer/owner step as noted — I don't have a copy of the PDF.

@ten9876 ten9876 self-assigned this Oct 4, 2026

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

Reviewed head da95a70e1fec08ce10c67ebceff8b6b583df8f3d.

Issue fit: Partially addresses #4953: KPA500 telemetry, explicit standby/operate, faults and fan control are delivered; KAT500, power controls and band pushing are deferred as the design note says. The standalone-peripheral shape follows the issue discussion. The stale-readout remediation works at the instant silence is declared, but production state injection shows that stale measurements become current again without fresh measurement replies. The parser also fails two claimed recovery properties.

Scope

File/group Change Claimed? Verdict
Kpa500Protocol.{h,cpp}, kpa500_protocol_test.cpp, test registration Codec, commands, polls and socket-free checks Yes In scope; defects below
Kpa500Connection.{h,cpp} Serial lifecycle, polling and silence tracking Yes In scope; stale-state defect
Kpa500Applet.{h,cpp} Dedicated controls and telemetry Yes In scope; owner must approve final feature UI
AppletPanel.{h,cpp}, KPA parts of MainWindow.{h,cpp}, MainWindow_Wiring.cpp Register/connect applet, meter/scale integration Yes In scope
KPA parts of RadioSetupDialog.{h,cpp} Port/baud settings and connection Yes In scope
AmpAppletStyles.h, AcomApplet.cpp Shared clear-fault style and KPA standby-button style Follow-up comment explains Related deduplication; ACOM style preserved
CMakeLists.txt, architecture tags/manifest/design note Build and peripheral documentation Yes In scope
Other MainWindow/RadioSetup a11y edits SWR/ATU/AGC menu status tips and automation/network control descriptions No Unrelated; unbundle

Earlier WSJT-X overlay changes are no longer in this diff. The body's claim that the shared Standby pill turns orange is stale: the follow-up reverted that; this head changes the KPA button and leaves the family pill blue. No CHANGELOG change or baseline weakening found.

Blockers

  1. Do not resurrect stale amplifier telemetry on an unrelated reply — src/core/Kpa500Connection.cpp:27–34,245–251; consumer src/gui/MainWindow_Wiring.cpp:6834–6839. Silence changes m_responding but retains all m_status values. Any parsed command sets responding true before validation; a subsequent band/temperature response emits the entire old snapshot. Socket-free production-object injection reproduced:

    status responding=1 operate=1 power=500.0 swr=1.5 band=-1
    after silence responding=0
    status responding=1 operate=1 power=500.0 swr=1.5 band=5
    

    The final input was only ^BN05;, with no fresh OS or WS. The MainWindow consumer then applies those old watts/SWR, and its responding+operate gates suppress the exciter again. This is the precise stale/unknown safety requirement in #4953, not just cosmetic repainting. Invalidate live fields on silence, accept only validated replies as liveness, and retain per-field unknown/fresh state until the relevant reply arrives. Add a socket-free silence→partial-resume regression. The observed evidence is the production connection state/signal; the MainWindow pixel consequence is traced from its consumer, not a claimed GUI reproduction.

  2. Parse valid coalesced frames before applying a partial-frame cap, and really resynchronize at a new caret — src/core/Kpa500Protocol.cpp:30–49. The initial 256-byte whole-buffer clear discards arbitrarily many complete valid frames; indexOf('^') chooses the first caret, consuming a later valid frame as malformed payload. Production parser probes returned:

    input: 60 concatenated ^OS0; frames
    coalesced callbacks=0 expected=60
    input: ^BAD^OS0;
    frame BAD:^OS0
    

    Serial read chunk size is not a frame boundary. A delayed GUI read can coalesce replies; corrupted/incomplete input must not swallow the next valid standby/fault state. Use bounded incremental framing that resets on a new ^, applies the 64-byte cap to an individual incomplete frame, and processes all complete frames. Extend the pure codec tests with both cases. The current suite passes despite these counterexamples.

  3. Unbundle unrelated a11y edits — src/gui/MainWindow.cpp:10045–10111, src/gui/RadioSetupDialog.cpp:2095–2099,2192–2203,2273–2277,2419,2433. The new SWR/ATU/AGC menu status tips and network/automation control accessibility descriptions may be useful fixes, but neither KPA500 support nor #4953 explains them. CONTRIBUTING.md says “One issue per PR. Keep changes focused and reviewable.” Move these hunks to their own focused change; keep the KPA connection-constructor plumbing here. This is a scope finding, not an objection to accessibility improvements.

Nits / limits (non-blocking)

  1. src/core/Kpa500Protocol.cpp:217–222 and its test assertion: buildPowerOn() emits ^ON1; while citing Rev A2. That reference documents ^ON0; for power-off and bootloader P for power-on, not ^ON1;. The builder is unused by UI, so this is not a reproduced operational failure; remove the unused builder or cite captured hardware/newer documented evidence for it. See Elecraft Rev A2, pages 3 and 5.
  2. src/core/Kpa500Protocol.cpp:115–140: validate the documented maximums as well as non-negativity (SWR raw ≤990; voltage/current raw ≤999). Extremely large positive integers currently become accepted telemetry. The official reference above gives the ranges; malformed-input coverage should include them.
  3. src/gui/RadioSetupDialog.cpp:9730–9769: without HAVE_SERIALPORT, the Connect button remains enabled with a handler that does nothing. Dim unavailable controls with a stated reason. This was identified by inspection of the no-SerialPort compile path, not driven in the GUI.
  4. docs/architecture/elecraft-kpa500-kat500-design.md:78–117,193–197, src/gui/Kpa500Applet.cpp:78: reconcile stale polling/silence/gauge/validation descriptions. Head uses WS/OS/BN fast polling, 15 silent ticks, and a 500 W applet gauge; body says 600 W and the design note describes older schedules/three ticks. The head's codec table can now be checked against the public PDF; claims that it was both verified and unavailable should not coexist.

Verification and attacks

  • Rechecked added test and registration: pure codec, no sockets or peer; built the actual kpa500_protocol_test using GCC16.2.1/Qt6.11.2: ALL PASSED (0 failure(s)). Changed only SWR scaling /10→/100 in a scratch implementation: two checks failed, exit1. The existing decode test genuinely detects a scaling defect.
  • Built a focused harness with the actual Kpa500Connection/Protocol and QObject event infrastructure. Injected parser/state and called timer handlers directly, with no SerialPort support, opened port, listener, synthetic firmware or TX. It reproduced both parser failures and stale-state revival above. No base failure comparison is applicable to these entirely new classes.
  • Independently compared BN, FC, FL, OS, WS and VI semantics against Elecraft's official reference. Valid sample scalings agree; the bounds/power-on caveats above remain. Did not reproduce the author's serial hardware or wattmeter observations.
  • Read poll rotation, reconnect reset, disconnect timer cancellation, no automatic OPERATE, no band SET, command echo ownership, applet registration and settings ownership. Those paths retain explicit user intent; the shared ACOM style extraction preserves its literal style.
  • Four engine gates pass at base and head. Capability, command-plane and manifest outputs are identical; engine output changes only line numbers and new file counts, retaining 102 legacy findings and zero blockers. New touchpoints are disclosed standalone peripherals, not vendor-header declassification. No baseline grows.
  • No full-app/bridge session: SimBackend does not implement this serial peripheral, and no live accessory access was authorized. The connection/parser evidence is reproduced; UI integration remains code-reviewed. Automated code-review skill unavailable; older bot findings were rechecked, including the now-fixed no-SerialPort member reference and reverted shared pill restyle.

CI and recommendation

Request changes for stale-state revival, framing recovery and unrelated scope. Five displayed checks pass, but earliest head run 2026-10-02T21:48:38Z predates current main 2026-10-04T18:26:40Z; green applies to that frozen merge result and non-strict protection does not force refresh. Fix the reproduced state/parser paths with socket-free regressions, unbundle the unrelated hunks, update the body/design note, then obtain the maintainer's final peripheral/UI acceptance and bench validation.

— Jeremy KK7GWY + Codex

@ten9876 ten9876 removed their assignment Oct 4, 2026

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

Independent re-review at head da95a70e1fec08ce10c67ebceff8b6b583df8f3d. Same head as the earlier ten9876 + Codex review. I reproduced its main findings with my own harness rather than adopting them. On one of them I disagree about severity, and I've added findings it did not have.

Issue fit: partial, as scoped. For #4953 this delivers KPA500 telemetry, explicit STANDBY/OPERATE, faults and fan control over serial, as a standalone peripheral(elecraft) accessory, which is the shape the triage proposed. KAT500, band push and power control are deferred in the design note. No control is ever automated: nothing selects OPERATE on its own, and buttons are requests repainted from the ^OS reply (Principle II holds).

Scope

File/group Claimed? Verdict
Kpa500Protocol.{h,cpp}, kpa500_protocol_test.cpp, tests.cmake Yes In scope; socket-free codec test
Kpa500Connection.{h,cpp} Yes In scope; blocker 2
Kpa500Applet.{h,cpp}, AppletPanel.{h,cpp}, KPA parts of MainWindow*.{h,cpp} and RadioSetupDialog.{h,cpp} Yes In scope
AmpAppletStyles.h, AcomApplet.cpp Follow-up commit Related deduplication; ACOM keeps its literal style
CMakeLists.txt, touchpoint tags and manifest, design note Yes In scope
MainWindow.cpp:10044-10111 (SWR-scan, pre-tune, clear-ATU and AGC-T setStatusTips) and RadioSetupDialog.cpp buildNetworkTab/buildIpConfigGroup (setAccessibleDescriptions) No Unrelated: blocker 3

On the body:

  • Its claim that the shared Standby pill turns orange is stale. That restyle was reverted, and this head adds a KPA-only ampStandbyBtnStyle().
  • "Fast poll (200 ms: ^WS, ^OS, ^BN)" is wrong; see the nits.
  • All 11 commits are signed, and there's no CHANGELOG entry.

Blockers

  1. This no longer merges with main. git merge-tree origin/main <head> reports conflicts in 9 files: aetherd-touchpoint-tags.json, aetherd-touchpoints.md, AppletPanel.{h,cpp}, MainWindow.{h,cpp}, RadioSetupDialog.{h,cpp} and tests/tests.cmake. Rebase, and regenerate the touchpoint manifest afterwards rather than hand-merging it.

  2. Silence doesn't clear the stale amp state, contrary to what commit 186d6b97 ("safety — clear stale readouts and meter gates on silence") claims. This confirms the earlier review.

    • On silence, Kpa500Connection.cpp:204 flips m_responding but keeps all of m_status.
    • The parser callback (:27-34) then marks the amp responding again on any parsed frame and emits the whole old snapshot.

    I reproduced it with a harness built from this head's Kpa500Connection.cpp/Kpa500Protocol.cpp, injecting into the parser and driving fastPollTick() directly (no serial port, no peer):

    statusUpdated          responding=1 operate=1 power=500.0 swr=1.5 band=-1
    after silence          responding=0 operate=1 power=500.0 swr=1.5 band=-1
    statusUpdated          responding=1 operate=1 power=500.0 swr=1.5 band=5      <- input was only ^BN05;
    

    MainWindow_Wiring.cpp:6838 then puts 500 W on the main PWR meter, and the gates at :6107/:6132 suppress the radio's own TX meters. This lasts until fresh ^OS and ^WS replies arrive, which takes up to ~600 ms (see the poll-rate nit). Typically the amp has just been power-cycled into STANDBY.

    Fix: on silence, clear m_status's live fields (operate, forwardPowerW, swr, paVoltageV, paCurrentA, temperatureC, faultCode) and emit statusUpdated so the applet blanks. Add a socket-free silence → partial-resume test to kpa500_protocol_test or a sibling test.

  3. Unbundle the unrelated a11y edits. This confirms the earlier review. git log -S'setStatusTip(swrTip)' shows these hunks arrived in the PR's first commit fbdb061d, not through the merge from main, and main doesn't carry them. CONTRIBUTING.md: "One issue per PR. Keep changes focused and reviewable." The edits may well be good; they belong in their own PR.

Nits (non-blocking)

  • Framing parser: confirmed, but I'd call it a nit, not a blocker. My harness gives the same two counterexamples as the earlier review: ^BAD^OS0; delivers BAD:^OS0 and drops the OS, and 60 coalesced ^OS0; frames (300 B) deliver 0 callbacks, while 40 (200 B) deliver all 40. Both recover on the next poll: the first loses one reply, and the second needs ≥ ~1.5 s of event-loop stall. A lost ^FL delays a fault display by one slow rotation, and the amp protects itself. Still worth fixing, and it's small. At Kpa500Protocol.cpp:44 take the last caret before the ;, and drop the pre-loop clear at :30-32 (the post-loop cap at :71 already bounds a partial frame):
            // A new '^' starts a new frame: anything before the last one is the
            // remains of a corrupt frame (includes the null-command echo).
            int caret = raw.lastIndexOf('^');
  • Merge leftover in a comment. MainWindow_Wiring.cpp:6864 reads // cache here could skip a needed re-apply. upstream/main. That's a conflict marker da95a70e missed, and a whitespace-only line was added near :6893.
  • Poll rate. fastPollTick() sends one command per 200 ms tick, rotating WS → OS → BN, so each is read every 600 ms. The comment at Kpa500Protocol.cpp:244 ("queried every ~200 ms") and the body are wrong. The consequence: in OPERATE the radio's own TX meters are gated off (:6107, :6132), so the main PWR meter updates at ~1.7 Hz from ^WS. Either say so, or put WS on every tick and rotate only OS/BN.
  • ^WS SWR of 000 is decoded as 0.0. Elecraft's KPA500 Programmer's Reference Rev A2 says "sss will return as 000 when not transmitting". Kpa500Protocol.cpp:123-125 stores that as SWR 0.0, and :6838 passes it to applyAmpTxMeters (value_or(1.0f) doesn't apply, because the optional is set). Leave swr unset on 000.
  • Range checks. :123 and :139 check only >= 0. The same reference gives SWR 1.0–99.0 and V/A 00.0–99.9, so reject sss > 990 and vvv/iii > 999.
  • buildPowerOn() (:217) emits ^ON1;. Rev A2 documents only ^ON0; (off) and says power-on is the boot-loader P command. It's unused outside its own test, so remove it or cite hardware evidence for it.
  • Stale docs. The body's 600 W and the orange shared pill, and the design note's three-tick silence and older poll schedule, don't match this head (500 W, 15 ticks, the rotation above).

Verification and what I tried to break

  • Socket preflight: kpa500_protocol_test is codec-only (no QSerialPort, no socket, no Kpa500Connection, no fake amp), so it's allowed under canon. It is registered in tests.cmake.
  • Built the exact head (app and kpa500_protocol_test): ALL PASSED. Mutation: decoding ^WS watts as ppp / 10 fails 2 checks ([FAIL] ^WS250 015; → 250 W, SWR 1.5). Restored (sha256 matches HEAD) and it passes again, so the decode table really is pinned.
  • Harness: built against this head's real Kpa500Connection and Kpa500Protocol with moc (Qt Core only, HAVE_SERIALPORT off). It reproduced blocker 2 and both parser cases.
  • Protocol against the vendor document: I checked ^BN bands, ^FC 0–6, ^WS/^VI implied decimals and the ^BRP rate table against Elecraft's Rev A2 PDF. The decoders agree apart from the 000 and range nits above.
  • Read for TX safety: no automatic OPERATE on connect or reconnect, no band SET, the exciter-meter gates require connected && responding && operate, and reconnect resets m_status (onTransportUp).
  • Not driven in the app: SimBackend has no serial peripheral path, and I did not touch any serial hardware. The author's on-air observations (260 W, band latency) remain their evidence.
  • CI: green on 2026-10-02 against a base that the 9-file conflict shows no longer exists.

Recommendation: request changes. Rebase; clear live state on silence and add a test for it; split out the a11y hunks. After that this is a maintainer decision on accepting a new amplifier peripheral and its applet UI, plus bench validation on a real KPA500.

— Jeremy KK7GWY + Claude

@WA8PAM

WA8PAM commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks Jeremy, requested changes will be made as soon as I return from Japan.

@Ozy311 Ozy311 self-assigned this Oct 12, 2026
@Ozy311 Ozy311 self-assigned this Oct 14, 2026
@Ozy311 Ozy311 removed their assignment Oct 14, 2026
@Ozy311

Ozy311 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Bot-review follow-up is pushed at 6a49aeeb.

The new issue routed from #6452 was valid: malformed KPA reply arguments could put control characters into decoded diagnostics. The parser now rejects non-printable bytes before delivery and recognizes command letters using ASCII ranges. Raw RX diagnostics retain percent-escaped bytes. The two new injected-transport/parser regressions failed before the fix and now pass, covering every byte outside printable ASCII and recovery on the next valid frame.

I also checked and replied individually to all eight older inline bot threads. Their earlier fixes are present at the current head: optional-SerialPort compilation, stale applet/meter handling, shared standby colors, per-instance poll rotation, bounded frame handling, design-note references and baud documentation. Those addressed threads are now resolved; existing human reviews are unchanged.

All six native Mac KPA500 suites and the documentation production build passed. The connection suite compiles without HAVE_SERIALPORT; the combined KAT500 stack also built the native Mac app and passed all 12 Elecraft suites. Both PRs retain their separate contribution/scope descriptions. All required Linux/Windows/macOS CI, Static Checks and sanitizer configuration, and docs have now passed at 6a49aeeb. No radio transmission, merge or Windows lab redeployment was performed in this follow-up.


73, Ozy K6OZY · prepared with Codex

@Ozy311 Ozy311 assigned Ozy311 and unassigned Ozy311 Oct 14, 2026
@Ozy311 Ozy311 removed their assignment Oct 14, 2026
@Ozy311

Ozy311 commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Rechecked the current head a9f5bb2c and the existing review history. No new KPA500 review finding or unresolved inline thread appeared. The previously addressed parser, stale-state, optional-SerialPort, scope and documentation fixes remain in the current branch. Its Linux, Windows, macOS, Static checks and docs jobs are green.

The description now records Jeremy's direction: TX Controls retains radio metering and native ATU/MEM; the accepted percent readouts remain. Amplifier output stays in the dedicated applet and VU/PWR. RFC #6477 has now been closed as withdrawn, with the direction relayed by Ozy and Ozy's confirmation of the reverted UI. This does not dismiss the earlier changes-requested review: Jeremy's fresh review is still needed. WA8PAM's attribution, screenshots and the forthcoming aether-serial-bridge note are retained. No KPA500 source change or new RF test in this pass.


73, Ozy K6OZY · prepared with Codex

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

Reviewed exact head 874cf214e19aae344682b57996f8a77a76d1e2b1 against main 7864ef544ffbb465bb2136438d748f9da58aa30c.

This delivers the KPA500 portion of #4953: an independent serial/raw-TCP peripheral, confirmed-state applet, Advanced maintenance, shared VU/PWR metering and user documentation. KAT500 remains in #6452. The landing request accepts the proposed feature; this review checks its implementation and merge readiness.

File/group Scope and verdict
Kpa500Protocol, Kpa500Connection, maintenance/fault codecs Claimed KPA500 protocol, transport, freshness and guarded maintenance; in scope
KPA500 applet/Advanced, AppletPanel, Peripherals and MainWindow wiring Claimed device setup, controls and metering; in scope
HGauge, shared clear-fault style Opt-in KPA scale strokes and equivalent ACOM style factoring; existing behavior retained
TX Controls percentage labels Explicit direction recorded in #6477; radio-only metering/control policy retained
CMake/test registration, touchpoints, design/user docs/screenshots Supporting feature integration and evidence; in scope

Blockers remaining: none. The review found and fixed one additional stale-power path in src/core/Kpa500Connection.h:47 and the four MainWindow shared-meter sites: unrelated valid replies could keep expired amplifier watts selected. hasLiveTxMeters() now requires current powered-on/Operate/forward-power readback for both routing and scale. tests/kpa500_connection_test.cpp:239 exercises expiry, unrelated partial replies, fresh zero and Standby. Fix: 874cf214e.

The older review blockers are addressed: stale snapshots are invalidated, only validated replies establish liveness, framing is bounded/resynchronizing, unrelated WSJT-X/a11y changes are removed, optional-SerialPort compilation works, and the generated index was reconciled with current main. Both old changes-requested reviews were dismissed with this evidence; all eight pre-existing inline threads are resolved with replies.

Verification:

  • Native macOS/Qt 6.12 full app build passed; 11/11 selected headless CTests passed (six KPA500 suites, MeterModel, TX Controls reconciliation, both HGauges and Peripherals automatic setup). The connection suite uses in-memory injection and compiles without HAVE_SERIALPORT. No new socket peer or fake firmware ran.
  • Mutation: removing forward-power freshness produces two failed stale-selection assertions; restoring it passes. The tests also cover malformed/coalesced/truncated replies, per-field and total silence, reconnect/cancellation, transport write failures, maintenance preflight/TX refusal, bounded transfers, no write retry, identity mismatch and byte-for-byte verification.
  • Clean-bundle bridge on isolated, offscreen, TX-disabled DEMO-0001 passed 10 assertions for disconnected/unknown device state, no demo TX-band target, Serial/Network setup, and disabled hardware maintenance. Radio TX/MOX/Tune stayed false. The owned process was disconnected and closed. This is app/demo evidence; amplifier-positive behavior remains injected/source-reviewed, not live hardware proof.
  • Base/head engine findings remain identical after line normalization: 102 tracked warnings, zero blockers. Capability/command-plane counts and vendor classifications do not grow. Touchpoints, test registration, frozen CI gate, colour ratchet, accessibility, generated docs/references and user-doc production build pass. Log rules pass 707 checks; their existing moved-line advisory is non-blocking.
  • Standard command scalings, ranges, SWR 000 and bootloader P agree with Elecraft Rev. A2. The added maintenance format remains explicitly scoped to observed firmware 01.54. Physical restore/banner-write persistence, higher-power accuracy and live RF on this final revision remain unverified; no hardware/TX was used.

Non-blocking limits: the history screenshot predates DRV and is disclosed as such; final high-power/hardware-write qualification remains separate. No new automated review skill was available; the pass was performed manually and existing bot findings were checked against the current diff.

CI and merge freshness: all required Linux build, Windows check-windows, macOS check-macos and Static checks passed at this exact head; docs and sanitizer configuration also passed. The earliest head run was created at 2026-10-17T15:16:10Z, after current main 7864ef544 (2026-10-16T05:46:18Z). Main is unchanged, all 25 PR commits are GitHub-verified, no unresolved threads remain, and git merge-tree is clean. Post-merge CI on the squash commit remains a separate required verification.

Approved for the requested guarded squash merge at this exact head.

Generated with OpenAI Codex (GPT-6 Astra)

@jensenpat
jensenpat merged commit 5813556 into aethersdr:main Oct 17, 2026
7 checks passed
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.

4 participants