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:
@@ -45,6 +45,7 @@ import (
|
||||
"hamlog/internal/operating"
|
||||
"hamlog/internal/pota"
|
||||
"hamlog/internal/powergenius"
|
||||
"hamlog/internal/spe"
|
||||
"hamlog/internal/profile"
|
||||
"hamlog/internal/qslcard"
|
||||
"hamlog/internal/qso"
|
||||
@@ -465,6 +466,7 @@ type App struct {
|
||||
motorInhibited atomic.Bool // TX currently inhibited by the motor-antenna watcher
|
||||
antgenius *antgenius.Client // Antenna Genius (4O3A) switch (TCP); nil when disabled
|
||||
pgxl *powergenius.Client // PowerGenius XL (4O3A) amp fan control (TCP); nil when disabled
|
||||
spe *spe.Client // SPE Expert amplifier (serial/TCP); nil when disabled or not SPE
|
||||
audioMgr *audio.Manager
|
||||
qsoRec *audio.Recorder // continuous QSO recorder (rolling pre-roll)
|
||||
solar *solar.Manager // live space-weather (SFI/SSN/A/K) for the header + QSO stamping
|
||||
@@ -12219,22 +12221,48 @@ func (a *App) startPGXL() {
|
||||
go a.pgxl.Stop()
|
||||
a.pgxl = nil
|
||||
}
|
||||
if a.spe != nil {
|
||||
go a.spe.Stop()
|
||||
a.spe = nil
|
||||
}
|
||||
s, err := a.GetPGXLSettings()
|
||||
if err != nil || !s.Enabled {
|
||||
return
|
||||
}
|
||||
// Only the PowerGenius XL (TCP) is driven for now. SPE Expert control (serial /
|
||||
// RS232-to-Ethernet) is a separate protocol, not yet implemented — its settings
|
||||
// are stored but no client is started.
|
||||
if s.Type != "" && s.Type != "pgxl" {
|
||||
applog.Printf("amplifier: type %q selected — control not implemented yet (settings saved)", s.Type)
|
||||
if s.Type == "" || s.Type == "pgxl" {
|
||||
if strings.TrimSpace(s.Host) == "" {
|
||||
return
|
||||
}
|
||||
a.pgxl = powergenius.New(s.Host, s.Port)
|
||||
_ = a.pgxl.Start()
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(s.Host) == "" {
|
||||
// SPE Expert — USB serial or an RS232-to-Ethernet bridge.
|
||||
if s.Transport == "serial" && strings.TrimSpace(s.ComPort) == "" {
|
||||
return
|
||||
}
|
||||
a.pgxl = powergenius.New(s.Host, s.Port)
|
||||
_ = a.pgxl.Start()
|
||||
if s.Transport == "tcp" && strings.TrimSpace(s.Host) == "" {
|
||||
return
|
||||
}
|
||||
a.spe = spe.New(spe.Config{Transport: s.Transport, ComPort: s.ComPort, Baud: s.Baud, Host: s.Host, Port: s.Port})
|
||||
_ = a.spe.Start()
|
||||
}
|
||||
|
||||
// GetSPEStatus returns the SPE Expert amplifier state for the UI poll.
|
||||
func (a *App) GetSPEStatus() spe.Status {
|
||||
if a.spe == nil {
|
||||
return spe.Status{}
|
||||
}
|
||||
return a.spe.GetStatus()
|
||||
}
|
||||
|
||||
// SPESetOperate puts the SPE amp in OPERATE (true) or STANDBY (false). The amp has
|
||||
// a single OPERATE toggle key, so this sends it only when the state must change.
|
||||
func (a *App) SPESetOperate(on bool) error {
|
||||
if a.spe == nil {
|
||||
return fmt.Errorf("SPE amplifier not connected — enable it in Settings → Amplifier")
|
||||
}
|
||||
return a.spe.Operate(on)
|
||||
}
|
||||
|
||||
// GetPGXLStatus returns the amp's fan/connection state for the UI poll.
|
||||
|
||||
Reference in New Issue
Block a user