feat: SPE Expert amplifier control (serial/TCP) — OPERATE toggle + live status
Implements the SPE Application Programmer's Guide protocol: packets 0x55 0x55 0x55|CNT|DATA|CHK (sum%256), OPERATE key 0x0D (toggles STANDBY/OPERATE) and STATUS request 0x90 whose 0xAA-framed reply is a 19-field CSV (mode, RX/TX, band, power level, output W, SWR, V/I, temp, warnings, alarms). internal/spe polls status ~1/s over USB serial (go.bug.st/serial, 8N1) or TCP (RS232-to- Ethernet bridge) — same codec, different transport. Wired via startPGXL (starts the SPE client for spe* types), bindings GetSPEStatus / SPESetOperate, and a live status card + OPERATE/STANDBY toggle in the Amplifier settings panel. Only the two example-anchored commands are sent (safe); other keystroke codes were ambiguous in the guide's table. Untested on hardware.
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
GetRotatorSettings, SaveRotatorSettings, TestRotator, RotatorPark, RotatorStop,
|
||||
GetUltrabeamSettings, SaveUltrabeamSettings, TestUltrabeam,
|
||||
GetAntGeniusSettings, SaveAntGeniusSettings,
|
||||
GetPGXLSettings, SavePGXLSettings,
|
||||
GetPGXLSettings, SavePGXLSettings, GetSPEStatus, SPESetOperate,
|
||||
GetWinkeyerSettings, SaveWinkeyerSettings, ListSerialPorts,
|
||||
GetAudioSettings, SaveAudioSettings, ListAudioInputDevices, ListAudioOutputDevices, PickAudioFolder, TestPTT,
|
||||
GetClublogCtyInfo, SetClublogCtyEnabled, DownloadClublogCty,
|
||||
@@ -648,6 +648,49 @@ type RelayRuleUI = { device_id: string; relay: number; mode: string; freq_lo_khz
|
||||
type StationDevUI = { id: string; type: string; name: string; labels: string[] };
|
||||
const RELAY_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m', '6m', '4m', '2m', '70cm'];
|
||||
const relayCountUI = (type: string) => (type === 'kmtronic' || type === 'denkovi' ? 8 : 5);
|
||||
|
||||
// Live SPE Expert amplifier status + OPERATE/STANDBY toggle. Module-scoped (not a
|
||||
// nested component) so it isn't remounted on every parent render. Polls once a
|
||||
// second while shown.
|
||||
function SPEStatusCard() {
|
||||
const [st, setSt] = useState<any>({ connected: false });
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
const tick = () => GetSPEStatus().then((s) => alive && setSt(s || {})).catch(() => {});
|
||||
tick();
|
||||
const id = window.setInterval(tick, 1000);
|
||||
return () => { alive = false; window.clearInterval(id); };
|
||||
}, []);
|
||||
const operate = !!st.operate;
|
||||
return (
|
||||
<div className="rounded-md border border-border p-3 space-y-2 text-xs max-w-xl">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn('size-2 rounded-full', st.connected ? 'bg-success animate-pulse' : 'bg-danger')} />
|
||||
<span className="font-semibold">{st.connected ? `SPE ${st.model || 'Expert'}` : 'SPE — not connected'}</span>
|
||||
{!st.connected && st.last_error && <span className="text-danger truncate text-[10px]">{st.last_error}</span>}
|
||||
<span className="flex-1" />
|
||||
<Button size="sm" variant={operate ? 'default' : 'outline'} disabled={!st.connected}
|
||||
onClick={() => SPESetOperate(!operate).catch(() => {})}
|
||||
title="Toggle OPERATE / STANDBY">
|
||||
{operate ? 'OPERATE' : 'STANDBY'}
|
||||
</Button>
|
||||
</div>
|
||||
{st.connected && (
|
||||
<div className="grid grid-cols-4 gap-x-3 gap-y-1 font-mono text-[11px]">
|
||||
<div>{st.tx ? 'TX' : 'RX'}</div>
|
||||
<div>Band {st.band}</div>
|
||||
<div>Pwr {st.power_level}</div>
|
||||
<div>{st.output_w} W</div>
|
||||
<div>SWR {Number(st.swr_ant ?? 0).toFixed(2)}</div>
|
||||
<div>{st.temp_c}°C</div>
|
||||
<div>{st.volt_pa} V</div>
|
||||
<div>{st.curr_pa} A</div>
|
||||
{(st.warnings || st.alarms) && <div className="col-span-4 text-warning">⚠ {st.warnings} {st.alarms}</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function RelayAutoPanel() {
|
||||
const { t } = useI18n();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
@@ -2724,9 +2767,10 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isPGXL && pgxl.enabled && <SPEStatusCard />}
|
||||
{!isPGXL && (
|
||||
<p className="text-xs text-warning">
|
||||
SPE Expert control is not wired up yet — these settings are saved, but OpsLog can't command the amp until its serial protocol is implemented.
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
SPE control uses the amplifier's proprietary serial protocol (OPERATE toggle + live status). Save to (re)connect. Band / power-level / antenna are still managed on the amp from the transceiver CAT.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user