feat: multiple amplifiers + SmartSDR v4 DSP filters
Multi-amp: Settings->Amplifier becomes a list (amps.json, legacy single-amp keys auto-migrate to entry #1); one client per enabled amp with per-id bindings (GetAmplifiers/SaveAmplifiers/GetAmpStatuses/AmpOperate/AmpPower/AmpPowerLevel/AmpFanMode); legacy a.pgxl/a.spe/a.acom point at the first enabled amp of each family. Amp cards in FlexPanel and Station Control gain a dropdown to pick the amp; the status bar shows one clickable chip per amp. Use case: two SPEs run in parallel. Flex v4 DSP (8000/Aurora): NRL/ANFL (lms_nr/lms_anf), NRS (speex_nr), NRF (nrf) with level sliders, RNN (rnnoise) and ANFT on/off — keys per the FlexLib slice docs; the section only shows when the radio reports these keys (dsp_v4 flag), so 6000-series panels are unchanged.
This commit is contained in:
+96
-16
@@ -30,10 +30,10 @@ type Flex struct {
|
||||
conn net.Conn
|
||||
dialCancel context.CancelFunc // cancels an in-flight Connect dial (Interrupt/Stop); nil when not dialing
|
||||
wmu sync.Mutex // serialises writes to conn
|
||||
seq int
|
||||
handle string
|
||||
model string
|
||||
gotHandle bool
|
||||
seq int
|
||||
handle string
|
||||
model string
|
||||
gotHandle bool
|
||||
|
||||
slices map[int]*flexSlice
|
||||
tx flexTX // transmit/ATU state pushed by the radio (FlexRadio tab)
|
||||
@@ -90,12 +90,27 @@ type flexSlice struct {
|
||||
apfLevel int
|
||||
wnb bool // wideband noise blanker
|
||||
wnbLevel int
|
||||
rit bool // receive incremental tuning enabled
|
||||
ritFreq int // RIT offset in Hz (negative = down)
|
||||
xit bool // transmit incremental tuning enabled
|
||||
xitFreq int // XIT offset in Hz
|
||||
filterLo int // slice filter low cut (Hz)
|
||||
filterHi int // slice filter high cut (Hz)
|
||||
// SmartSDR v4 DSP (8000/Aurora series). Key names per the FlexLib slice
|
||||
// docs: lms_nr(NRL) / lms_anf(ANFL) / speex_nr(NRS) / rnnoise(RNN) /
|
||||
// anft(ANFT) / nrf(NRF). Older radios never report them — dspV4 flips true
|
||||
// the first time any of these keys appears, and gates the extra UI rows.
|
||||
lmsNR bool // NRL — legacy LMS noise reduction
|
||||
lmsNRLevel int
|
||||
lmsANF bool // ANFL — legacy LMS auto-notch
|
||||
lmsANFLevel int
|
||||
speexNR bool // NRS — spectral subtraction NR
|
||||
speexNRLevel int
|
||||
rnnoise bool // RNN — AI noise reduction (on/off only)
|
||||
anft bool // ANFT — FFT-based auto-notch (on/off only)
|
||||
nrf bool // NRF — noise reduction w/ filter
|
||||
nrfLevel int
|
||||
dspV4 bool
|
||||
rit bool // receive incremental tuning enabled
|
||||
ritFreq int // RIT offset in Hz (negative = down)
|
||||
xit bool // transmit incremental tuning enabled
|
||||
xitFreq int // XIT offset in Hz
|
||||
filterLo int // slice filter low cut (Hz)
|
||||
filterHi int // slice filter high cut (Hz)
|
||||
rxAnt string // selected RX antenna (e.g. ANT1, ANT2, RX_A)
|
||||
txAnt string // selected TX antenna
|
||||
antList []string // antennas valid for this slice (RX side)
|
||||
@@ -809,6 +824,26 @@ func (f *Flex) handleStatus(payload string) {
|
||||
s.wnb = val == "1"
|
||||
case "wnb_level":
|
||||
s.wnbLevel = atoiDefault(val, s.wnbLevel)
|
||||
case "lms_nr":
|
||||
s.lmsNR, s.dspV4 = val == "1", true
|
||||
case "lms_nr_level":
|
||||
s.lmsNRLevel, s.dspV4 = atoiDefault(val, s.lmsNRLevel), true
|
||||
case "lms_anf":
|
||||
s.lmsANF, s.dspV4 = val == "1", true
|
||||
case "lms_anf_level":
|
||||
s.lmsANFLevel, s.dspV4 = atoiDefault(val, s.lmsANFLevel), true
|
||||
case "speex_nr":
|
||||
s.speexNR, s.dspV4 = val == "1", true
|
||||
case "speex_nr_level":
|
||||
s.speexNRLevel, s.dspV4 = atoiDefault(val, s.speexNRLevel), true
|
||||
case "rnnoise":
|
||||
s.rnnoise, s.dspV4 = val == "1", true
|
||||
case "anft":
|
||||
s.anft, s.dspV4 = val == "1", true
|
||||
case "nrf":
|
||||
s.nrf, s.dspV4 = val == "1", true
|
||||
case "nrf_level":
|
||||
s.nrfLevel, s.dspV4 = atoiDefault(val, s.nrfLevel), true
|
||||
case "rit_on":
|
||||
s.rit = val == "1"
|
||||
case "rit_freq":
|
||||
@@ -1328,6 +1363,17 @@ func (f *Flex) FlexState() FlexTXState {
|
||||
st.APFLevel = rx.apfLevel
|
||||
st.WNB = rx.wnb
|
||||
st.WNBLevel = rx.wnbLevel
|
||||
st.LMSNR = rx.lmsNR
|
||||
st.LMSNRLevel = rx.lmsNRLevel
|
||||
st.LMSANF = rx.lmsANF
|
||||
st.LMSANFLevel = rx.lmsANFLevel
|
||||
st.SpeexNR = rx.speexNR
|
||||
st.SpeexNRLevel = rx.speexNRLevel
|
||||
st.RNN = rx.rnnoise
|
||||
st.ANFT = rx.anft
|
||||
st.NRF = rx.nrf
|
||||
st.NRFLevel = rx.nrfLevel
|
||||
st.DSPV4 = rx.dspV4
|
||||
st.RIT = rx.rit
|
||||
st.RITFreq = rx.ritFreq
|
||||
st.XIT = rx.xit
|
||||
@@ -1396,6 +1442,26 @@ func (f *Flex) sendSlice(param string, val any) error {
|
||||
rx.apf = val == "1"
|
||||
case "apf_level":
|
||||
rx.apfLevel = toInt(val)
|
||||
case "lms_nr":
|
||||
rx.lmsNR = val == "1"
|
||||
case "lms_nr_level":
|
||||
rx.lmsNRLevel = toInt(val)
|
||||
case "lms_anf":
|
||||
rx.lmsANF = val == "1"
|
||||
case "lms_anf_level":
|
||||
rx.lmsANFLevel = toInt(val)
|
||||
case "speex_nr":
|
||||
rx.speexNR = val == "1"
|
||||
case "speex_nr_level":
|
||||
rx.speexNRLevel = toInt(val)
|
||||
case "rnnoise":
|
||||
rx.rnnoise = val == "1"
|
||||
case "anft":
|
||||
rx.anft = val == "1"
|
||||
case "nrf":
|
||||
rx.nrf = val == "1"
|
||||
case "nrf_level":
|
||||
rx.nrfLevel = toInt(val)
|
||||
case "rxant":
|
||||
rx.rxAnt = fmt.Sprint(val)
|
||||
case "txant":
|
||||
@@ -1515,14 +1581,15 @@ func (f *Flex) SetNR(on bool) error { return f.sendSlice("nr", boolFlex(on))
|
||||
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)) }
|
||||
|
||||
// RIT/XIT — an offset applied to the RX (RIT) or TX (XIT) frequency of the active
|
||||
// slice, without moving the slice itself. SmartSDR keeps the offset even while the
|
||||
// switch is off, so turning RIT back on restores the last offset — same as the
|
||||
// radio's own knob.
|
||||
func (f *Flex) SetRIT(on bool) error { return f.sendSlice("rit_on", boolFlex(on)) }
|
||||
func (f *Flex) SetRITFreq(hz int) error { return f.sendSlice("rit_freq", clampOffset(hz)) }
|
||||
func (f *Flex) SetXIT(on bool) error { return f.sendSlice("xit_on", boolFlex(on)) }
|
||||
func (f *Flex) SetXITFreq(hz int) error { return f.sendSlice("xit_freq", clampOffset(hz)) }
|
||||
func (f *Flex) SetRIT(on bool) error { return f.sendSlice("rit_on", boolFlex(on)) }
|
||||
func (f *Flex) SetRITFreq(hz int) error { return f.sendSlice("rit_freq", clampOffset(hz)) }
|
||||
func (f *Flex) SetXIT(on bool) error { return f.sendSlice("xit_on", boolFlex(on)) }
|
||||
func (f *Flex) SetXITFreq(hz int) error { return f.sendSlice("xit_freq", clampOffset(hz)) }
|
||||
|
||||
// clampOffset keeps a RIT/XIT offset inside what SmartSDR accepts (±99 999 Hz).
|
||||
func clampOffset(hz int) int {
|
||||
@@ -1537,8 +1604,21 @@ func clampOffset(hz int) int {
|
||||
|
||||
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)) }
|
||||
func (f *Flex) SetWNB(on bool) error { return f.sendSlice("wnb", boolFlex(on)) }
|
||||
func (f *Flex) SetWNBLevel(l int) error { return f.sendSlice("wnb_level", clampLevel(l)) }
|
||||
|
||||
// SmartSDR v4 DSP (8000/Aurora series) — the extra noise tools SmartSDR ships
|
||||
// beyond nb/nr/anf/wnb. Key names per the FlexLib slice command docs.
|
||||
func (f *Flex) SetLMSNR(on bool) error { return f.sendSlice("lms_nr", boolFlex(on)) }
|
||||
func (f *Flex) SetLMSNRLevel(l int) error { return f.sendSlice("lms_nr_level", clampLevel(l)) }
|
||||
func (f *Flex) SetLMSANF(on bool) error { return f.sendSlice("lms_anf", boolFlex(on)) }
|
||||
func (f *Flex) SetLMSANFLevel(l int) error { return f.sendSlice("lms_anf_level", clampLevel(l)) }
|
||||
func (f *Flex) SetSpeexNR(on bool) error { return f.sendSlice("speex_nr", boolFlex(on)) }
|
||||
func (f *Flex) SetSpeexNRLevel(l int) error { return f.sendSlice("speex_nr_level", clampLevel(l)) }
|
||||
func (f *Flex) SetRNN(on bool) error { return f.sendSlice("rnnoise", boolFlex(on)) }
|
||||
func (f *Flex) SetANFT(on bool) error { return f.sendSlice("anft", boolFlex(on)) }
|
||||
func (f *Flex) SetNRF(on bool) error { return f.sendSlice("nrf", boolFlex(on)) }
|
||||
func (f *Flex) SetNRFLevel(l int) error { return f.sendSlice("nrf_level", clampLevel(l)) }
|
||||
func (f *Flex) SetWNB(on bool) error { return f.sendSlice("wnb", boolFlex(on)) }
|
||||
func (f *Flex) SetWNBLevel(l int) error { return f.sendSlice("wnb_level", clampLevel(l)) }
|
||||
|
||||
// ── CW keyer controls (top-level "cw" commands) ──
|
||||
|
||||
|
||||
Reference in New Issue
Block a user