import { useRef } from 'react'; import { Flame } from 'lucide-react'; import { cn } from '@/lib/utils'; 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. const METER_SEGMENTS = 26; function MeterBar({ label, value, unit, lo, hi, accent = '#16a34a', display, segColor, compact }: { label: string; value: number; unit?: string; lo: number; hi: number; accent?: string; display?: string; segColor?: (frac: number) => string; compact?: boolean; }) { const span = hi - lo; const pct = span > 0 ? Math.max(0, Math.min(100, ((value - lo) / span) * 100)) : 0; const lit = Math.round((pct / 100) * METER_SEGMENTS); return (
{label} {display !== undefined ? display : ( <>{Math.abs(value) >= 100 ? value.toFixed(0) : value.toFixed(1)}{unit} )}
{Array.from({ length: METER_SEGMENTS }).map((_, i) => { const on = i < lit; const frac = i / METER_SEGMENTS; const col = segColor ? segColor(frac) : (frac > 0.82 ? '#dc2626' : accent); return (
); })}
); } function Card({ icon: Icon, title, accent, children }: { icon: any; title: string; accent?: string; children: React.ReactNode }) { return (
{title}
{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). 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 > 2000) { 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 (
{(['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 (
{operate ? t('flxp.ampInLine') : t('flxp.ampBypassed')} {(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; 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 ; })}
); })()} ); }