feat(kpa): the Elecraft amplifiers in the Settings, the card and the widget
Wired into the multi-amplifier support that already carries the SPE, the
ACOM and the PowerGenius: a fourth brand rather than a fourth mechanism.
The linking, the fan-out and the poll all work on it unchanged.
Three things the KPA does differently, and each is handled where it shows
rather than explained in a hint:
- A KPA500 has no network side at all. Choosing it puts the entry on
serial, and a configuration still asking for TCP is refused with a
line saying so instead of retrying an address that cannot answer.
- The amplifier answers ^ON while its main supplies are OFF — a sleeping
microcontroller stays awake for exactly that — so power-on stays
available over the network, unlike the SPE and ACOM which need their
serial control lines.
- Going to OPERATE clears the current fault, everything except
temperature. The widget says so under the fault, because the button
that fixes it is the one already on screen.
Defaults that leave a working configuration when the model is changed:
38400 baud, TCP port 1500, and a full-scale power mark of 500 or 1500 W
by model — a KPA500 read against a 1500 W scale looks idle at full
output.
This commit is contained in:
@@ -15,7 +15,7 @@ import { AmpOperate, AmpPower, AmpPowerLevel, AmpFanMode, FlexAmpOperate } from
|
||||
// With several amplifiers configured the caller passes a selection ("all" or an
|
||||
// amp id) chosen from the toolbar icon's dropdown.
|
||||
|
||||
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 };
|
||||
|
||||
// Full-scale watts for the output bar, from the model string.
|
||||
function maxW(model?: string, fallback = 1300): number {
|
||||
@@ -92,27 +92,39 @@ function OperateButton({ operate, disabled, onClick, t }: {
|
||||
function AmpBlock({ amp, flex, showName, t }: {
|
||||
amp: Amp; flex: any; showName: boolean; t: (k: string, v?: any) => string;
|
||||
}) {
|
||||
const spe = amp.spe, acom = amp.acom;
|
||||
const spe = amp.spe, acom = amp.acom, kpaAmp = amp.kpa;
|
||||
const hold = usePeakHold();
|
||||
|
||||
if (spe || acom) {
|
||||
const s = spe || acom;
|
||||
// One block for the three amplifiers OpsLog drives over its own link. They
|
||||
// report the same handful of things under different names, so the differences
|
||||
// are named here rather than spread through the markup.
|
||||
if (spe || acom || kpaAmp) {
|
||||
const s = spe || acom || kpaAmp;
|
||||
// The amp reports zero watts on receive, so its TX flag clears the meter as
|
||||
// soon as the operator lets go — and the radio's own flag, when we have one,
|
||||
// gets there first (the amp is polled on its own slower cycle).
|
||||
const txing = typeof flex?.transmitting === 'boolean' ? flex.transmitting : s.tx !== false;
|
||||
const w = hold('w', Number(spe ? s.output_w : s.fwd_w) || 0, txing);
|
||||
const swr = Number(spe ? s.swr_ant : s.swr) || 0;
|
||||
const hi = spe ? maxW(s.model) : (Number(s.max_w) || 800);
|
||||
// The full-scale mark. A KPA500 reading against a 1500 W scale would look
|
||||
// idle at full output, so the model decides it.
|
||||
const hi = spe ? maxW(s.model)
|
||||
: kpaAmp ? (String(s.model).includes('500') ? 500 : 1500)
|
||||
: (Number(s.max_w) || 800);
|
||||
// Power ON drives the remote-on control lines, so it stays available while
|
||||
// the amplifier is off and reporting nothing — but only over a serial link.
|
||||
const canPowerOn = spe ? (s.connected || s.transport === 'serial') : (s.port_open && s.transport === 'serial');
|
||||
// A KPA answers ^ON while its main supplies are off — the sleeping
|
||||
// microcontroller stays awake for exactly that — so power-on is available
|
||||
// whenever the link itself is up, over serial and over the network alike.
|
||||
const canPowerOn = spe ? (s.connected || s.transport === 'serial')
|
||||
: kpaAmp ? !!s.connected
|
||||
: (s.port_open && s.transport === 'serial');
|
||||
return (
|
||||
<div className="h-full flex flex-col gap-1.5">
|
||||
{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' : '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>
|
||||
)}
|
||||
@@ -155,11 +167,17 @@ function AmpBlock({ amp, flex, showName, t }: {
|
||||
) : (
|
||||
<div className="text-[10px] text-center text-muted-foreground italic py-1">{t('ampw.offline')}</div>
|
||||
)}
|
||||
{(s.warnings || s.alarms || s.err_text) && (
|
||||
{(s.warnings || s.alarms || s.err_text || s.fault_text) && (
|
||||
<div className="rounded-md border border-danger-border bg-danger-muted text-danger-muted-foreground text-[9px] px-1.5 py-0.5 text-center break-words">
|
||||
{s.err_text || `${s.warnings || ''} ${s.alarms || ''}`.trim()}
|
||||
{s.fault_text || s.err_text || `${s.warnings || ''} ${s.alarms || ''}`.trim()}
|
||||
</div>
|
||||
)}
|
||||
{/* Said where the fault is read, because it is the way out of it: on a
|
||||
KPA, going to OPERATE clears the current fault — everything except
|
||||
temperature, which clears by cooling. */}
|
||||
{kpaAmp && s.fault_text && (
|
||||
<div className="text-[9px] text-center text-muted-foreground">{t('ampw.kpaClear')}</div>
|
||||
)}
|
||||
<PowerMeter label={t('flxp.outputPower')} watts={s.connected ? w : 0} maxWatts={hi} />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user