fix(kpa): its own card, and no band-follow it does not need
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.
This commit is contained in:
@@ -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 (
|
||||
<Card icon={Flame} ckey="amplifier" title={`${t('flxp.amplifier')} · ${amp.name || `ACOM${acom.model ? ' ' + acom.model : ''}`}`} accent="#ea580c">
|
||||
<Card icon={Flame} ckey="amplifier" title={`${t('flxp.amplifier')} · ${amp.name || `Acom${acom.model ? ' ' + acom.model : ''}`}`} accent="#ea580c">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<button type="button" disabled={!acom.connected}
|
||||
onClick={() => AmpOperate(amp.id, !acom.operate).catch(() => {})}
|
||||
@@ -168,6 +169,53 @@ export function AmpCard({ amp, flex, t }: { amp: Amp; flex: any; t: (k: string,
|
||||
);
|
||||
}
|
||||
|
||||
if (isKPA) {
|
||||
const kpa = amp.kpa;
|
||||
return (
|
||||
<Card icon={Flame} ckey="amplifier" title={`${t('flxp.amplifier')} · ${amp.name || kpa.model || 'Elecraft'}`} accent="#ea580c">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<button type="button" disabled={!kpa.connected}
|
||||
onClick={() => AmpOperate(amp.id, !kpa.operate).catch(() => {})}
|
||||
title={kpa.fault_text ? t('flxp.kpaClearsFault') : undefined}
|
||||
className={cn('px-4 py-2 rounded-lg text-sm font-extrabold tracking-wide border-2 transition-all disabled:opacity-30',
|
||||
kpa.operate ? 'bg-warning text-warning-foreground border-warning shadow-[0_0_14px] shadow-warning/50' : 'bg-card text-warning border-warning hover:bg-warning-muted')}>
|
||||
{kpa.operate ? 'OPERATE' : 'STANDBY'}
|
||||
</button>
|
||||
{/* 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. */}
|
||||
<div className="inline-flex rounded-lg overflow-hidden border-2 border-success/70">
|
||||
<button type="button" disabled={!kpa.connected}
|
||||
onClick={() => AmpPower(amp.id, true).catch(() => {})}
|
||||
className="px-3 py-2 text-sm font-bold bg-card text-success hover:bg-success/15 disabled:opacity-30">ON</button>
|
||||
<button type="button" disabled={!kpa.connected}
|
||||
onClick={() => AmpPower(amp.id, false).catch(() => {})}
|
||||
className="px-3 py-2 text-sm font-bold bg-card text-danger border-l-2 border-success/70 hover:bg-danger/15 disabled:opacity-30">OFF</button>
|
||||
</div>
|
||||
<span className={cn('inline-flex items-center gap-1.5 text-sm', kpa.connected ? 'text-muted-foreground' : 'text-danger')}>
|
||||
<span className={cn('size-2 rounded-full', kpa.connected ? 'bg-success' : 'bg-danger')} />
|
||||
{kpa.connected ? (kpa.tuning ? t('flxp.kpaTuning') : (kpa.power_on ? 'ON' : 'OFF')) : t('flxp.acomOffline')}
|
||||
</span>
|
||||
{kpa.connected && (
|
||||
<span className="text-sm font-mono text-muted-foreground tabular-nums">
|
||||
{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
|
||||
</span>
|
||||
)}
|
||||
<div className="flex-1" />
|
||||
{kpa.fault_text && (
|
||||
<span className="px-2 py-1 rounded bg-danger-muted text-danger-muted-foreground text-xs font-bold">⚠ {kpa.fault_text}</span>
|
||||
)}
|
||||
</div>
|
||||
{kpa.connected && (
|
||||
<MeterBar label={t('flxp.outputPower')} value={Number(kpa.fwd_w) || 0} unit="W"
|
||||
lo={0} hi={String(kpa.model).includes('500') ? 500 : 1500}
|
||||
display={`${Number(kpa.fwd_w) || 0} W`}
|
||||
segColor={(f) => (f > 0.9 ? '#dc2626' : f > 0.75 ? '#f59e0b' : '#ea580c')} />
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// PowerGenius XL — OPERATE + meters ride on the Flex; fan mode on the GSCP link.
|
||||
const pg = amp.pgxl || {};
|
||||
const viaFlex = !!flex?.amp_available;
|
||||
|
||||
@@ -124,7 +124,7 @@ function AmpBlock({ amp, flex, showName, t }: {
|
||||
{showName && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className={cn('size-1.5 rounded-full shrink-0', s.connected ? 'bg-success shadow-[0_0_6px_rgba(16,185,129,0.8)]' : 'bg-danger')} />
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider truncate">{amp.name || (spe ? 'SPE' : kpaAmp ? (s.model || 'KPA') : 'ACOM')}</span>
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider truncate">{amp.name || (spe ? 'SPE' : kpaAmp ? (s.model || 'KPA') : 'Acom')}</span>
|
||||
{s.connected && s.band && <span className="ml-auto text-[9px] text-muted-foreground shrink-0">{s.band}</span>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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 (
|
||||
<div key={amp.id || `new-${i}`} className="rounded-lg border border-border p-3 space-y-3">
|
||||
@@ -4223,7 +4224,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
<SelectContent>
|
||||
<SelectItem value="pgxl">4O3A</SelectItem>
|
||||
<SelectItem value="spe">SPE</SelectItem>
|
||||
<SelectItem value="acom">ACOM</SelectItem>
|
||||
<SelectItem value="acom">Acom</SelectItem>
|
||||
<SelectItem value="kpa">Elecraft</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -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 && (
|
||||
<div className="border-t border-border/60 pt-3 space-y-3">
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox
|
||||
|
||||
Reference in New Issue
Block a user