feat: multiple amplifiers + SmartSDR v4 DSP filters
Multi-amp: Settings->Amplifier becomes a list (amps.json, legacy single-amp keys auto-migrate to entry #1); one client per enabled amp with per-id bindings (GetAmplifiers/SaveAmplifiers/GetAmpStatuses/AmpOperate/AmpPower/AmpPowerLevel/AmpFanMode); legacy a.pgxl/a.spe/a.acom point at the first enabled amp of each family. Amp cards in FlexPanel and Station Control gain a dropdown to pick the amp; the status bar shows one clickable chip per amp. Use case: two SPEs run in parallel. Flex v4 DSP (8000/Aurora): NRL/ANFL (lms_nr/lms_anf), NRS (speex_nr), NRF (nrf) with level sliders, RNN (rnnoise) and ANFT on/off — keys per the FlexLib slice docs; the section only shows when the radio reports these keys (dsp_v4 flag), so 6000-series panels are unchanged.
This commit is contained in:
+34
-56
@@ -43,7 +43,7 @@ import {
|
||||
GetAwardDefs,
|
||||
GetUIPref, GetActiveProfile, QuitApp,
|
||||
ReportLiveActivity, LiveLastQSOAgeSec,
|
||||
GetPGXLSettings, GetPGXLStatus, GetSPEStatus, GetACOMStatus, SPESetOperate, ACOMSetOperate, PGXLSetOperate,
|
||||
GetAmpStatuses, AmpOperate,
|
||||
GetFlexState, FlexAmpOperate,
|
||||
} from '../wailsjs/go/main/App';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
@@ -441,45 +441,22 @@ export default function App() {
|
||||
// click reverts the UI and the click looks like it did nothing.
|
||||
const agPending = useRef<{ a?: { v: number; t: number }; b?: { v: number; t: number } }>({});
|
||||
const [dbConn, setDbConn] = useState<{ backend: string; label: string } | null>(null);
|
||||
// Amplifier chip in the status bar: name + green OPERATE / orange STANDBY /
|
||||
// red offline. Config re-read every 5s (so enabling the amp in Settings makes
|
||||
// the chip appear); status polled every 2s while enabled.
|
||||
const [ampCfg, setAmpCfg] = useState<{ enabled: boolean; type: string }>({ enabled: false, type: 'pgxl' });
|
||||
const [ampSt, setAmpSt] = useState<any>({ connected: false });
|
||||
// Amplifier chips in the status bar — ONE PER configured amp (several are
|
||||
// possible: some ops run two SPEs in parallel). Green OPERATE / orange
|
||||
// STANDBY / red offline; click toggles. The Flex state rides along because a
|
||||
// PGXL's OPERATE state comes from the radio when it reports the amp.
|
||||
const [ampSts, setAmpSts] = useState<any[]>([]);
|
||||
const [flexAmp, setFlexAmp] = useState<any>(null);
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
const load = () => GetPGXLSettings().then((s: any) => { if (alive) setAmpCfg({ enabled: !!s?.enabled, type: s?.type || 'pgxl' }); }).catch(() => {});
|
||||
load();
|
||||
const id = window.setInterval(load, 5000);
|
||||
return () => { alive = false; window.clearInterval(id); };
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!ampCfg.enabled) { setAmpSt({ connected: false }); return; }
|
||||
let alive = true;
|
||||
// PGXL: the Flex reports the amp's OPERATE state authoritatively (the direct
|
||||
// GSCP status doesn't carry it), so merge GetFlexState in when a Flex sees
|
||||
// the amp — state AND toggle then go through the radio; the direct TCP link
|
||||
// remains the fallback (fan mode always uses it).
|
||||
const tick = ampCfg.type === 'pgxl'
|
||||
? () => Promise.all([
|
||||
GetPGXLStatus().catch(() => ({ connected: false })),
|
||||
GetFlexState().catch(() => null),
|
||||
]).then(([pg, fx]: any[]) => {
|
||||
if (!alive) return;
|
||||
const viaFlex = !!fx?.amp_available;
|
||||
setAmpSt({
|
||||
...(pg || {}),
|
||||
connected: !!pg?.connected || viaFlex,
|
||||
operate: viaFlex ? !!fx.amp_operate : !!pg?.operate,
|
||||
via_flex: viaFlex,
|
||||
});
|
||||
})
|
||||
: () => (ampCfg.type.startsWith('acom') ? GetACOMStatus : GetSPEStatus)()
|
||||
.then((s: any) => alive && setAmpSt(s || { connected: false })).catch(() => {});
|
||||
const tick = () => Promise.all([
|
||||
GetAmpStatuses().catch(() => []),
|
||||
GetFlexState().catch(() => null),
|
||||
]).then(([l, fx]: any[]) => { if (alive) { setAmpSts((l ?? []) as any[]); setFlexAmp(fx); } });
|
||||
tick();
|
||||
const id = window.setInterval(tick, 2000);
|
||||
return () => { alive = false; window.clearInterval(id); };
|
||||
}, [ampCfg.enabled, ampCfg.type]);
|
||||
}, []);
|
||||
// Multi-op "who's on air" widget: every operator's live status from the shared
|
||||
// MySQL logbook (freq/mode/version). Only polled on a MySQL logbook.
|
||||
type LiveStation = { operator: string; station: string; freq_hz: number; band: string; mode: string; online: boolean; version: string; age_sec: number };
|
||||
@@ -5188,38 +5165,39 @@ export default function App() {
|
||||
disabled={!rotatorHeading.enabled}
|
||||
onClick={() => { setSettingsSection('rotator'); setShowSettings(true); }}
|
||||
/>
|
||||
{/* Amplifier chip: green = OPERATE, orange = STANDBY, red = offline.
|
||||
CLICK toggles OPERATE ↔ STANDBY on every backend (PGXL included —
|
||||
its direct TCP link takes operate=0/1); optimistic flip, the 2s
|
||||
poll reconciles. Offline → click opens the settings instead. */}
|
||||
{ampCfg.enabled && (() => {
|
||||
const isPGXL = ampCfg.type === 'pgxl';
|
||||
const name = isPGXL ? 'PGXL'
|
||||
: ampCfg.type.startsWith('acom') ? `ACOM ${ampSt.model || ''}`.trim()
|
||||
: `SPE ${ampSt.model || ''}`.trim();
|
||||
const dot = !ampSt.connected ? 'bg-danger' : ampSt.operate ? 'bg-success' : 'bg-warning';
|
||||
const state = !ampSt.connected ? (ampSt.last_error || 'offline') : ampSt.operate ? 'OPERATE' : 'STANDBY';
|
||||
{/* Amplifier chips — one per configured amp: green = OPERATE, orange =
|
||||
STANDBY, red = offline. CLICK toggles OPERATE ↔ STANDBY (optimistic
|
||||
flip, the 2s poll reconciles); offline → click opens the settings. */}
|
||||
{ampSts.map((a: any) => {
|
||||
const isPGXL = !a.spe && !a.acom;
|
||||
const viaFlex = isPGXL && !!flexAmp?.amp_available;
|
||||
const raw = a.spe ?? a.acom ?? a.pgxl ?? { connected: false };
|
||||
const connected = !!raw.connected || viaFlex;
|
||||
const operate = viaFlex ? !!flexAmp.amp_operate : !!raw.operate;
|
||||
const dot = !connected ? 'bg-danger' : operate ? 'bg-success' : 'bg-warning';
|
||||
const state = !connected ? (raw.last_error || 'offline') : operate ? 'OPERATE' : 'STANDBY';
|
||||
const toggle = () => {
|
||||
// Offline — nothing to command; open the settings to fix the link.
|
||||
if (!ampSt.connected) { setSettingsSection('pgxl'); setShowSettings(true); return; }
|
||||
const want = !ampSt.operate;
|
||||
setAmpSt((s: any) => ({ ...s, operate: want }));
|
||||
(isPGXL ? (ampSt.via_flex ? FlexAmpOperate(want) : PGXLSetOperate(want))
|
||||
: ampCfg.type.startsWith('acom') ? ACOMSetOperate(want)
|
||||
: SPESetOperate(want)).catch(() => {});
|
||||
if (!connected) { setSettingsSection('pgxl'); setShowSettings(true); return; }
|
||||
const want = !operate;
|
||||
if (viaFlex) setFlexAmp((f: any) => ({ ...f, amp_operate: want }));
|
||||
else setAmpSts((l) => l.map((x: any) => x.id === a.id
|
||||
? { ...x, spe: x.spe && { ...x.spe, operate: want }, acom: x.acom && { ...x.acom, operate: want }, pgxl: x.pgxl && { ...x.pgxl, operate: want } }
|
||||
: x));
|
||||
(viaFlex ? FlexAmpOperate(want) : AmpOperate(a.id, want)).catch(() => {});
|
||||
};
|
||||
return (
|
||||
<button
|
||||
key={a.id}
|
||||
type="button"
|
||||
title={`${name}: ${state}${ampSt.connected ? (ampSt.operate ? ' — click for STANDBY' : ' — click for OPERATE') : ' — click for settings'}`}
|
||||
title={`${a.name}: ${state}${connected ? (operate ? ' — click for STANDBY' : ' — click for OPERATE') : ' — click for settings'}`}
|
||||
onClick={toggle}
|
||||
className="inline-flex items-center gap-1.5 px-2 h-5 rounded border text-[11px] transition-colors border-border hover:bg-muted cursor-pointer"
|
||||
>
|
||||
<span className={cn('size-2 rounded-full', dot)} />
|
||||
{name}
|
||||
{a.name}
|
||||
</button>
|
||||
);
|
||||
})()}
|
||||
})}
|
||||
{/* ON AIR badge: "did I log a QSO in the last 5 min" — meaningful on ANY
|
||||
logbook backend (only the live_status PUBLISHING is MySQL-specific),
|
||||
so it is always shown. Gating it on MySQL made it vanish for
|
||||
|
||||
Reference in New Issue
Block a user