fix: PGXL status-bar chip and Station Control widget read OPERATE from the Flex when it reports the amp (the direct GSCP status doesn't carry operate state) — toggle then goes through FlexAmpOperate like the Flex panel; direct TCP link stays as fallback; log distinct raw PGXL status payloads to map unknown fields
This commit is contained in:
+22
-3
@@ -44,6 +44,7 @@ import {
|
|||||||
GetUIPref, GetActiveProfile, QuitApp,
|
GetUIPref, GetActiveProfile, QuitApp,
|
||||||
ReportLiveActivity, LiveLastQSOAgeSec,
|
ReportLiveActivity, LiveLastQSOAgeSec,
|
||||||
GetPGXLSettings, GetPGXLStatus, GetSPEStatus, GetACOMStatus, SPESetOperate, ACOMSetOperate, PGXLSetOperate,
|
GetPGXLSettings, GetPGXLStatus, GetSPEStatus, GetACOMStatus, SPESetOperate, ACOMSetOperate, PGXLSetOperate,
|
||||||
|
GetFlexState, FlexAmpOperate,
|
||||||
} from '../wailsjs/go/main/App';
|
} from '../wailsjs/go/main/App';
|
||||||
import { Combobox } from '@/components/ui/combobox';
|
import { Combobox } from '@/components/ui/combobox';
|
||||||
import { applyAwardRefs } from '@/lib/awardRefs';
|
import { applyAwardRefs } from '@/lib/awardRefs';
|
||||||
@@ -452,8 +453,26 @@ export default function App() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ampCfg.enabled) { setAmpSt({ connected: false }); return; }
|
if (!ampCfg.enabled) { setAmpSt({ connected: false }); return; }
|
||||||
let alive = true;
|
let alive = true;
|
||||||
const get = ampCfg.type === 'pgxl' ? GetPGXLStatus : ampCfg.type.startsWith('acom') ? GetACOMStatus : GetSPEStatus;
|
// PGXL: the Flex reports the amp's OPERATE state authoritatively (the direct
|
||||||
const tick = () => get().then((s: any) => alive && setAmpSt(s || { connected: false })).catch(() => {});
|
// 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();
|
tick();
|
||||||
const id = window.setInterval(tick, 2000);
|
const id = window.setInterval(tick, 2000);
|
||||||
return () => { alive = false; window.clearInterval(id); };
|
return () => { alive = false; window.clearInterval(id); };
|
||||||
@@ -5160,7 +5179,7 @@ export default function App() {
|
|||||||
if (!ampSt.connected) { setSettingsSection('pgxl'); setShowSettings(true); return; }
|
if (!ampSt.connected) { setSettingsSection('pgxl'); setShowSettings(true); return; }
|
||||||
const want = !ampSt.operate;
|
const want = !ampSt.operate;
|
||||||
setAmpSt((s: any) => ({ ...s, operate: want }));
|
setAmpSt((s: any) => ({ ...s, operate: want }));
|
||||||
(isPGXL ? PGXLSetOperate(want)
|
(isPGXL ? (ampSt.via_flex ? FlexAmpOperate(want) : PGXLSetOperate(want))
|
||||||
: ampCfg.type.startsWith('acom') ? ACOMSetOperate(want)
|
: ampCfg.type.startsWith('acom') ? ACOMSetOperate(want)
|
||||||
: SPESetOperate(want)).catch(() => {});
|
: SPESetOperate(want)).catch(() => {});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
GetRotatorHeading, RotatorGoTo, RotatorStop,
|
GetRotatorHeading, RotatorGoTo, RotatorStop,
|
||||||
GetUltrabeamStatus, SetUltrabeamDirection, UltrabeamRetract, MotorSetElement, MotorReadElements,
|
GetUltrabeamStatus, SetUltrabeamDirection, UltrabeamRetract, MotorSetElement, MotorReadElements,
|
||||||
ListDenkoviDevices, ListSerialPorts, TestStationDevice,
|
ListDenkoviDevices, ListSerialPorts, TestStationDevice,
|
||||||
GetPGXLSettings, GetPGXLStatus, PGXLSetFanMode, PGXLSetOperate,
|
GetPGXLSettings, GetPGXLStatus, PGXLSetFanMode, PGXLSetOperate, GetFlexState, FlexAmpOperate,
|
||||||
GetSPEStatus, SPESetOperate, SPESetPower, SPESetPowerLevel,
|
GetSPEStatus, SPESetOperate, SPESetPower, SPESetPowerLevel,
|
||||||
GetACOMStatus, ACOMSetOperate, ACOMSetPower,
|
GetACOMStatus, ACOMSetOperate, ACOMSetPower,
|
||||||
} from '../../wailsjs/go/main/App';
|
} from '../../wailsjs/go/main/App';
|
||||||
@@ -259,8 +259,24 @@ function AmplifierWidget({ ampType, t }: { ampType: string; t: (k: string, v?: a
|
|||||||
const [st, setSt] = useState<any>({ connected: false });
|
const [st, setSt] = useState<any>({ connected: false });
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let alive = true;
|
let alive = true;
|
||||||
const get = isPGXL ? GetPGXLStatus : isACOM ? GetACOMStatus : GetSPEStatus;
|
// PGXL: the Flex reports the amp's OPERATE state authoritatively (the direct
|
||||||
const tick = () => get().then((s: any) => alive && setSt(s || { connected: false })).catch(() => {});
|
// 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();
|
tick();
|
||||||
const id = window.setInterval(tick, 1500);
|
const id = window.setInterval(tick, 1500);
|
||||||
return () => { alive = false; window.clearInterval(id); };
|
return () => { alive = false; window.clearInterval(id); };
|
||||||
@@ -285,7 +301,7 @@ function AmplifierWidget({ ampType, t }: { ampType: string; t: (k: string, v?: a
|
|||||||
{isPGXL ? (
|
{isPGXL ? (
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<button type="button" disabled={!st.connected}
|
<button type="button" disabled={!st.connected}
|
||||||
onClick={() => { const want = !st.operate; setSt((s: any) => ({ ...s, operate: want })); PGXLSetOperate(want).catch(() => {}); }}
|
onClick={() => { const want = !st.operate; setSt((s: any) => ({ ...s, operate: want })); (st.via_flex ? FlexAmpOperate(want) : PGXLSetOperate(want)).catch(() => {}); }}
|
||||||
className={cn('px-3 py-1.5 rounded-lg text-xs font-extrabold tracking-wide border-2 transition-all disabled:opacity-40',
|
className={cn('px-3 py-1.5 rounded-lg text-xs font-extrabold tracking-wide border-2 transition-all disabled:opacity-40',
|
||||||
st.operate ? 'bg-warning text-warning-foreground border-warning' : 'bg-card text-warning border-warning hover:bg-warning-muted')}>
|
st.operate ? 'bg-warning text-warning-foreground border-warning' : 'bg-card text-warning border-warning hover:bg-warning-muted')}>
|
||||||
{st.operate ? 'OPERATE' : 'STANDBY'}
|
{st.operate ? 'OPERATE' : 'STANDBY'}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"hamlog/internal/applog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -46,6 +48,7 @@ type Client struct {
|
|||||||
|
|
||||||
statusMu sync.RWMutex
|
statusMu sync.RWMutex
|
||||||
status Status
|
status Status
|
||||||
|
lastRaw string // last raw status payload — logged on change so unknown fields (operate?) can be mapped from a real amp
|
||||||
// Optimistic fan mode kept until the amp's status poll confirms it (or it
|
// Optimistic fan mode kept until the amp's status poll confirms it (or it
|
||||||
// ages out) — otherwise a stale poll right after a change reverts the UI.
|
// ages out) — otherwise a stale poll right after a change reverts the UI.
|
||||||
fanPending string
|
fanPending string
|
||||||
@@ -223,6 +226,12 @@ func (c *Client) parse(resp string) {
|
|||||||
c.statusMu.Lock()
|
c.statusMu.Lock()
|
||||||
c.status.Connected = true
|
c.status.Connected = true
|
||||||
c.status.LastError = ""
|
c.status.LastError = ""
|
||||||
|
// Log each DISTINCT status payload once: the PGXL's field set isn't fully
|
||||||
|
// documented, so this is how we learn the real key for e.g. operate/standby.
|
||||||
|
if data != c.lastRaw {
|
||||||
|
c.lastRaw = data
|
||||||
|
applog.Printf("pgxl: status raw=%q", data)
|
||||||
|
}
|
||||||
for _, pair := range strings.Fields(data) {
|
for _, pair := range strings.Fields(data) {
|
||||||
kv := strings.SplitN(pair, "=", 2)
|
kv := strings.SplitN(pair, "=", 2)
|
||||||
if len(kv) != 2 {
|
if len(kv) != 2 {
|
||||||
|
|||||||
Reference in New Issue
Block a user