feat: While importing ADIF, update MY fields
This commit is contained in:
@@ -78,6 +78,10 @@ type flexSlice struct {
|
||||
nrLevel int
|
||||
anf bool // auto notch filter
|
||||
anfLevel int
|
||||
apf bool // CW audio peaking filter
|
||||
apfLevel int
|
||||
filterLo int // slice filter low cut (Hz)
|
||||
filterHi int // slice filter high cut (Hz)
|
||||
}
|
||||
|
||||
// flexTX mirrors the radio's transmit/ATU/interlock objects (the SmartSDR-style
|
||||
@@ -97,6 +101,12 @@ type flexTX struct {
|
||||
micLevel int
|
||||
atuStatus string
|
||||
atuMemories bool
|
||||
// CW keyer params (set via the top-level "cw" commands).
|
||||
cwSpeed int // WPM
|
||||
cwPitch int // Hz
|
||||
cwBreakInDelay int // ms (QSK delay)
|
||||
cwSidetone bool // sidetone (audible monitor) enable
|
||||
cwMonLevel int // sidetone level (mon_gain_cw)
|
||||
}
|
||||
|
||||
// flexAmp mirrors the external amplifier object (PowerGenius XL). handle is the
|
||||
@@ -176,6 +186,7 @@ func (f *Flex) Connect() error {
|
||||
f.send("sub atu all") // antenna-tuner status + memories
|
||||
f.send("sub amplifier all") // external amplifier (PowerGenius XL) operate/standby
|
||||
f.send("sub radio all") // radio-wide incl. interlock (TX/RX state)
|
||||
f.send("sub cwx all") // CWX: the LIVE CW speed/pitch/break-in (transmit holds only a static default)
|
||||
f.startMeters(conn) // open the UDP VITA-49 stream for live meters
|
||||
if f.spotsEnabled {
|
||||
// Subscribe so the radio pushes existing spots (we learn their indices),
|
||||
@@ -364,12 +375,43 @@ func (f *Flex) handleStatus(payload string) {
|
||||
f.tx.mon = val == "1"
|
||||
case "mon_gain_sb":
|
||||
f.tx.monLevel = atoiDefault(val, f.tx.monLevel)
|
||||
case "mon_gain_cw":
|
||||
f.tx.cwMonLevel = atoiDefault(val, f.tx.cwMonLevel)
|
||||
case "sidetone", "cw_sidetone":
|
||||
f.tx.cwSidetone = val == "1"
|
||||
// NOTE: speed/pitch/break_in_delay also appear in the transmit
|
||||
// object, but they are STALE defaults there — the LIVE values come
|
||||
// from the cwx object (see the cwx branch). Parsing them here too
|
||||
// would let the stale value overwrite the correct one depending on
|
||||
// status arrival order, so we deliberately ignore them here.
|
||||
case "mic_level", "miclevel":
|
||||
f.tx.micLevel = atoiDefault(val, f.tx.micLevel)
|
||||
}
|
||||
}
|
||||
f.mu.Unlock()
|
||||
}
|
||||
// CWX object — the LIVE CW keyer values (speed/pitch/break-in delay).
|
||||
// SmartSDR reads these here; the transmit object only carries a static
|
||||
// default. Logged in full so we can confirm the exact field names.
|
||||
if len(fields) >= 1 && fields[0] == "cwx" {
|
||||
debugLog.Printf("Flex: status %s", payload)
|
||||
f.mu.Lock()
|
||||
for _, kv := range fields[1:] {
|
||||
key, val, ok := splitKV(kv)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "wpm", "speed", "cw_speed":
|
||||
f.tx.cwSpeed = atoiDefault(val, f.tx.cwSpeed)
|
||||
case "pitch", "cw_pitch":
|
||||
f.tx.cwPitch = atoiDefault(val, f.tx.cwPitch)
|
||||
case "delay", "break_in_delay", "cw_break_in_delay":
|
||||
f.tx.cwBreakInDelay = atoiDefault(val, f.tx.cwBreakInDelay)
|
||||
}
|
||||
}
|
||||
f.mu.Unlock()
|
||||
}
|
||||
// ATU object — auto-tuner status + memories.
|
||||
if len(fields) >= 1 && fields[0] == "atu" {
|
||||
debugLog.Printf("Flex: status %s", payload)
|
||||
@@ -425,6 +467,16 @@ func (f *Flex) handleStatus(payload string) {
|
||||
f.amp.operate = val == "1" || strings.EqualFold(val, "OPERATE")
|
||||
case "mode":
|
||||
f.amp.operate = strings.EqualFold(val, "OPERATE")
|
||||
case "state":
|
||||
// The PowerGenius XL reports its live state here (the status
|
||||
// push has no operate= field). Anything but STANDBY/OFF means
|
||||
// the amp is IN LINE (OPERATE) — IDLE = operate, not keyed.
|
||||
switch strings.ToUpper(val) {
|
||||
case "STANDBY", "OFF", "POWERED_OFF", "DISCONNECTED":
|
||||
f.amp.operate = false
|
||||
case "OPERATE", "IDLE", "TRANSMIT", "TX", "RECEIVE", "RX", "KEYED", "OPERATING":
|
||||
f.amp.operate = true
|
||||
}
|
||||
case "fault":
|
||||
f.amp.fault = val
|
||||
}
|
||||
@@ -582,11 +634,28 @@ func (f *Flex) handleStatus(payload string) {
|
||||
s.anf = val == "1"
|
||||
case "anf_level":
|
||||
s.anfLevel = atoiDefault(val, s.anfLevel)
|
||||
case "apf":
|
||||
s.apf = val == "1"
|
||||
case "apf_level":
|
||||
s.apfLevel = atoiDefault(val, s.apfLevel)
|
||||
case "filter_lo":
|
||||
s.filterLo = atoiDefault(val, s.filterLo)
|
||||
case "filter_hi":
|
||||
s.filterHi = atoiDefault(val, s.filterHi)
|
||||
}
|
||||
}
|
||||
f.mu.Unlock()
|
||||
}
|
||||
|
||||
// defInt returns v, or def when v is zero (so sliders show sane defaults before
|
||||
// the radio has pushed the real value).
|
||||
func defInt(v, def int) int {
|
||||
if v == 0 {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ReadState returns the cached state derived from the radio's push messages —
|
||||
// no round-trip, so it's always current.
|
||||
func (f *Flex) ReadState() (RigState, error) {
|
||||
@@ -899,9 +968,16 @@ func (f *Flex) FlexState() FlexTXState {
|
||||
MicLevel: f.tx.micLevel,
|
||||
ATUStatus: f.tx.atuStatus,
|
||||
ATUMemories: f.tx.atuMemories,
|
||||
// CW keyer (defaults applied so the sliders show sane values pre-read).
|
||||
CWSpeed: defInt(f.tx.cwSpeed, 25),
|
||||
CWPitch: defInt(f.tx.cwPitch, 600),
|
||||
CWBreakInDelay: defInt(f.tx.cwBreakInDelay, 30),
|
||||
CWSidetone: f.tx.cwSidetone,
|
||||
CWMonLevel: f.tx.cwMonLevel,
|
||||
}
|
||||
if _, rx := f.rxSliceLocked(); rx != nil {
|
||||
st.RXAvail = true
|
||||
st.Mode = strings.ToUpper(rx.mode)
|
||||
st.AGCMode = rx.agcMode
|
||||
st.AGCThreshold = rx.agcThreshold
|
||||
st.AudioLevel = rx.audioLevel
|
||||
@@ -911,6 +987,10 @@ func (f *Flex) FlexState() FlexTXState {
|
||||
st.NRLevel = rx.nrLevel
|
||||
st.ANF = rx.anf
|
||||
st.ANFLevel = rx.anfLevel
|
||||
st.APF = rx.apf
|
||||
st.APFLevel = rx.apfLevel
|
||||
st.FilterLo = rx.filterLo
|
||||
st.FilterHi = rx.filterHi
|
||||
}
|
||||
if f.amp.handle != "" {
|
||||
st.AmpAvailable = true
|
||||
@@ -960,6 +1040,10 @@ func (f *Flex) sendSlice(param string, val any) error {
|
||||
rx.anf = val == "1"
|
||||
case "anf_level":
|
||||
rx.anfLevel = toInt(val)
|
||||
case "apf":
|
||||
rx.apf = val == "1"
|
||||
case "apf_level":
|
||||
rx.apfLevel = toInt(val)
|
||||
}
|
||||
}
|
||||
f.mu.Unlock()
|
||||
@@ -1000,6 +1084,104 @@ func (f *Flex) SetNR(on bool) error { return f.sendSlice("nr", boolFlex(
|
||||
func (f *Flex) SetNRLevel(l int) error { return f.sendSlice("nr_level", clampLevel(l)) }
|
||||
func (f *Flex) SetANF(on bool) error { return f.sendSlice("anf", boolFlex(on)) }
|
||||
func (f *Flex) SetANFLevel(l int) error { return f.sendSlice("anf_level", clampLevel(l)) }
|
||||
func (f *Flex) SetAPF(on bool) error { return f.sendSlice("apf", boolFlex(on)) }
|
||||
func (f *Flex) SetAPFLevel(l int) error { return f.sendSlice("apf_level", clampLevel(l)) }
|
||||
|
||||
// ── CW keyer controls (top-level "cw" commands) ──
|
||||
|
||||
func (f *Flex) SetCWSpeed(wpm int) error {
|
||||
if wpm < 5 {
|
||||
wpm = 5
|
||||
} else if wpm > 100 {
|
||||
wpm = 100
|
||||
}
|
||||
f.mu.Lock()
|
||||
f.tx.cwSpeed = wpm
|
||||
f.mu.Unlock()
|
||||
f.send(fmt.Sprintf("cw wpm %d", wpm))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Flex) SetCWPitch(hz int) error {
|
||||
if hz < 100 {
|
||||
hz = 100
|
||||
} else if hz > 6000 {
|
||||
hz = 6000
|
||||
}
|
||||
f.mu.Lock()
|
||||
f.tx.cwPitch = hz
|
||||
f.mu.Unlock()
|
||||
f.send(fmt.Sprintf("cw pitch %d", hz))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Flex) SetCWBreakInDelay(ms int) error {
|
||||
if ms < 0 {
|
||||
ms = 0
|
||||
} else if ms > 2000 {
|
||||
ms = 2000
|
||||
}
|
||||
f.mu.Lock()
|
||||
f.tx.cwBreakInDelay = ms
|
||||
f.mu.Unlock()
|
||||
f.send(fmt.Sprintf("cw break_in_delay %d", ms))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Flex) SetCWSidetone(on bool) error {
|
||||
f.mu.Lock()
|
||||
f.tx.cwSidetone = on
|
||||
f.mu.Unlock()
|
||||
f.send("cw sidetone " + boolWord(on))
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSidetoneLevel sets the CW sidetone (audible monitor) gain via mon_gain_cw.
|
||||
func (f *Flex) SetSidetoneLevel(l int) error {
|
||||
l = clampLevel(l)
|
||||
return f.txSet(fmt.Sprintf("transmit set mon_gain_cw=%d", l), "mon_gain_cw", func(t *flexTX) { t.cwMonLevel = l })
|
||||
}
|
||||
|
||||
// SetCWFilter changes the CW passband WIDTH to bw Hz while keeping the current
|
||||
// filter CENTER fixed — so the frequency never shifts. The new low/high cuts are
|
||||
// center ± bw/2, where center is the midpoint of the slice's current filter
|
||||
// (falling back to the CW pitch only if the filter isn't known yet).
|
||||
func (f *Flex) SetCWFilter(bw int) error {
|
||||
if bw < 50 {
|
||||
bw = 50
|
||||
}
|
||||
f.mu.Lock()
|
||||
idx, rx := f.rxSliceLocked()
|
||||
connected := f.conn != nil
|
||||
center := 0
|
||||
if rx != nil && (rx.filterLo != 0 || rx.filterHi != 0) {
|
||||
center = (rx.filterLo + rx.filterHi) / 2
|
||||
} else {
|
||||
center = defInt(f.tx.cwPitch, 600)
|
||||
}
|
||||
lo := center - bw/2
|
||||
hi := center + bw/2
|
||||
if rx != nil {
|
||||
rx.filterLo, rx.filterHi = lo, hi
|
||||
}
|
||||
f.mu.Unlock()
|
||||
if !connected {
|
||||
return fmt.Errorf("flex: not connected")
|
||||
}
|
||||
if rx == nil || idx < 0 {
|
||||
return fmt.Errorf("flex: no receive slice")
|
||||
}
|
||||
f.send(fmt.Sprintf("filt %d %d %d", idx, lo, hi))
|
||||
return nil
|
||||
}
|
||||
|
||||
// boolWord renders a Flex on/off boolean as the word form some commands want.
|
||||
func boolWord(on bool) string {
|
||||
if on {
|
||||
return "on"
|
||||
}
|
||||
return "off"
|
||||
}
|
||||
|
||||
// connected reports whether the TCP link is up (commands are no-ops otherwise).
|
||||
func (f *Flex) connected() bool {
|
||||
|
||||
Reference in New Issue
Block a user