From 5aac28f564f598509308691b58982ea1f93ac0bc Mon Sep 17 00:00:00 2001 From: rouggy Date: Wed, 22 Jul 2026 09:33:39 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20PGXL=20status-bar=20chip=20and=20Station?= =?UTF-8?q?=20Control=20widget=20read=20OPERATE=20from=20the=20Flex=20when?= =?UTF-8?q?=20it=20reports=20the=20amp=20(the=20direct=20GSCP=20status=20d?= =?UTF-8?q?oesn't=20carry=20operate=20state)=20=E2=80=94=20toggle=20then?= =?UTF-8?q?=20goes=20through=20FlexAmpOperate=20like=20the=20Flex=20panel;?= =?UTF-8?q?=20direct=20TCP=20link=20stays=20as=20fallback;=20log=20distinc?= =?UTF-8?q?t=20raw=20PGXL=20status=20payloads=20to=20map=20unknown=20field?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 25 ++++++++++++++++--- .../src/components/StationControlPanel.tsx | 24 +++++++++++++++--- internal/powergenius/powergenius.go | 9 +++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 813921d..6ad7ab0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -44,6 +44,7 @@ import { GetUIPref, GetActiveProfile, QuitApp, ReportLiveActivity, LiveLastQSOAgeSec, GetPGXLSettings, GetPGXLStatus, GetSPEStatus, GetACOMStatus, SPESetOperate, ACOMSetOperate, PGXLSetOperate, + GetFlexState, FlexAmpOperate, } from '../wailsjs/go/main/App'; import { Combobox } from '@/components/ui/combobox'; import { applyAwardRefs } from '@/lib/awardRefs'; @@ -452,8 +453,26 @@ export default function App() { useEffect(() => { if (!ampCfg.enabled) { setAmpSt({ connected: false }); return; } let alive = true; - const get = ampCfg.type === 'pgxl' ? GetPGXLStatus : ampCfg.type.startsWith('acom') ? GetACOMStatus : GetSPEStatus; - const tick = () => get().then((s: any) => alive && setAmpSt(s || { connected: false })).catch(() => {}); + // 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(() => {}); tick(); const id = window.setInterval(tick, 2000); return () => { alive = false; window.clearInterval(id); }; @@ -5160,7 +5179,7 @@ export default function App() { if (!ampSt.connected) { setSettingsSection('pgxl'); setShowSettings(true); return; } const want = !ampSt.operate; setAmpSt((s: any) => ({ ...s, operate: want })); - (isPGXL ? PGXLSetOperate(want) + (isPGXL ? (ampSt.via_flex ? FlexAmpOperate(want) : PGXLSetOperate(want)) : ampCfg.type.startsWith('acom') ? ACOMSetOperate(want) : SPESetOperate(want)).catch(() => {}); }; diff --git a/frontend/src/components/StationControlPanel.tsx b/frontend/src/components/StationControlPanel.tsx index d95d92f..f2e1656 100644 --- a/frontend/src/components/StationControlPanel.tsx +++ b/frontend/src/components/StationControlPanel.tsx @@ -13,7 +13,7 @@ import { GetRotatorHeading, RotatorGoTo, RotatorStop, GetUltrabeamStatus, SetUltrabeamDirection, UltrabeamRetract, MotorSetElement, MotorReadElements, ListDenkoviDevices, ListSerialPorts, TestStationDevice, - GetPGXLSettings, GetPGXLStatus, PGXLSetFanMode, PGXLSetOperate, + GetPGXLSettings, GetPGXLStatus, PGXLSetFanMode, PGXLSetOperate, GetFlexState, FlexAmpOperate, GetSPEStatus, SPESetOperate, SPESetPower, SPESetPowerLevel, GetACOMStatus, ACOMSetOperate, ACOMSetPower, } from '../../wailsjs/go/main/App'; @@ -259,8 +259,24 @@ function AmplifierWidget({ ampType, t }: { ampType: string; t: (k: string, v?: a const [st, setSt] = useState({ connected: false }); useEffect(() => { let alive = true; - const get = isPGXL ? GetPGXLStatus : isACOM ? GetACOMStatus : GetSPEStatus; - const tick = () => get().then((s: any) => alive && setSt(s || { connected: false })).catch(() => {}); + // PGXL: the Flex reports the amp's OPERATE state authoritatively (the direct + // GSCP status doesn't carry it) — merge it in when a Flex sees the amp. + const tick = isPGXL + ? () => Promise.all([ + GetPGXLStatus().catch(() => ({ connected: false })), + GetFlexState().catch(() => null), + ]).then(([pg, fx]: any[]) => { + if (!alive) return; + const viaFlex = !!fx?.amp_available; + setSt({ + ...(pg || {}), + connected: !!pg?.connected || viaFlex, + operate: viaFlex ? !!fx.amp_operate : !!pg?.operate, + via_flex: viaFlex, + }); + }) + : () => (isACOM ? GetACOMStatus : GetSPEStatus)() + .then((s: any) => alive && setSt(s || { connected: false })).catch(() => {}); tick(); const id = window.setInterval(tick, 1500); return () => { alive = false; window.clearInterval(id); }; @@ -285,7 +301,7 @@ function AmplifierWidget({ ampType, t }: { ampType: string; t: (k: string, v?: a {isPGXL ? (