From de95a437bbf0352639455221e6b874e51fc3b064 Mon Sep 17 00:00:00 2001 From: rouggy Date: Wed, 26 Aug 2026 18:00:46 +0200 Subject: [PATCH] fix(kpa): its own card, and no band-follow it does not need MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things the wiring got wrong, both visible on screen. A KPA fell through to the PowerGenius branch of the amplifier card and was drawn as one — titled 'POWERGENIUSXL · ELECRAFT KPA1500', with a PowerGenius's meters and none of its own. It has a card now: OPERATE, ON and OFF, band, power, SWR, temperature, supply, and the fault across the end. The OPERATE button carries a tooltip saying it also clears the fault, because that is the button an operator already has under the cursor when they need it. And the band-follow option was offered on it. That option exists because an Acom POLLS a transceiver and has no command to be given a band, so following it needs a second serial port and a rig emulator answering those polls. The KPA has ^BN — OpsLog simply tells it, on the link it is already using, and only when the band CHANGES. The option is gone from the KPA and the telling is automatic. Also: Acom rather than ACOM everywhere it is read, which is how the company writes it. Identifiers, package names and log lines are left alone — renaming those is churn with no reader. --- app.go | 17 ++++++-- frontend/package.json.md5 | 2 +- frontend/src/components/AmpCard.tsx | 52 ++++++++++++++++++++++- frontend/src/components/AmpWidget.tsx | 2 +- frontend/src/components/SettingsModal.tsx | 14 ++++-- frontend/src/lib/i18n.tsx | 8 ++-- internal/kpa/kpa.go | 38 +++++++++++++++++ 7 files changed, 118 insertions(+), 15 deletions(-) diff --git a/app.go b/app.go index 0ec1bc0..743cdda 100644 --- a/app.go +++ b/app.go @@ -18106,7 +18106,7 @@ func ampTypeLabel(t string) string { case strings.HasPrefix(t, "spe"): return "SPE " + map[string]string{"spe13": "1.3K-FA", "spe15": "1.5K-FA", "spe2k": "2K-FA"}[t] case strings.HasPrefix(t, "acom"): - return "ACOM " + strings.TrimPrefix(t, "acom") + "S" + return "Acom " + strings.TrimPrefix(t, "acom") + "S" case strings.HasPrefix(t, "kpa"): return "Elecraft " + strings.ToUpper(t) } @@ -18278,6 +18278,17 @@ func (a *App) feedAmpBandFollow(s cat.RigState) { inst.catemu.SetFrequency(s.FreqHz) inst.catemu.SetMode(s.Mode) } + // A KPA is TOLD its band, on the link it is already on. + // + // The emulator above exists because an Acom polls a transceiver and has + // no command to be given a band; the KPA has one (^BN), so it needs + // neither a second serial port nor a pretend rig. Sent from a goroutine + // because this runs on the CAT state-change path, and nothing about the + // rig should wait on an amplifier's link. + if inst.kpa != nil && s.Band != "" { + k, band := inst.kpa, s.Band + go func() { _ = k.SetBand(band) }() + } } } @@ -18553,7 +18564,7 @@ func (a *App) GetACOMStatus() acom.Status { // protocol has explicit commands for each, unlike the SPE's toggle key. func (a *App) ACOMSetOperate(on bool) error { if a.acom == nil { - return fmt.Errorf("ACOM amplifier not connected — enable it in Settings → Amplifier") + return fmt.Errorf("Acom amplifier not connected — enable it in Settings → Amplifier") } return a.acom.Operate(on) } @@ -18562,7 +18573,7 @@ func (a *App) ACOMSetOperate(on bool) error { // the power-on pins wired in the cable) or off (false, the OFF data command). func (a *App) ACOMSetPower(on bool) error { if a.acom == nil { - return fmt.Errorf("ACOM amplifier not connected — enable it in Settings → Amplifier") + return fmt.Errorf("Acom amplifier not connected — enable it in Settings → Amplifier") } if on { return a.acom.PowerOn() diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 693b40b..b826f3b 100644 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -f9b41e192918fa2511f68cd1b361fcd3 \ No newline at end of file +704fe1bf370b669665df0606fae8a69d \ No newline at end of file diff --git a/frontend/src/components/AmpCard.tsx b/frontend/src/components/AmpCard.tsx index a02e642..ff2893b 100644 --- a/frontend/src/components/AmpCard.tsx +++ b/frontend/src/components/AmpCard.tsx @@ -47,7 +47,7 @@ function powerLevelLabel(pl?: string): string { } } -type Amp = { id: string; name: string; type?: string; spe?: any; acom?: any; pgxl?: any }; +type Amp = { id: string; name: string; type?: string; spe?: any; acom?: any; kpa?: any; pgxl?: any }; export function AmpCard({ amp, flex, t }: { amp: Amp; flex: any; t: (k: string, v?: any) => string }) { // Peak-hold so the jittery VITA-49 meters read steadily (own ref per card). @@ -64,6 +64,7 @@ export function AmpCard({ amp, flex, t }: { amp: Amp; flex: any; t: (k: string, const isSPE = !!amp.spe; const isACOM = !!amp.acom; + const isKPA = !!amp.kpa; if (isSPE) { const spe = amp.spe; @@ -127,7 +128,7 @@ export function AmpCard({ amp, flex, t }: { amp: Amp; flex: any; t: (k: string, if (isACOM) { const acom = amp.acom; return ( - +
+ {/* The amplifier answers while its main supplies are off — a sleeping + microcontroller stays awake for exactly that — so ON is offered + over the network as well, unlike the SPE and Acom. */} +
+ + +
+ + + {kpa.connected ? (kpa.tuning ? t('flxp.kpaTuning') : (kpa.power_on ? 'ON' : 'OFF')) : t('flxp.acomOffline')} + + {kpa.connected && ( + + {kpa.band ? `${kpa.band} · ` : ''}{kpa.fwd_w}W · SWR {Number(kpa.swr ?? 0).toFixed(1)} · {kpa.temp_c}°C · {kpa.volt_v}V {kpa.cur_a}A + + )} +
+ {kpa.fault_text && ( + ⚠ {kpa.fault_text} + )} +
+ {kpa.connected && ( + (f > 0.9 ? '#dc2626' : f > 0.75 ? '#f59e0b' : '#ea580c')} /> + )} + + ); + } + // PowerGenius XL — OPERATE + meters ride on the Flex; fan mode on the GSCP link. const pg = amp.pgxl || {}; const viaFlex = !!flex?.amp_available; diff --git a/frontend/src/components/AmpWidget.tsx b/frontend/src/components/AmpWidget.tsx index 94563a9..1721ceb 100644 --- a/frontend/src/components/AmpWidget.tsx +++ b/frontend/src/components/AmpWidget.tsx @@ -124,7 +124,7 @@ function AmpBlock({ amp, flex, showName, t }: { {showName && (
- {amp.name || (spe ? 'SPE' : kpaAmp ? (s.model || 'KPA') : 'ACOM')} + {amp.name || (spe ? 'SPE' : kpaAmp ? (s.model || 'KPA') : 'Acom')} {s.connected && s.band && {s.band}}
)} diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index dab807c..d7a168a 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -4196,6 +4196,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged const brand = brandOf(amp.type); const isPGXL = brand === 'pgxl'; const isACOM = brand === 'acom'; + const isKPA = brand === 'kpa'; const isSerial = !isPGXL && amp.transport === 'serial'; return (
@@ -4223,7 +4224,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged 4O3A SPE - ACOM + Acom Elecraft @@ -4314,10 +4315,15 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged )} {/* Band-follow, for any amp that takes its band from a transceiver - CAT link (ACOM and SPE both do) — never PowerGenius, which is + CAT link (Acom and SPE both do) — never PowerGenius, which is driven over its network protocol. A SECOND serial port, - separate from the metering one above. */} - {!isPGXL && ( + separate from the metering one above. + Never a KPA either: it has a band command of its own (^BN), + so OpsLog tells it directly on the link it is already using. + Offering a second serial port and a transceiver emulator for + that would be a workaround for a problem this amplifier does + not have. */} + {!isPGXL && !isKPA && (