Skip to content

feat(kxpa100): add amplifier and optional tuner applet - #6475

Merged
jensenpat merged 2 commits into
mainfrom
codex/kxpa100-applet
Oct 17, 2026
Merged

jensenpat merged 2 commits into
mainfrom
codex/kxpa100-applet

Conversation

@Ozy311

@Ozy311 Ozy311 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Closes #6512.

Adds the compact Elecraft KX100 applet for the KXPA100 amplifier and optional KXAT100 tuner, built on the merged KPA500/KAT500 foundations (#5991, #6452). Supports local serial and raw TCP, confirmed amplifier telemetry, manual tuner controls, Full/Mini presentation, history, diagnostics and Advanced maintenance.

  • The applet provides Standby/Operate, tuner Auto/Manual/Bypass, antennas, tuner-only Tune/Cancel, Recall/Memorize, physical bypass/attenuator and C/L relays. Telemetry includes PA input DRV, forward/reflected power, SWR, current, voltage, dissipation and temperature. A missing optional tuner dims only tuner-specific controls.
  • Use with current radio binds the amplifier serial to the exact radio solely for VU/PWR metering and applet frequency context. Association sends no device commands; refused writes retain the previous durable association and display/log the failure.
  • Fresh associated PA Operate telemetry supplies VU/PWR after higher-priority radio-integrated amplifiers and KPA500. KAT500 KEY BLOCK retains the KPA500 radio fallback. Stale/standby/tuning KXPA100 telemetry falls back to the radio. Main’s KPA500 telemetry-freshness admission is preserved.
  • TX Controls remains radio-side, following the direction recorded in withdrawn RFC [RFC] Elecraft integration direction: dedicated controls and VU metering #6477. ATU/MEM keeps the radio’s behavior and availability reasons.
  • Advanced provides staged per-band antenna configuration/readback, validated atomic native .KXPAConfig backup/restore, stored faults/CSV, memory inspection/confirmed erase and separately confirmed reset. Firmware-dependent maintenance is restricted to the verified 01.39 layout. Connecting and opening Advanced perform no maintenance write.

Transport/parser queues and receive buffers are bounded, endpoints are validated, and late/uncertain transactions do not replay writes. Basic SWR polling accepts documented nn.n decimal replies, including zero, alongside captured compact replies. No Telnet/RFC2217 support, remote power command, firmware updater or raw Command Tester is added. Protocol knowledge comes from public references and isolated Utility I/O observations, without copying proprietary implementation.

Validation at rebased head 220f8726e6330ee3b7287cb8a51e76008c2c8a83:

  • Native macOS ARM64 app build passed. All 10 selected headless CTests passed: six KXPA100 targets plus KAT500 meter-source selection, Radio Setup pair grid and TX power reconciliation, and the repaired Radio Setup operations target.
  • Disabling decimal SWR support failed both protocol and production-scheduler model regressions; restoring it passed both. The model cases include fragmented replies, request completion, advancing past the old deadline and continued polling without disconnect.
  • Isolated TX-disabled DEMO-0001 bridge session: one slice/pan, transmitting=false, native ATU/MEM; disconnected KXPA100 Advanced read/restore/erase/reset controls were disabled with accessible reasons. The owned app exited.
  • Four architecture checks passed with unchanged normalized legacy findings; generated references and Log Analyzer checks passed. The new Radio Setup operations target includes both peripheral Advanced widget sources; all 14 peripheral-page test targets were audited. Hosted Docs and main-target platform CI are recorded in this PR’s checks.
  • Peripheral tests use injected transport/time or invalid-endpoint refusal; no synthetic socket/serial firmware peer was run. Actual hardware communication, EEPROM effects, RF/TX and cross-platform runtime operation were not revalidated in this landing pass. The demo does not establish device convergence.

The Full meter screenshot uses explicitly labeled replay telemetry and is not a live RF measurement:

KXPA100 Full applet with replay telemetry

Generated with OpenAI Codex (GPT-6 Astra)

@Ozy311
Ozy311 requested review from a team as code owners October 14, 2026 18:59
@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 9 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 15
Files with findings: 1
Total findings : 9

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

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

Reviewed at head 88e9edb against the stacked base 4ca5cd34 (so this review covers the KXPA100 additions only, as the body asks). Findings below are reasoned from the code in the PR head checkout — I have no build, no test run and no way to drive the GUI, so nothing here was reproduced at runtime.

1. Issue fit

There is no linked issue and no RFC for the KXPA100. search_issues for "KXPA100" returns only PRs (#6475, #5991) — #4953, which #6452 closes, covers the KAT500/KPA500 pair, not this device. So there is no requirements list to map the diff against; I reviewed against the PR's own stated intent, which the diff does deliver: protocol, model, applet, Advanced maintenance, and TX Controls integration are all present and coherent.

On GOVERNANCE.md: the applet half is explicitly carved out — "New applets or dialogs that don't change existing UX" does not require an RFC. The TX Controls half is a different matter, because it changes what the existing ATU and MEM buttons do once a device is associated (ATU→AUTO, MEM→SAVE, % suffixes on RF Power / Tune Pwr, and the shared meter source), and GOVERNANCE.md lists "Any change to default UX behavior — what a click, shortcut, or gesture does out of the box" as RFC-requiring. You already flag this yourself ("Default-on TX Controls integration after association, the AUTO/ATU/SAVE overlay, meter selection and visual/UX choices still need maintainer direction"), and #6452 set the precedent of tracking through an issue instead. Noting it as a maintainer call, not asserting a violation.

CI: only 4 checks ran (Static checks, Sanitizer configures, docs, PDF-skipped) because the PR targets codex/kat500-applet. There is no compile lane and no test lane in that set, so there is currently zero CI compile or test evidence for this diff — as the body states. The build/link checks I could do statically are in §5.

2. Scope

Files What it changes Claimed? Verdict
src/core/Kxpa100Protocol.*, Kxpa100Maintenance.* (480 lines) New frame parser, status decode, EEPROM/backup/fault primitives Yes In scope
src/models/Kxpa100Model*.{h,cpp}, Kxpa100Types.h (887 lines) Transport, transaction queue, maintenance state machine Yes In scope
src/models/Kxpa100TxController.* (451 lines) TX Controls external-tuner sequence Yes In scope
src/gui/Kxpa100Applet.*, Kxpa100AdvancedWidget.* (1,825 lines) New applet + Advanced page Yes In scope
src/gui/AppletPanel.*, MainWindow.{h,cpp}, RadioSetupDialog*.{h,cpp} Register applet, pass the model, build the Peripherals page Yes In scope
src/gui/TxApplet.*, SharedTxMeterSelection.h, MainWindow_Wiring.cpp ATU/MEM overlay, meter routing, autoconnect Yes In scope — but see Blockers 1 and 2; this is the only shared behaviour touched
tests/kxpa100_*.cpp (6), tests/fixtures/kxpa100/* (5), tests/tests.cmake New coverage; Kxpa100AdvancedWidget.cpp added to 12 existing peripherals targets Yes In scope
CMakeLists.txt, docs/architecture/aetherd-touchpoint* 7 new sources; manifest 262→265 / 44→47 models Yes In scope (generated; --check passes in Static checks)
docs/user/docs/kxpa100-amplifier-control.md, peripherals.md, sidebars.js, 6 PNGs User guide Yes In scope

Everything in the 49-file diff is explained by the stated feature. No CHANGELOG.md edit (correct — please keep it that way). One thing a reader shouldn't have to check: commit 6ae7906 is titled "Add KPA500 radio drive and label KXPA100 DRV", but no Kpa500* file appears in this PR's file list — the KPA500 half rode in via the base merge and belongs to #5991, matching what the body says.

3. Blockers

1. src/gui/MainWindow_Wiring.cpp:6449-6453 — a KXPA100 association short-circuits the entire shared meter policy, including the KPA500/PGXL arms.

    if (m_appletPanel && m_appletPanel->txApplet()->kxpa100Associated()) {
        return m_kxpa100Model.amplifierMeterEligible()
            ? SharedTxMeterSelection{SharedTxMeterSource::Kxpa100, 100, false}
            : SharedTxMeterSelection{SharedTxMeterSource::Radio, maxWatts, false};
    }
    const bool amplifierActive = m_radioModel.amplifier().present() ...
    const bool kpa500Active = m_kpa500Conn.isConnected() && ...
    return selectSharedTxMeters(maxWatts, amplifierActive, kpa500Active,
                                m_kat500Model.ampKeyBlockConfirmed());

This return sits above every other source. Note the contrast with how the KAT500 was integrated in the same function: it is passed into selectSharedTxMeters(...) as a parameter, so one policy function still decides. The KXPA100 instead bypasses that function entirely.

Failure scenario: a KX3 or HL2 → KXPA100 → KPA500 chain (a normal Elecraft stack). The operator associates the KXPA100 and the KPA500 is in Operate. kpa500Active and amplifierActive are never evaluated, so the meters are pinned either to the KXPA100's hard-coded 100 W or to the radio's maxWatts — the 500/1500 W amplifier scale is unreachable, and m_kat500Model.ampKeyBlockConfirmed() is skipped too. The final amplifier in the chain is the one the meters should be showing.

The right precedence is a design call, so I'm not proposing the patch: either fold the KXPA100 in as another parameter to selectSharedTxMeters (consistent with the KAT500), or keep the override and document it. Also worth a second look: 100 is hard-coded here where the other arms derive their scale.

2. src/gui/TxApplet.cpp:155-156, 176 — association (not selection) permanently tears down KAT500 TX routing, even for a KXPA100 with no tuner fitted.

            if (kxpa100Associated() && m_katTx) { m_katTx.reset(); }
            else if (!kxpa100Associated() && !m_katTx && m_kat500) { rebuildKat500Routing(); }
    if (m_radioModel && m_kat500 && !kxpa100Associated()) {

The gate is kxpa100Associated() → Kxpa100TxController::associated(), which at src/models/Kxpa100TxController.cpp:71-77 checks only the settings scope and the serial — it does not require tunerPresent. Meanwhile atuRoute() claims the buttons only when selected() is true, and selected() does require tunerEligible() (hence tunerPresent == true).

Failure scenario: an operator with a bare KXPA100 (no KXAT100 option) plus a real KAT500 associates the amplifier so the shared power meters read amplifier watts. m_katTx is destroyed and never rebuilt while the association stands; the KXPA100 never claims the route because it has no tuner; so atuRoute() falls back to Radio and TX Controls silently reverts to the radio's internal ATU/MEM. The KAT500 integration is gone with no indication, and neither the KXPA100 guide nor kat500-tuner-control.md mentions that owning a KXPA100 overrides it.

Blockers 1 and 2 are the same underlying question — association is currently a hard override of every sibling device rather than a participant in the existing precedence — so they probably want one ruling. That ruling is also the second half of the default-on question you already raised.

3. src/models/Kxpa100TxController.cpp:130 — the TX Controls preference write result is discarded, with no log.

    doc["useTxControls"] = enabled;
    scope.setFeature("kxpa100", 1, doc);

docs/agents/settings.md is explicit: "Check the write result. setFeature() can refuse (read-only store, reset in progress); a mutation that silently doesn't persist while the UI repaints from the store is the worst failure shape. Log loudly at minimum." That is exactly the shape here — Kxpa100Model::m_useTxControls has already been set in memory, so the checkbox repaints as enabled while the store rejected it, and the preference reverts next session.

The body discloses the UI half ("a failed persistence write of the integration preference has no dedicated UI warning"), but canon's minimum bar is the log, and there is no log either. The two sibling writers in the same file get this right — associate() at :89 and dissociate() at :117 both return false on refusal — so this is the one outlier. No test covers it (tests/kxpa100_tx_controls_test.cpp exercises setUseTxControls and associate but never a refusing store), which is why nothing caught it.

    doc["useTxControls"] = enabled;
    if (!scope.setFeature("kxpa100", 1, doc)) {
        qCWarning(lcKxpa100Tx) << "kxpa100: TX Controls preference did not persist";
    }

lcKxpa100 is defined with internal linkage in Kxpa100Model.cpp:13, so it isn't visible here; the drop-in needs a category in this TU — add #include <QLoggingCategory> and, inside the AetherSDR namespace, namespace { Q_LOGGING_CATEGORY(lcKxpa100Tx, "aether.kxpa100") }.

4. Security audit

Required and run (security-audit subagent, read-only, over the harness file list and this PR's new parser/maintenance/model/GUI code). No confirmed defects. Two parity gaps I verified myself and credit to security-audit (CodeGuard):

  • src/models/Kxpa100Model.cpp:22 — m_socket(std::make_unique<QTcpSocket>()) never sets a read-buffer cap, where the sibling src/core/Kpa500Connection.cpp:28 does: m_socket.setReadBufferSize(4096);. A hostile raw-TCP bridge peer flooding bytes while the GUI thread is stalled grows Qt's internal read buffer unbounded. Heavily mitigated — readyRead drains with readAll() straight into FrameParser::feed, whose own buffer is capped at 512 (src/core/Kxpa100Protocol.cpp:32) — so this needs a blocked event loop to matter. Kat500Model.cpp:24 has the same omission. One line to match the KPA500.
  • src/models/Kxpa100Model.cpp:48 — if (m_connected || m_connecting || host.trimmed().isEmpty() || !port) { return; } does no host syntax validation, where Kpa500Connection.cpp:161-165 rejects over-long hosts, /, and non-printables. Not exploitable here: the host is operator-typed in Radio Setup and goes to connectToHost with no URL or SSRF surface. Defence-in-depth parity only.

The agent refuted, with the discriminating reason in each case, the whole list of things worth worrying about in wire-facing code: out-of-bounds frame[2]/frame[3]/(*bytes)[0]/fields[0..3]/captured(N) indexing (every size test short-circuits ahead of its index), off-by-one in rangeValid/buildEepromWrite/parseEepromReply, memory[address + offset] escaping the 1024/16384 buffers in decodeBackup, unbounded growth in FrameParser::feed (512), the request queue (64), debug ring (1000) and history deques (4096), division-by-zero or overflow in the scaling math (all divisors compile-time constants; relayMaskForValue casts to qint64), CSV injection in faultHistoryCsv (no wire-derived field can begin =/+/-/@), path traversal in saveBackup/QSaveFile and the device-derived SN%1.KXPAConfig default name (^SN is validated as exactly five digits), and credentials (none — the only persisted values are a 5-digit serial, a bool, and applet UI prefs; nothing belongs in QtKeychain). The .KXPAConfig restore path is gated by CRC32 + full-coverage + identityValid + exact serial/firmware match + firmware pinned to 01.39 + explicit confirmed + per-block write-then-readback. No new listening socket or bind: both models are outbound connectToHost clients only.

On the harness's 888 CodeGuard findings: none of them land on a line this PR adds. Every cited location is in .github/workflows/static-checks.yml, docs/user/screens.json, docs/user/src/components/LogAnalyzer/rules.json or src/core/AutomationServer.cpp — four files this PR does not modify, none of which contains a case-insensitive kxpa reference — plus docs/architecture/aetherd-touchpoint-tags.json:10 and :1157, nowhere near this PR's +1630 hunk. That is a whole-file scan of base content, and I have not folded any of it in.

5. Nits (non-blocking)

  • src/gui/MainWindow_Wiring.cpp:7611-7615 — the applet's association handler discards the result: Kxpa100TxController::dissociate(...) / associate(...) both return bool and both are ignored. The Peripherals button gets this right at src/gui/RadioSetupDialog_Peripherals.cpp:104-105 (const bool saved = ... then an explanatory tooltip either way). Since the guide offers both entry points equally ("click Use with current radio in the peripheral page or applet Settings"), one silently does nothing on failure and the other explains. Same root cause as Blocker 3.
  • src/gui/RadioSetupDialog.h:214 — two declarations collapsed onto one line: ...& pageReseeds); void buildKxpa100Device(PeripheralDeviceUi& ui, QWidget* stackParent,. Compiles, just unreadable; split it at the );.
  • tests/tests.cmake:4623-4628 — kxpa100_model_test lacks the if(TARGET Qt6::SerialPort) block its sibling kat500_model_test has at :4650-4653. Harmless for the build (every QSerialPort use in Kxpa100Model.cpp is correctly inside #ifdef HAVE_SERIALPORT, and the target doesn't link aethercore, which is what defines it PUBLIC), but it means the serial transport arm is compiled out of the only model test, so connectSerial has no coverage on any platform where the KAT500 equivalent does.
  • src/gui/TxApplet.cpp:745 — m_kxpa100->status().mode is unguarded inside a block entered on selected() || busy(), while the same function guards m_kxpa100 && m_kxpa100->status().mode 16 lines later at :757. As far as I can trace this is not a reachable null (the ternary short-circuits when busy is true, and selected() implies the model is alive), so this is consistency only.
  • src/models/Kxpa100TxController.cpp:198 — m_tuner->tunerEligible() is dereferenced before the && m_tuner later in the same expression; safe only because associated() short-circuits first. The null checks on m_radio/m_tuner at the end of that chain are dead.
  • src/models/Kxpa100TxController.cpp:17 — effectiveTxFrequencyKhz reads slice->frequency(), so on a transverter band the displayed RF frequency fails mhz < 100.0 and the function returns 0, making the integration unavailable with "KXPA100 requires a TX frequency from 1800 to 54000 kHz". Refusing is the correct outcome (a VHF transverter's output doesn't pass through a 1.8–54 MHz amplifier), and it's the same txSlice()->frequency() seam #6472 tracks — flagging only so it isn't mistaken for a bug later.

6. Socket-test disclosure

Per #5254 reporting: no new socket-owning test. I grepped all six added test sources for QTcpServer|QTcpSocket|QUdpSocket|QLocalServer|listen(|bind(|connectToHost|Fake|QProcess — zero hits. kxpa100_model_test and kxpa100_applet_test link Qt6::Network only because Kxpa100Model owns a QTcpSocket member; the tests inject the transport via Kxpa100ModelTest friend access (model.m_write = ...), so nothing opens, binds, listens or connects, and no fake amplifier/tuner peer is introduced. Nothing to act on — recording it as the notification.

7. What I tried to break

  • Principle VI, the carrier sequence. Walked every path out of Phase::Carrier. The carrier only starts in operationFinished() after status().tuning == true and originalIntentValid() and internalBypassed() (Kxpa100TxController.cpp:288-295); it is dropped by the 30 s deadline, by ^FE, by the 100 ms watchdog the moment isTuning() or internalBypassed() goes false, by operationFailed, by tuner disconnect, by radio disconnect, and by ~Kxpa100TxController (m_carrier.stop() at :137). Nothing in the status path can manufacture authority — a spurious ^FE or ^TP0 from the wire can only stop the carrier. tunePower is never written. Held.
  • Antenna and freshness. Tried to start a tune with no confirmed antenna: tunerEligible() (Kxpa100Model.cpp:17-22) requires fresh ^TU/^MD/^AN and antenna.has_value(), so the "ANT1 after power-off" case in your own troubleshooting section can't be tuned into. Tried to publish stale watts to the shared meters: amplifierMeterEligible() requires fresh("^PF") plus operate == true and tuning == false, so the value_or(0) in the statusUpdated lambda can't surface a stale forward reading. Both held.
  • The tuner-loss window. Found one state where busy() survives a destroyed tuner — Phase::Cancelling, where refresh() only calls pollCancellation(), which early-returns on a null m_tuner, leaving the phase set until the 5 s deadline. Every other phase self-heals to Idle through originalIntentValid()/saveIntentValid()/the Result guard. I could not turn that window into a reachable crash (it needs an event loop running after the model is destroyed, and the model is a direct MainWindow member), hence the nit rather than a finding.
  • "Restores normal radio controls." Verified the claim rather than taking it: updateAtuAvailability restores setCheckable(true), the MEM text, kRadioTxButtonStyle and the m_radioHasTuner/m_radioHasTunerMemories enable rules on the radio fallback path, and the KXPA100 arm mirrors the KAT500 arm exactly. Held.
  • The build CI never ran. Swept tests.cmake programmatically: all 12 targets that compile RadioSetupDialog_Peripherals.cpp gained src/gui/Kxpa100AdvancedWidget.cpp — none missed. All 3 targets that compile TxApplet.cpp link aethercore, which carries Kxpa100TxController.cpp via CORE_SOURCES, so the new symbol resolves. All 7 new sources are in CMakeLists.txt; all 6 new tests have add_test; kxpa100_applet_test and kxpa100_advanced_widget_test joined AETHER_SETTINGS_CONSUMERS (the two *_tx_controls_test targets correctly don't — they link aethercore rather than compiling AppSettings.cpp, matching the KAT500 precedent). I found no static build break, but this is not a substitute for the compile matrix.
  • The tests, attacked rather than counted. kxpa100_tx_controls_test.cpp is not self-confirming: it asserts b->wire.isEmpty() across association, identity change and mode change (so a stray radio command fails it), b->wire.count("tune:on") == 1 only after ^TP1, tune:off == 1 after ^FE, tunePower() == 1 unchanged, and the full fallback (^MDB → ATU/MEM/disabled, foreign identity → MEM). Those would fail with the guards broken. It uses TestSettingsProfile for isolation. The gap is the one behind Blocker 3 — no refusing-store case — and sharedTxMeterSelection() is unreachable from any test since nothing constructs MainWindow, which is why Blocker 1 has no coverage either.
  • Settings canon. associate/savePreference/dissociate/loadAssociation all use featureExact (never the family-wide feature()), all refuse a schema_version newer than 1, and associate additionally refuses Corrupt/Unavailable and an empty radioId — so no accidental family-wide row. Per-radio state is in a radio_settings feature document via settingsScope(), and only the client-side connection prefs use the flat PeripheralSettings helper, which is correct. The only break is the unchecked write at :130.

8. Recommendation

Needs maintainer decision. The engineering here is careful and the hard part — the TX sequence — is right: the carrier is strictly subordinate to a confirmed tuner state, every abort path drops it, and the parser survived a dedicated adversarial pass with no confirmed defect. Blocker 3 is a two-line fix. What actually needs a ruling is Blockers 1 and 2: as written, associating a KXPA100 is a hard override of every sibling device — it short-circuits the shared meter policy above the KPA500/PGXL arms, and it permanently destroys KAT500 TX routing even when the KXPA100 has no tuner fitted. That is the same question as the default-on TX Controls behaviour you already flagged for CODEOWNERS, and it's worth answering once for all three devices in the stack before any of them merge.

Concrete next step: fix Blocker 3, get a ruling on association precedence, then retarget to main once #5991 and #6452 land so the compile matrix and test lanes actually run — right now four checks have passed and none of them built this code. Thanks for the unusually honest limits section; "a zero sampled peak is not an event-wide power measurement" and the explicit "Linux native KXPA100 validation is not yet recorded" made this much faster to review.


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

@aethersdr-agent

Copy link
Copy Markdown
Contributor

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

  • [CRITICAL] CS-VLN-CMDI-SUBSHELL — Vulnerability: CS-VLN-CMDI-SUBSHELL in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:496
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:14
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:36
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:81
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:172
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:309
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:332
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:340
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:355
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:399
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:414
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:469
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:531
  • [MEDIUM] CS-VLN-CMDI-ENVVAR — Vulnerability: CS-VLN-CMDI-ENVVAR in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6475/.github/workflows/static-checks.yml:534
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in docs/architecture/aetherd-touchpoint-tags.json /tmp/aetherclaude/pr-6475/docs/architecture/aetherd-touchpoint-tags.json:10
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in docs/architecture/aetherd-touchpoint-tags.json /tmp/aetherclaude/pr-6475/docs/architecture/aetherd-touchpoint-tags.json:1157
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:2579
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:4219
  • [MEDIUM] CS-PII-EMAIL — PII detected: email in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:3877
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:1122
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:2579
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:2625
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:2659
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:2756
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:2796
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6475/docs/user/screens.json:4219
  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in docs/user/src/components/LogAnalyzer/rules.json /tmp/aetherclaude/pr-6475/docs/user/src/components/LogAnalyzer/rules.json:89
  • [HIGH] CS-VLN-PATH-UNC — Vulnerability: CS-VLN-PATH-UNC in docs/user/src/components/LogAnalyzer/rules.json /tmp/aetherclaude/pr-6475/docs/user/src/components/LogAnalyzer/rules.json:744
  • [HIGH] CS-VLN-PATH-UNC — Vulnerability: CS-VLN-PATH-UNC in docs/user/src/components/LogAnalyzer/rules.json /tmp/aetherclaude/pr-6475/docs/user/src/components/LogAnalyzer/rules.json:1003
  • [HIGH] CS-PII-DL-1 — PII detected: drivers_license in docs/user/src/components/LogAnalyzer/rules.json /tmp/aetherclaude/pr-6475/docs/user/src/components/LogAnalyzer/rules.json:88
  • [HIGH] CS-VLN-SSRF-PRIV-192 — Vulnerability: CS-VLN-SSRF-PRIV-192 in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:6982
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:12245
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:12246
  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:6192
  • [CRITICAL] CS-VLN-CMDI-SEMI — Vulnerability: CS-VLN-CMDI-SEMI in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:1759
  • [CRITICAL] CS-VLN-CMDI-SEMI — Vulnerability: CS-VLN-CMDI-SEMI in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:7470
  • [CRITICAL] CS-VLN-CMDI-SEMI — Vulnerability: CS-VLN-CMDI-SEMI in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:13036
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:2394
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:8433
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/core/AutomationServer.cpp /tmp/aetherclaude/pr-6475/src/core/AutomationServer.cpp:9459

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: $28.1619 · model: claude-opus-5

@Ozy311

Ozy311 commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Published shared RFC #6477 for the KAT500 and KXPA100 TX Controls changes: default-on behavior, AUTO/ATU/SAVE and route indication, optional-tuner coexistence, and shared amplifier-meter precedence. It records that implementation preceded the RFC and requests maintainer approval rather than treating existing code or operator approval as that decision. The KXPA100 precedence findings are explicit open items; this link does not mark them resolved.

@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 9 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 15
Files with findings: 1
Total findings : 9

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

1 similar comment
@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 9 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 15
Files with findings: 1
Total findings : 9

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@Ozy311

Ozy311 commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the unchecked preference-write finding in signed commit 7942283a. savePreference() now checks setFeature() and logs an aether.kxpa100 warning that a rejected change applies only to the current session. The old durable document is preserved; no routing/default decision or automatic retry was added.

A regression holds a competing transaction on the isolated test database. It failed before the fix and passes afterward, checking the categorized warning, retained saved preference, session-only change, reload behavior, successful save after releasing the lock, and no preference-triggered radio/device commands. Native Mac app build, all three focused KXPA100 suites and docs production build pass. No hardware or RF was used.

The two multi-device routing/meter-precedence findings and RFC #6477 remain open. New-head stacked CI is running; this does not claim main-target platform CI.

@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 9 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 13
Files with findings: 1
Total findings : 9

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

1 similar comment
@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 9 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 13
Files with findings: 1
Total findings : 9

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@Ozy311

Ozy311 commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the outstanding review items at 982ce601 (KXPA100 fix ab9f4f5d, with KAT500 transport dependency d0f2dfe8):

  • The association-first shared-meter override is gone. The single selection function preserves radio-integrated amplifier priority, then KPA500 / confirmed KEY BLOCK fallback, then eligible KXPA100 telemetry. Regression cases cover those combinations.
  • KXPA100 association no longer owns or destroys any KAT500 TX routing. Both TX Controls overlays and their preferences are removed; the KXPA100 TX controller is gone and the remaining KAT500 controller serves only its dedicated Pre-tune operation. A bare KXPA100 remains eligible for PA telemetry; the optional-tuner-absent regression preserves native radio ATU/MEM behavior.
  • The unchecked preference writer was removed with that feature. Remaining association writes check refusal, log it, retain the old durable state, and now display failure in applet Settings as well. The existing competing-SQLite-transaction regression and the new applet result checks pass.
  • Capped TCP receive buffering to 4096 bytes and brought endpoint validation in line with KPA500. Both new model cap regressions failed before their fixes. Invalid endpoint tests leave the socket unconnected; no test connects to a radio/bridge.
  • The model test now compiles the SerialPort arm when QtSerialPort is available (verified in the Mac Ninja graph), including invalid serial-argument refusal without opening a port. Other widget targets still cover the optional no-SerialPort compilation path.
  • Split the collapsed RadioSetupDialog declarations. Removed the misleading Space-to-preview instruction inherited by KXPA100; the real application reserves Space for PTT, so the widget test now verifies mouse hold/release and the separate application shortcut regression remains intact.

The old TxApplet/controller null-check nits became obsolete with removal of those paths. The transverter-frequency refusal noted by the reviewer remains intentional. The nine RadioSetupDialog accessibility warnings reproduce on the KAT500 base; none is new in this PR. The earlier CodeGuard report's out-of-diff findings remain outside this change.

Native Mac app build, all 16 combined focused offline suites and production docs pass. The retained screenshots identify replay telemetry explicitly; no RF or hardware writes were performed. RFC #6477 is closed as withdrawn, recording Jeremy's direction and Ozy's confirmation of the radio-side TX Controls. Full main-target platform CI remains required after eventual retargeting, along with Jeremy's code review.


73, Ozy K6OZY · prepared with Codex

rfoust
rfoust previously requested changes Oct 15, 2026

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

The PR delivers the combined KXPA100/KXAT100 applet, Advanced maintenance tools and exact-radio metering association described in its current body. There is no linked KXPA100 feature issue; the removal of the external TX Controls overlay is consistent with the direction recorded in #6477. The earlier meter-precedence and association-write findings are addressed. One documented-protocol compatibility blocker remains, reproduced through the production model with injected transport.

Scope

Files/group Change Claimed? Verdict
Kxpa100Protocol.*, Kxpa100Maintenance.*, Kxpa100Types.h, Kxpa100Model* Wire parsing, confirmed state, bounded device/maintenance operations Yes In scope; SWR compatibility blocker below
Kxpa100Applet.*, Kxpa100AdvancedWidget.* Dedicated combined applet and maintenance UI Yes In scope
AppletPanel.*, MainWindow*, RadioSetupDialog* Registration, lifecycle and peripheral setup Yes In scope
Kxpa100RadioAssociation.*, SharedTxMeterSelection.h Metering-only radio association and amplifier priority Yes In scope
CMakeLists.txt, six KXPA100 tests, fixtures, tests/tests.cmake Build and offline coverage Yes In scope
User guide, sidebar, screenshots and touchpoint manifests User documentation and generated architecture inventory Yes In scope; unused overlay assets noted below

No unrelated code changes or CHANGELOG.md edits found. The current diff matches the revised feature scope, apart from obsolete title/assets.

Blocker

  1. [P2] Accept documented decimal SWR replies before treating the poll as timed out. See the inline comment on src/core/Kxpa100Protocol.cpp:93-95. Elecraft's published decimal replies are rejected, leaving the routine SWR request pending until the model closes the transport. The existing protocol test explicitly requires rejection of decimal SWR and therefore preserves this incompatibility. This is an injected-transport reproduction, not a claim that firmware 01.39 was observed sending decimal replies on hardware. Please support the documented format alongside any independently captured variant and add a model regression proving that a valid decimal reply completes the poll without disconnecting.

Nits — non-blocking

  • Update the title: it still advertises the removed TX Controls integration.
  • Remove the three unused overlay screenshots: kxpa100-tx-auto.png, kxpa100-tx-disconnected.png, and kxpa100-tx-manual.png.

Verification and attacks

  • Reviewed head 982ce6019680cc28d58b2f16f02b06392fa7c4d0, stacked base d0f2dfe8b5edea6fe7baaa8d1592137413b86826, and merge 1bfef7e53ae4a9cfeba96d11bc542d3c6a5d5d79. The merge tree equals the tested head, which includes main 86f592a9f5303e3581d514d9ceb8a7dead9a3235.
  • Full native macOS ARM64 app build passed with RADE enabled; host/system processors and final executable verified ARM64, with no RNNoise x86 sources. macOS icon generation needed normal host access; explicit Qt 6.12 runtime paths avoided the host's mixed-framework launch issue. These are excluded from PR findings.
  • All six focused KXPA100 tests passed: protocol, maintenance, model, applet, Advanced widget, and TX-controls/association. Preflight found no socket/serial-opening test: invalid-endpoint refusals and transport/state injection only.
  • The model tests survived timeout, identity-change, RF-guard and interrupted-restore scenarios. Removing the TCP receive-buffer cap made the supplied test fail. Association refusal, optional-tuner absence and shared-meter precedence tests passed.
  • All four architecture checks passed on both base and head; engine-boundary findings were unchanged after normalizing shifted line numbers. No separate automated review skill was available; prior bot feedback was checked against this revision, followed by a second manual pass.
  • Drove the built app offscreen with isolated settings, an explicit socket and TX pinned off. Connected only to DEMO-0001; the bridge reported model: "AetherSDR Demo", one slice/pan, and transmitting: false. ATU/MEM retained their radio-side labels and were disabled for the simulator. Adding the disconnected KXPA100 entry and opening Advanced left read/restore/erase/reset disabled with Connect the KXPA100 first. The review instance was closed and its exit verified.
  • Current static/docs/sanitizer-configuration checks passed; release PDF attachment was skipped. All 20 commit signatures verified valid. Full platform build/test CI has not run for this stacked target and remains required after dependency landing/retargeting. Actual KXPA100 communication, EEPROM effects and Linux/Windows execution remain unverified; the demo cannot establish external-device convergence.

Recommendation: Request changes for the SWR compatibility issue, then rerun focused coverage and obtain the full platform CI before merge. No live radio connection, RF transmission or device maintenance write was performed during this review.

Comment thread src/core/Kxpa100Protocol.cpp Outdated
@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 9 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 13
Files with findings: 1
Total findings : 9

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@Ozy311 Ozy311 changed the title feat(kxpa100): add combined amplifier/tuner applet and TX Controls integration feat(kxpa100): add amplifier and optional tuner applet Oct 15, 2026
@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 9 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 13
Files with findings: 1
Total findings : 9

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

1 similar comment
@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 9 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 13
Files with findings: 1
Total findings : 9

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@jensenpat jensenpat self-assigned this Oct 17, 2026
@jensenpat
jensenpat force-pushed the codex/kat500-applet branch from 0113445 to e83398b Compare October 17, 2026 21:44
Base automatically changed from codex/kat500-applet to main October 17, 2026 22:14
Ozy311 and others added 2 commits October 17, 2026 15:14
Rebase the final KXPA100-only delta from 969a586 onto the reviewed KAT500 integration, retaining documented decimal SWR handling and main KPA500 telemetry freshness admission.
@jensenpat
jensenpat force-pushed the codex/kxpa100-applet branch from 969a586 to 220f872 Compare October 17, 2026 22:15
@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 9 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 13
Files with findings: 1
Total findings : 9

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@jensenpat
jensenpat dismissed rfoust’s stale review October 17, 2026 22:17

Documented decimal SWR finding fixed in final main-target head 220f872, carrying the 9a6f154 parser/scheduler fix. kxpa100_protocol_test and Kxpa100ModelTest::swrPolling pass on this head; disabling decimal handling in identical sources failed both regressions before rebase. All 10 selected headless tests and native ARM64 app build pass. The resolved thread has the detailed evidence. Title and unused overlay PNG nits are addressed. This dismissal retires the fixed finding; merge still awaits current platform/Docs CI.

@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 final main-target head 220f872, based on merged KAT500 main 450f7b3. No outstanding code blocker.

The prior documented-decimal SWR defect is fixed: protocol and production-scheduler tests cover decimal/zero/compact replies, fragmented delivery, clearing the active request and continued polling beyond its original timeout. Both tests failed when decimal handling was disabled in the identical pre-rebase parser source, passed after restoration, and pass on this final head. The title, obsolete overlay assets and feature-issue linkage are corrected. The old changes-requested review was dismissed with this evidence and its thread remains answered/resolved.

The rebase preserves main’s KPA500 hasLiveTxMeters admission and radio-integrated/KPA500/KEY BLOCK/KXPA100 meter precedence. No external TX Controls takeover remains. Audited all 14 test targets that compile the Peripherals page; each includes both Advanced widget sources. The previously missing operations-test dependency is repaired in 220f872.

Native macOS ARM64 app build passed. All 10 selected offscreen CTests pass: six KXPA100 tests, KAT500 meter selection, Radio Setup pair grid, TX power reconciliation and Radio Setup operations. Four architecture gates pass on main and head with unchanged normalized findings; generated references, Log Analyzer, registration and frozen gate checks pass. The actual 13-file GUI diff has the same nine legacy accessibility warnings as its base, with no new finding. The optional automated code-review skill was unavailable; manual parser/lifecycle/maintenance and failure-path review was performed.

Final-head bridge proof used fresh isolated settings and TX pinned off, connected only to DEMO-0001: one slice/pan, transmitting=false, native ATU/MEM; disconnected Advanced read/restore/erase/reset controls carry the accessible reason “Connect the KXPA100 first.” The owned app exited. No live radio, RF or EEPROM write was performed. Real-device convergence, physical persistence and cross-platform runtime are unverified by this pass. Hosted main-target platform/Docs checks must pass before merge.

@jensenpat
jensenpat enabled auto-merge (squash) October 17, 2026 22:20
@jensenpat
jensenpat merged commit 2c5f684 into main Oct 17, 2026
11 of 17 checks passed
auto-merge was automatically disabled October 17, 2026 22:47

Pull Request is not mergeable

@jensenpat
jensenpat deleted the codex/kxpa100-applet branch October 17, 2026 22:47
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.

Add Elecraft KXPA100 amplifier and optional KXAT100 tuner support

3 participants