OmniRig: on the IC-7610 the generic Freq property reports the wrong VFO (its Main/Sub model confuses the stock ini), so OpsLog showed VFO B. Detect the rig by RigType and, in simplex, read VFO A explicitly — matching Log4OM. Only the 7610 is affected; other rigs keep using the generic Freq. Icom network: when the rig tears the session down (control/CI-V 0x05) OpsLog only logged it and kept the half-dead link until the 6 s liveness timeout expired. Mark the link dead on 0x05 so Alive() fails on the next poll and the manager reconnects cleanly right away.
506 lines
18 KiB
Go
506 lines
18 KiB
Go
package cat
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-ole/go-ole"
|
|
"github.com/go-ole/go-ole/oleutil"
|
|
)
|
|
|
|
// OmniRig Split is an enum, not a boolean: PM_SPLITON vs PM_SPLITOFF — both
|
|
// non-zero, so it must be compared to PM_SPLITON (testing "!= 0" reads OFF as
|
|
// split). Values confirmed empirically from real rigs (FT-710, SmartSDR):
|
|
// split ON = 0x8000, split OFF = 0x10000.
|
|
const (
|
|
pmSplitOn = 0x8000 // PM_SPLITON
|
|
pmSplitOff = 0x10000 // PM_SPLITOFF
|
|
)
|
|
|
|
// OmniRig talks to the user's installed OmniRig server over COM.
|
|
//
|
|
// All methods MUST be called from the same OS thread (the one Manager.run
|
|
// locks). COM is thread-affine on Windows — calling these from random
|
|
// goroutines will return E_FAIL or crash.
|
|
//
|
|
// The user must install OmniRig separately and configure their rig (COM port,
|
|
// baud rate) in OmniRig's own GUI. HamLog just reads/writes through it.
|
|
type OmniRig struct {
|
|
RigNum int // 1 (Rig1) or 2 (Rig2)
|
|
|
|
omnirig *ole.IDispatch
|
|
rig *ole.IDispatch
|
|
lastSig string // last logged Split/VFO signature — only log on change
|
|
rigType string // OmniRig's RigType string (the .ini title), e.g. "IC-7610"
|
|
|
|
// lastSetFreq is the frequency most recently COMMANDED via SetFrequency.
|
|
// SetMode uses it to pick USB vs LSB for "SSB" instead of reading OmniRig's
|
|
// async Freq property, which still reports the OLD band for a poll or two
|
|
// after a QSY — that lag is why a clicked spot needed a second click to fix
|
|
// the sideband (freq moved, but mode read the old band → wrong sideband).
|
|
lastSetFreq int64
|
|
lastSetFreqAt time.Time
|
|
}
|
|
|
|
// NewOmniRig creates a non-connected backend. Call Connect before use.
|
|
func NewOmniRig(rigNum int) *OmniRig {
|
|
if rigNum < 1 || rigNum > 2 {
|
|
rigNum = 1
|
|
}
|
|
return &OmniRig{RigNum: rigNum}
|
|
}
|
|
|
|
func (o *OmniRig) Name() string { return "omnirig" }
|
|
|
|
func (o *OmniRig) Connect() error {
|
|
debugLog.Printf("OmniRig.Connect Rig%d — log path: %s", o.RigNum, DebugLogPath())
|
|
if err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED); err != nil {
|
|
// 0x1 = S_FALSE → COM already initialised on this thread, fine.
|
|
if oerr, ok := err.(*ole.OleError); !ok || oerr.Code() != 0x00000001 {
|
|
return fmt.Errorf("CoInitializeEx: %w", err)
|
|
}
|
|
}
|
|
|
|
unk, err := oleutil.CreateObject("Omnirig.OmnirigX")
|
|
if err != nil {
|
|
return fmt.Errorf("Omnirig.OmnirigX not available — is OmniRig installed and running?: %w", err)
|
|
}
|
|
omnirig, err := unk.QueryInterface(ole.IID_IDispatch)
|
|
unk.Release()
|
|
if err != nil {
|
|
return fmt.Errorf("query interface: %w", err)
|
|
}
|
|
|
|
rigVar, err := oleutil.GetProperty(omnirig, fmt.Sprintf("Rig%d", o.RigNum))
|
|
if err != nil {
|
|
omnirig.Release()
|
|
return fmt.Errorf("get Rig%d: %w", o.RigNum, err)
|
|
}
|
|
o.omnirig = omnirig
|
|
o.rig = rigVar.ToIDispatch()
|
|
|
|
if rt, err := oleutil.GetProperty(o.rig, "RigType"); err == nil {
|
|
o.rigType = rt.ToString()
|
|
debugLog.Printf("OmniRig connected to Rig%d type=%q", o.RigNum, o.rigType)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// isIC7610 reports whether the connected rig is an IC-7610. OmniRig's generic
|
|
// Freq property reads the wrong VFO on the 7610 (its Main/Sub model confuses the
|
|
// stock ini), so we read VFO A explicitly for it instead — matching what Log4OM
|
|
// shows.
|
|
func (o *OmniRig) isIC7610() bool {
|
|
return strings.Contains(strings.ToUpper(o.rigType), "7610")
|
|
}
|
|
|
|
func (o *OmniRig) Disconnect() {
|
|
if o.rig != nil {
|
|
o.rig.Release()
|
|
o.rig = nil
|
|
}
|
|
if o.omnirig != nil {
|
|
o.omnirig.Release()
|
|
o.omnirig = nil
|
|
}
|
|
ole.CoUninitialize()
|
|
}
|
|
|
|
func (o *OmniRig) ReadState() (RigState, error) {
|
|
if o.rig == nil {
|
|
return RigState{}, fmt.Errorf("not connected")
|
|
}
|
|
var s RigState
|
|
s.Backend = o.Name()
|
|
s.RigNum = o.RigNum
|
|
|
|
// Status: 0 = NOTCONFIGURED, 1 = DISABLED, 2 = PORTBUSY,
|
|
// 3 = NOTRESPONDING, 4 = ONLINE.
|
|
if statusVar, err := oleutil.GetProperty(o.rig, "Status"); err == nil {
|
|
s.Connected = statusVar.Val == 4
|
|
}
|
|
|
|
if rigTypeVar, err := oleutil.GetProperty(o.rig, "RigType"); err == nil {
|
|
s.Rig = rigTypeVar.ToString()
|
|
}
|
|
|
|
if !s.Connected {
|
|
// Status string from OmniRig is informative for the user.
|
|
if statusStrVar, err := oleutil.GetProperty(o.rig, "StatusStr"); err == nil {
|
|
s.Error = statusStrVar.ToString()
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
if modeVar, err := oleutil.GetProperty(o.rig, "Mode"); err == nil {
|
|
s.Mode = omniRigMode(modeVar.Val)
|
|
}
|
|
rawVfo := int64(0)
|
|
if vfoVar, err := oleutil.GetProperty(o.rig, "Vfo"); err == nil {
|
|
rawVfo = vfoVar.Val
|
|
s.Vfo = omniRigVfo(vfoVar.Val)
|
|
}
|
|
|
|
// Read the active/displayed frequency (generic Freq) AND both VFOs. The
|
|
// generic Freq is what the rig is operating on — the reliable source for the
|
|
// main/TX frequency. FreqA/FreqB are only needed to expose a genuine split.
|
|
freqMain, freqA, freqB := int64(0), int64(0), int64(0)
|
|
if v, err := oleutil.GetProperty(o.rig, "Freq"); err == nil {
|
|
freqMain = v.Val
|
|
}
|
|
if v, err := oleutil.GetProperty(o.rig, "FreqA"); err == nil {
|
|
freqA = v.Val
|
|
}
|
|
if v, err := oleutil.GetProperty(o.rig, "FreqB"); err == nil {
|
|
freqB = v.Val
|
|
}
|
|
|
|
// Split is an enum (PM_SPLITON / PM_SPLITOFF) — both non-zero, so it must be
|
|
// compared to PM_SPLITON, not "!= 0".
|
|
splitRaw := int64(0)
|
|
if v, err := oleutil.GetProperty(o.rig, "Split"); err == nil {
|
|
splitRaw = v.Val
|
|
}
|
|
|
|
// Diagnostic logged ONLY when Split or VFO changes (not on a timer), so
|
|
// normal operation stays quiet but toggling split on the radio is captured —
|
|
// needed to pin down this rig's PM_SPLITON value.
|
|
if sig := fmt.Sprintf("%x:%x", splitRaw, rawVfo); sig != o.lastSig {
|
|
o.lastSig = sig
|
|
debugLog.Printf("OmniRig Rig%d raw: Freq=%d FreqA=%d FreqB=%d Vfo=%q(raw=0x%X) Split=0x%X status=%d",
|
|
o.RigNum, freqMain, freqA, freqB, s.Vfo, rawVfo, splitRaw, func() int64 {
|
|
if v, e := oleutil.GetProperty(o.rig, "Status"); e == nil {
|
|
return v.Val
|
|
}
|
|
return -1
|
|
}())
|
|
}
|
|
|
|
// A genuine split: the rig explicitly flags PM_SPLITON, the two VFOs are
|
|
// distinct and non-zero, AND they're in the same band. The same-band test
|
|
// kills the common false positive where VFO B just holds a leftover from
|
|
// another band (a "28 MHz / 7 MHz split" is nonsensical), which on the
|
|
// FT-710 / TS-570 otherwise froze the main/TX freq on the wrong VFO.
|
|
genuineSplit := splitRaw == pmSplitOn &&
|
|
freqA != 0 && freqB != 0 && freqA != freqB &&
|
|
BandFromHz(freqA) == BandFromHz(freqB)
|
|
|
|
if genuineSplit {
|
|
// ADIF: FreqHz = TX, RxFreqHz = RX. Determine which VFO is RX from the
|
|
// ACTIVE frequency (OmniRig's generic Freq — the VFO you're listening on):
|
|
// RX = the active VFO, TX = the other one. This is far more reliable than
|
|
// trusting OmniRig's Vfo AB/BA enum, which several rigs (e.g. Yaesu FTDX10)
|
|
// report inverted — the split then showed TX/RX swapped.
|
|
s.Split = true
|
|
switch {
|
|
case freqMain != 0 && freqMain == freqA:
|
|
s.RxFreqHz, s.FreqHz = freqA, freqB // listening on A → TX on B
|
|
case freqMain != 0 && freqMain == freqB:
|
|
s.RxFreqHz, s.FreqHz = freqB, freqA // listening on B → TX on A
|
|
case s.Vfo == "BA":
|
|
s.FreqHz, s.RxFreqHz = freqA, freqB // fall back to the Vfo enum
|
|
default:
|
|
s.FreqHz, s.RxFreqHz = freqB, freqA
|
|
}
|
|
} else {
|
|
// Simplex: the operating frequency is OmniRig's generic Freq (the active
|
|
// VFO), like Log4OM. Fall back to the per-VFO value only if Freq is 0.
|
|
s.Split = false
|
|
s.RxFreqHz = 0
|
|
s.FreqHz = freqMain
|
|
// IC-7610 quirk: OmniRig's generic Freq reports VFO B (its Main/Sub model
|
|
// confuses the stock ini), so OpsLog showed the wrong VFO. Read VFO A
|
|
// explicitly for the 7610 — what the operator actually wants to see.
|
|
if o.isIC7610() && freqA != 0 {
|
|
s.FreqHz = freqA
|
|
}
|
|
if s.FreqHz == 0 {
|
|
if s.Vfo == "B" || s.Vfo == "BB" {
|
|
s.FreqHz = freqB
|
|
} else {
|
|
s.FreqHz = freqA
|
|
}
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (o *OmniRig) SetFrequency(hz int64) error {
|
|
if o.rig == nil {
|
|
debugLog.Printf("OmniRig.SetFrequency(%d): NOT CONNECTED", hz)
|
|
return fmt.Errorf("not connected")
|
|
}
|
|
// OmniRig Freq is a Long (int32). Validate to avoid silent truncation.
|
|
if hz < 0 || hz > 0x7fffffff {
|
|
debugLog.Printf("OmniRig.SetFrequency(%d): out of int32 range", hz)
|
|
return fmt.Errorf("frequency out of OmniRig int32 range")
|
|
}
|
|
hz32 := int32(hz)
|
|
// Remember the commanded frequency so a mode change moments later (a clicked
|
|
// spot sets freq then mode) picks the sideband from the TARGET band, not the
|
|
// not-yet-updated OmniRig Freq property.
|
|
o.lastSetFreq, o.lastSetFreqAt = hz, time.Now()
|
|
|
|
// Log the rig's writable-params, status and VFO state up front so a
|
|
// friend's session shows exactly what OmniRig reports for their rig.
|
|
status, statusStr, rigType := int64(-1), "", ""
|
|
if v, err := oleutil.GetProperty(o.rig, "Status"); err == nil {
|
|
status = v.Val
|
|
}
|
|
if v, err := oleutil.GetProperty(o.rig, "StatusStr"); err == nil {
|
|
statusStr = v.ToString()
|
|
}
|
|
if v, err := oleutil.GetProperty(o.rig, "RigType"); err == nil {
|
|
rigType = v.ToString()
|
|
}
|
|
rawVfo, vfo := int64(-1), ""
|
|
if vfoVar, err := oleutil.GetProperty(o.rig, "Vfo"); err == nil {
|
|
rawVfo = vfoVar.Val
|
|
vfo = omniRigVfo(vfoVar.Val)
|
|
} else {
|
|
debugLog.Printf("OmniRig.SetFrequency: Vfo read error: %v", err)
|
|
}
|
|
split := int64(0)
|
|
if v, err := oleutil.GetProperty(o.rig, "Split"); err == nil {
|
|
split = v.Val
|
|
}
|
|
// What can this rig's .ini actually write? OmniRig exposes a WriteableParams
|
|
// bitmask — if FreqA/FreqB/Freq bits are missing, the write is a silent no-op.
|
|
writeable := int64(-1)
|
|
if v, err := oleutil.GetProperty(o.rig, "WriteableParams"); err == nil {
|
|
writeable = v.Val
|
|
}
|
|
debugLog.Printf("OmniRig.SetFrequency(%d Hz / %.6f MHz): rig=%q status=%d(%s) vfo=%q(raw=%d) split=%d writeableParams=0x%X",
|
|
hz, float64(hz)/1e6, rigType, status, statusStr, vfo, rawVfo, split, writeable)
|
|
|
|
// Primary path: OmniRig's SetSimplexMode is the rig-agnostic "QSY here"
|
|
// method (RX=TX=freq, simplex). It works on rigs — notably Icom (IC-9100) —
|
|
// where direct FreqA/FreqB writes are accepted but never move the radio.
|
|
// Clearing split is the right thing when tuning to a spot anyway.
|
|
if _, err := oleutil.CallMethod(o.rig, "SetSimplexMode", int32(hz32)); err == nil {
|
|
debugLog.Printf("OmniRig.SetFrequency: SetSimplexMode(%d) OK", hz32)
|
|
} else {
|
|
debugLog.Printf("OmniRig.SetFrequency: SetSimplexMode unavailable (%v) — using property writes", err)
|
|
// Fallback: write the active VFO's property AND the generic Freq
|
|
// (always — some .ini honour only one, and split here is often misread).
|
|
prop := "FreqA"
|
|
switch vfo {
|
|
case "B", "BB", "BA":
|
|
prop = "FreqB"
|
|
}
|
|
okAny := false
|
|
for _, p := range []string{prop, "Freq"} {
|
|
if _, e := oleutil.PutProperty(o.rig, p, hz32); e != nil {
|
|
debugLog.Printf("OmniRig.SetFrequency: PutProperty(%s) error: %v", p, e)
|
|
} else {
|
|
debugLog.Printf("OmniRig.SetFrequency: PutProperty(%s, %d) OK", p, hz32)
|
|
okAny = true
|
|
}
|
|
}
|
|
if !okAny {
|
|
return fmt.Errorf("OmniRig: no writable frequency property for this rig")
|
|
}
|
|
}
|
|
|
|
// Read back all three immediately. OmniRig is async (the CAT command is
|
|
// queued + sent over serial), so these may still show the OLD value for
|
|
// one poll cycle — but if they NEVER change in the next poll, the rig
|
|
// isn't honouring the write (wrong .ini WRITE command for this model).
|
|
fa, fb, fg := int64(-1), int64(-1), int64(-1)
|
|
if v, err := oleutil.GetProperty(o.rig, "FreqA"); err == nil {
|
|
fa = v.Val
|
|
}
|
|
if v, err := oleutil.GetProperty(o.rig, "FreqB"); err == nil {
|
|
fb = v.Val
|
|
}
|
|
if v, err := oleutil.GetProperty(o.rig, "Freq"); err == nil {
|
|
fg = v.Val
|
|
}
|
|
debugLog.Printf("OmniRig.SetFrequency: readback FreqA=%d FreqB=%d Freq=%d (target %d)", fa, fb, fg, hz)
|
|
return nil
|
|
}
|
|
|
|
// SetMode maps an ADIF mode to the OmniRig PM_* bit and pushes it to the rig.
|
|
// For SSB, the USB/LSB side is chosen from the rig's current frequency
|
|
// following worldwide convention (LSB below 14 MHz, USB above).
|
|
//
|
|
// IMPORTANT: OmniRig's Mode property is typed as Long (VT_I4). go-ole would
|
|
// otherwise wrap a Go int64 into a VT_I8 variant which COM marshalling can
|
|
// reject silently or misinterpret — passing the wrong bit. Always cast to
|
|
// int32 explicitly.
|
|
//
|
|
// Logs each call to stdout so the user can cross-check what HamLog sent
|
|
// against OmniRig's Monitor window (right-click systray → Monitor) to find
|
|
// rig-specific mismatches (e.g. a Kenwood without FM on HF, an .ini with the
|
|
// wrong CAT command for a mode, etc.).
|
|
func (o *OmniRig) SetMode(mode string) error {
|
|
if o.rig == nil {
|
|
return fmt.Errorf("not connected")
|
|
}
|
|
var (
|
|
bit int64
|
|
bitName string
|
|
)
|
|
switch strings.ToUpper(strings.TrimSpace(mode)) {
|
|
case "CW":
|
|
bit, bitName = pmCWU, "PM_CW_U"
|
|
case "SSB":
|
|
// Decide USB vs LSB from the frequency. Prefer the freq we just COMMANDED
|
|
// (a clicked spot sets freq then mode ~150ms later): OmniRig's Freq
|
|
// property still reports the OLD band for a poll or two after a QSY, so
|
|
// reading it here picked the wrong sideband and the user had to click a
|
|
// second time. Fall back to the live read for a standalone mode change.
|
|
var freq int64
|
|
if o.lastSetFreq > 0 && time.Since(o.lastSetFreqAt) < 5*time.Second {
|
|
freq = o.lastSetFreq
|
|
} else if freqVar, err := oleutil.GetProperty(o.rig, "Freq"); err == nil {
|
|
freq = freqVar.Val
|
|
}
|
|
if freq > 0 && freq < 10_000_000 {
|
|
bit, bitName = pmSSBL, "PM_SSB_L"
|
|
} else {
|
|
bit, bitName = pmSSBU, "PM_SSB_U"
|
|
}
|
|
case "AM":
|
|
bit, bitName = pmAM, "PM_AM"
|
|
case "FM":
|
|
bit, bitName = pmFM, "PM_FM"
|
|
case "RTTY", "FSK":
|
|
// OmniRig has no specific RTTY/FSK mode — falls back to generic
|
|
// digital USB. Many rigs need RTTY selected manually on the panel.
|
|
bit, bitName = pmDIGU, "PM_DIG_U"
|
|
case "FT8", "FT4", "PSK31", "MFSK", "JS8", "JT65", "JT9", "OLIVIA", "DIGITALVOICE", "DATA":
|
|
bit, bitName = pmDIGU, "PM_DIG_U"
|
|
default:
|
|
return fmt.Errorf("OmniRig: unsupported mode %q", mode)
|
|
}
|
|
debugLog.Printf("OmniRig.SetMode(%q) → %s = 0x%08X (%d)", mode, bitName, bit, bit)
|
|
_, err := oleutil.PutProperty(o.rig, "Mode", int32(bit))
|
|
if err != nil {
|
|
debugLog.Printf("OmniRig.SetMode error: %v", err)
|
|
return fmt.Errorf("SetMode(%s) → %s: %w", mode, bitName, err)
|
|
}
|
|
|
|
// Read back what OmniRig now thinks the rig is on (best-effort —
|
|
// OmniRig is async so this may still be the old value for one poll).
|
|
if mv, err := oleutil.GetProperty(o.rig, "Mode"); err == nil {
|
|
debugLog.Printf("OmniRig.Mode immediately after Put = 0x%08X (%d) → %s",
|
|
mv.Val, mv.Val, omniRigMode(mv.Val))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetPTT keys or unkeys the rig via OmniRig's SetTx(PM_RX|PM_TX). Used by the
|
|
// Digital Voice Keyer to put the rig into TX while a voice message plays.
|
|
func (o *OmniRig) SetPTT(on bool) error {
|
|
if o.rig == nil {
|
|
debugLog.Printf("OmniRig.SetPTT(%v): NOT CONNECTED", on)
|
|
return fmt.Errorf("not connected")
|
|
}
|
|
status, statusStr, writeable := int64(-1), "", int64(-1)
|
|
if v, err := oleutil.GetProperty(o.rig, "Status"); err == nil {
|
|
status = v.Val
|
|
}
|
|
if v, err := oleutil.GetProperty(o.rig, "StatusStr"); err == nil {
|
|
statusStr = v.ToString()
|
|
}
|
|
if v, err := oleutil.GetProperty(o.rig, "WriteableParams"); err == nil {
|
|
writeable = v.Val
|
|
}
|
|
txWriteable := writeable != -1 && writeable&pmTX != 0
|
|
param, name := pmRX, "PM_RX"
|
|
if on {
|
|
param, name = pmTX, "PM_TX"
|
|
}
|
|
debugLog.Printf("OmniRig.SetPTT(%v): status=%d(%s) writeableParams=0x%X PM_TX-writeable=%v → Tx=%s",
|
|
on, status, statusStr, writeable, txWriteable, name)
|
|
// When OmniRig DID report its writeable params (writeable != -1) and PM_TX
|
|
// is NOT among them, writing Tx is a silent no-op: the rig never keys and
|
|
// SetPTT would otherwise return success, leaving the user puzzled ("Test PTT
|
|
// does nothing"). Surface a clear, actionable error instead. If we couldn't
|
|
// read the writeable params (-1), fall through and try anyway (best effort).
|
|
if on && writeable != -1 && writeable&pmTX == 0 {
|
|
debugLog.Printf("OmniRig.SetPTT: ⚠ PM_TX not writeable for this rig profile (writeableParams=0x%X)", writeable)
|
|
return fmt.Errorf("this rig's OmniRig profile doesn't expose CAT TX keying (PM_TX not writeable) — use RTS/DTR or VOX for PTT")
|
|
}
|
|
// OmniRig has NO SetTx method (that returns "unknown name"); the Tx
|
|
// parameter is set via the writeable Tx PROPERTY (PM_TX / PM_RX).
|
|
if _, err := oleutil.PutProperty(o.rig, "Tx", int32(param)); err != nil {
|
|
debugLog.Printf("OmniRig.SetPTT error: %v", err)
|
|
return fmt.Errorf("set Tx=%s: %w", name, err)
|
|
}
|
|
// Read the Tx param straight back. OmniRig is async — this may still show the
|
|
// previous value for a poll cycle — but if a key/unkey NEVER changes it, the
|
|
// write was coalesced or the rig isn't honouring PM_TX/PM_RX (wrong .ini).
|
|
if v, err := oleutil.GetProperty(o.rig, "Tx"); err == nil {
|
|
txState := "PM_RX"
|
|
if v.Val&pmTX != 0 {
|
|
txState = "PM_TX"
|
|
}
|
|
debugLog.Printf("OmniRig.SetPTT: Tx readback = 0x%X (%s)", v.Val, txState)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ===== OmniRig enum decoders =====
|
|
|
|
// Bit flags from OmniRig type library (RigParamX enum in OmniRig_TLB.pas).
|
|
//
|
|
// Cross-checked against https://github.com/VE3NEA/OmniRig — be careful when
|
|
// referencing other people's writeups online, several have these one bit
|
|
// too low which causes every mode to map to the slot below it (AM → DIG_L,
|
|
// FT8 → SSB_L, etc.).
|
|
const (
|
|
pmRX int64 = 1 << 20 // 0x00100000 — PM_RX (receive)
|
|
pmTX int64 = 1 << 21 // 0x00200000 — PM_TX (transmit / PTT on)
|
|
pmCWU int64 = 1 << 23 // 0x00800000
|
|
pmCWL int64 = 1 << 24 // 0x01000000
|
|
pmSSBU int64 = 1 << 25 // 0x02000000
|
|
pmSSBL int64 = 1 << 26 // 0x04000000
|
|
pmDIGU int64 = 1 << 27 // 0x08000000
|
|
pmDIGL int64 = 1 << 28 // 0x10000000
|
|
pmAM int64 = 1 << 29 // 0x20000000
|
|
pmFM int64 = 1 << 30 // 0x40000000 — still fits in int32 (max 2^31-1)
|
|
)
|
|
|
|
// omniRigMode maps the OmniRig Mode bit-flag to an ADIF mode string.
|
|
// OmniRig only reports rough categories; specific digital modes
|
|
// (FT8, RTTY, PSK31…) can't be inferred — DATA is returned and the user
|
|
// can keep / override the mode they already had in the entry form.
|
|
func omniRigMode(m int64) string {
|
|
switch {
|
|
case m&(pmCWU|pmCWL) != 0:
|
|
return "CW"
|
|
case m&(pmSSBU|pmSSBL) != 0:
|
|
return "SSB"
|
|
case m&(pmDIGU|pmDIGL) != 0:
|
|
return "DATA"
|
|
case m&pmAM != 0:
|
|
return "AM"
|
|
case m&pmFM != 0:
|
|
return "FM"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// omniRigVfo maps the OmniRig Vfo RigParamX enum to a short label, using the
|
|
// documented PM_VFO* constants.
|
|
func omniRigVfo(v int64) string {
|
|
switch {
|
|
case v&0x40 != 0: // PM_VFOAA
|
|
return "AA"
|
|
case v&0x80 != 0: // PM_VFOAB
|
|
return "AB"
|
|
case v&0x100 != 0: // PM_VFOBA
|
|
return "BA"
|
|
case v&0x200 != 0: // PM_VFOBB
|
|
return "BB"
|
|
case v&0x400 != 0: // PM_VFOA
|
|
return "A"
|
|
case v&0x800 != 0: // PM_VFOB
|
|
return "B"
|
|
}
|
|
return ""
|
|
}
|