import { useRef, useState } from 'react'; import { Flame, ChevronDown } from 'lucide-react'; import { cn } from '@/lib/utils'; import { MeterBar } from '@/components/MeterBar'; import { AmpOperate, AmpPower, AmpPowerLevel, AmpFanMode, FlexAmpOperate } from '../../wailsjs/go/main/App'; // AmpCard renders the amplifier card exactly like the one in the FlexRadio panel, // so Station Control (and anywhere else that needs it) shows the SAME card instead // of a stripped-down variant. It handles all three amp families: // • SPE Expert / ACOM — driven by OpsLog's own serial/TCP link (amp.spe/amp.acom) // • PowerGenius XL — OPERATE + meters come from the Flex (which reports the // amp), fan mode from the direct GSCP link (amp.pgxl) // Controls use the multi-amp API (AmpOperate/AmpPower/… by amp id) so several amps // can each get their own card. function Card({ icon: Icon, title, accent, children, ckey }: { icon: any; title: string; accent?: string; children: React.ReactNode; ckey?: string }) { // Collapsible with persisted state — same behaviour as the FlexRadio panel's Card. const storeKey = 'opslog.cardOpen.' + (ckey || title); const [open, setOpen] = useState(() => localStorage.getItem(storeKey) !== '0'); const toggle = () => setOpen((o) => { const n = !o; localStorage.setItem(storeKey, n ? '1' : '0'); return n; }); return (
{open &&
{children}
}
); } // speMaxW / powerLevelLabel — same helpers the Flex panel uses. function speMaxW(model?: string): number { const m = (model || '').toUpperCase(); if (m.includes('20K') || m.includes('2K')) return 2000; if (m.includes('15K')) return 1500; return 1300; } function powerLevelLabel(pl?: string): string { switch ((pl || '').trim().toUpperCase()) { case 'L': return 'Low'; case 'M': return 'Mid'; case 'H': return 'High'; default: return pl || ''; } } type Amp = { id: string; name: string; type?: string; spe?: any; acom?: 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). // One second: the same window as the docked widget, so the two never show // different figures for the same amplifier, and neither trails the end of a // transmission long enough to look like lag. const peak = useRef>({}); const peakHold = (key: string, val: number) => { const now = Date.now(); const p = peak.current[key]; if (!p || val >= p.v || now - p.t > 1000) { peak.current[key] = { v: val, t: now }; return val; } return p.v; }; const isSPE = !!amp.spe; const isACOM = !!amp.acom; if (isSPE) { const spe = amp.spe; return (
{/* Power ON pulses RTS then DTR — it must stay clickable while the amp is off (no status = not "connected"), and serial only. */}
{(['L', 'M', 'H'] as const).map((lvl, i) => { const active = (spe.power_level || '').trim().toUpperCase() === lvl; return ( ); })}
{spe.connected ? (spe.tx ? 'TX' : 'RX') : t('flxp.speOffline')} {spe.connected && ( {spe.band ? `${spe.band} · ` : ''}{spe.output_w}W · SWR {Number(spe.swr_ant ?? 0).toFixed(1)} · {spe.temp_c}°C · {powerLevelLabel(spe.power_level)} )}
{(spe.warnings || spe.alarms) && ( ⚠ {spe.warnings} {spe.alarms} )}
{spe.connected && ( (f > 0.9 ? '#dc2626' : f > 0.75 ? '#f59e0b' : '#ea580c')} /> )} ); } if (isACOM) { const acom = amp.acom; return (
{acom.connected ? acom.state : t('flxp.acomOffline')} {acom.connected && ( {acom.band ? `${acom.band} · ` : ''}{acom.fwd_w}W · SWR {Number(acom.swr ?? 0).toFixed(1)} · {acom.temp_c}°C · Fan {acom.fan} )}
{acom.err_text && ( ⚠ {acom.err_text} )}
{acom.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; const operate = viaFlex ? !!flex?.amp_operate : !!pg.operate; const connected = !!pg.connected || viaFlex; const fault = flex?.amp_fault; return (
{(pg.host || pg.connected) && ( )}
{fault && fault !== 'NONE' && ( {t('flxp.fault')}: {fault} )}
{/* Amplifier meters (FWD / ID / TEMP …) from the FlexRadio UDP stream. */} {viaFlex && (() => { const meters = (flex?.meters as any[]) || []; const dbmToW = (d: number) => Math.pow(10, (d - 30) / 10); const amps = meters.filter((m) => (m.src || '').toUpperCase().includes('AMP') && !/^(RL|DRV)$/i.test((m.name || '').trim())); if (amps.length === 0) return null; // Power comes from the radio's meter stream and nothing else. The // amplifier also reports a "peakfwd", and using it was a mistake twice // over: it is a latched maximum that is never reset, and it survives in // the last-known status after the amp disconnects — so it claimed // 1350 W from an old transmission while the radio was putting out 10. return (
{amps.map((m) => { if (/fwd|pwr/i.test(m.name || '') && /dbm/i.test(m.unit || '')) { return ; } const acc = /temp|degc|degf/i.test(`${m.unit}${m.name}`) ? '#ea580c' : /volt/i.test(m.unit || '') ? '#2563eb' : '#16a34a'; let lo = m.lo, hi = m.hi; if (/amp/i.test(m.unit || '') || /^ID$|current/i.test(m.name || '')) { lo = 0; hi = m.hi >= 25 ? m.hi : 25; } return ; })}
); })()} ); }