feat: configurable SteppIR tunable range — skip follow/inhibit off-band
A SteppIR only covers part of the spectrum (a standard one is 20 m–6 m) but can't report its own range, so the follow loop's existing out-of-range guard (FreqMin/FreqMax) never engaged for it. Tuning the rig to a band the antenna can't reach (e.g. 30 m) made the loop keep trying — the immediate cause of the stuck TX interlock addressed in the previous commit. Add a Tunable range (MHz) setting to the Antenna panel, wired into the SteppIR adapter's Status().FreqMin/FreqMax so the follow loop skips out-of-range frequencies entirely: no tune command, no TX inhibit. Defaults to 13–54 MHz (20 m–6 m, the common model) when unset; widen the low edge for a 40 m-equipped SteppIR. Ultrabeam is unaffected (it reports its own per-band coverage). Keeps the last-commanded-freq deadband from the previous commit as a universal safety net for any flaky-status case within the valid range.
This commit is contained in:
@@ -180,6 +180,8 @@ const (
|
||||
keyMotorCOM = "ultrabeam.com" // serial device name (COM3, /dev/ttyUSB0)
|
||||
keyMotorBaud = "ultrabeam.baud" // serial baud (SteppIR default 9600)
|
||||
keyMotorTXInhibit = "ultrabeam.tx_inhibit" // "1" → block Flex TX while the antenna is moving
|
||||
keyMotorFreqMin = "ultrabeam.freq_min" // SteppIR tunable range low edge (MHz); out-of-range = don't follow/inhibit
|
||||
keyMotorFreqMax = "ultrabeam.freq_max" // SteppIR tunable range high edge (MHz)
|
||||
keyStationDevices = "station.devices" // JSON list of relay boards for the Station Control tab
|
||||
|
||||
// Antenna Genius (4O3A) antenna switch — Hardware → Antenna Genius. TCP
|
||||
@@ -12214,7 +12216,10 @@ func (a ubAdapter) Status() motorStatus {
|
||||
return motorStatus{Connected: st.Connected, Direction: st.Direction, Frequency: st.Frequency, Band: st.Band, Moving: st.MotorsMoving != 0, FreqMin: st.FreqMin, FreqMax: st.FreqMax}
|
||||
}
|
||||
|
||||
type steppirAdapter struct{ c *steppir.Client }
|
||||
type steppirAdapter struct {
|
||||
c *steppir.Client
|
||||
freqMin, freqMax int // MHz; tunable range (the SteppIR can't report its own)
|
||||
}
|
||||
|
||||
func (a steppirAdapter) Start() error { return a.c.Start() }
|
||||
func (a steppirAdapter) Stop() { a.c.Stop() }
|
||||
@@ -12234,7 +12239,7 @@ func (a steppirAdapter) Status() motorStatus {
|
||||
if err != nil || st == nil {
|
||||
return motorStatus{}
|
||||
}
|
||||
return motorStatus{Connected: st.Connected, Direction: st.Direction, Frequency: st.Frequency, Band: st.Band, Moving: st.MotorsMoving != 0}
|
||||
return motorStatus{Connected: st.Connected, Direction: st.Direction, Frequency: st.Frequency, Band: st.Band, Moving: st.MotorsMoving != 0, FreqMin: a.freqMin, FreqMax: a.freqMax}
|
||||
}
|
||||
|
||||
// UltrabeamSettings is the JSON shape for the Hardware → Antenna panel. The name
|
||||
@@ -12250,6 +12255,12 @@ type UltrabeamSettings struct {
|
||||
Follow bool `json:"follow"` // re-tune the antenna to the rig's frequency
|
||||
StepKHz int `json:"step_khz"` // re-tune only when the freq moved this far (25/50/100)
|
||||
TXInhibit bool `json:"tx_inhibit"` // block Flex transmission while the elements are moving
|
||||
// SteppIR tunable range (MHz). The follow loop skips frequencies outside it —
|
||||
// no tune command AND no TX inhibit — so a band the antenna can't cover (e.g.
|
||||
// 30 m on a 20 m–6 m SteppIR) never traps TX. Ignored for the Ultrabeam, which
|
||||
// reports its own per-band coverage. 0 = defaults filled in on load for SteppIR.
|
||||
FreqMinMHz int `json:"freq_min_mhz"`
|
||||
FreqMaxMHz int `json:"freq_max_mhz"`
|
||||
}
|
||||
|
||||
// GetUltrabeamSettings returns the persisted motorized-antenna config, defaulting
|
||||
@@ -12261,7 +12272,7 @@ func (a *App) GetUltrabeamSettings() (UltrabeamSettings, error) {
|
||||
return out, fmt.Errorf("db not initialized")
|
||||
}
|
||||
m, err := a.settings.GetMany(a.ctx, keyUltrabeamEnabled, keyUltrabeamHost, keyUltrabeamPort, keyUltrabeamFollow, keyUltrabeamStep,
|
||||
keyMotorType, keyMotorTransport, keyMotorCOM, keyMotorBaud, keyMotorTXInhibit)
|
||||
keyMotorType, keyMotorTransport, keyMotorCOM, keyMotorBaud, keyMotorTXInhibit, keyMotorFreqMin, keyMotorFreqMax)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
@@ -12285,6 +12296,20 @@ func (a *App) GetUltrabeamSettings() (UltrabeamSettings, error) {
|
||||
if st, _ := strconv.Atoi(m[keyUltrabeamStep]); st == 25 || st == 50 || st == 100 {
|
||||
out.StepKHz = st
|
||||
}
|
||||
out.FreqMinMHz, _ = strconv.Atoi(m[keyMotorFreqMin])
|
||||
out.FreqMaxMHz, _ = strconv.Atoi(m[keyMotorFreqMax])
|
||||
// A SteppIR doesn't report its coverage, so default to the standard 20 m–6 m
|
||||
// range (13–54 MHz) when unset — the common model. Widen it (e.g. min 6 for a
|
||||
// 40 m-equipped SteppIR) in Settings. This is what lets the follow loop leave
|
||||
// TX alone on a band the antenna can't reach.
|
||||
if out.Type == "steppir" {
|
||||
if out.FreqMinMHz <= 0 {
|
||||
out.FreqMinMHz = 13
|
||||
}
|
||||
if out.FreqMaxMHz <= 0 {
|
||||
out.FreqMaxMHz = 54
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -12309,6 +12334,16 @@ func (a *App) SaveUltrabeamSettings(s UltrabeamSettings) error {
|
||||
if s.Baud < 1200 || s.Baud > 115200 {
|
||||
s.Baud = 9600
|
||||
}
|
||||
// Sanity: keep the range ordered; 0 = "no limit" (stored as-is).
|
||||
if s.FreqMinMHz < 0 {
|
||||
s.FreqMinMHz = 0
|
||||
}
|
||||
if s.FreqMaxMHz < 0 {
|
||||
s.FreqMaxMHz = 0
|
||||
}
|
||||
if s.FreqMinMHz > 0 && s.FreqMaxMHz > 0 && s.FreqMinMHz > s.FreqMaxMHz {
|
||||
s.FreqMinMHz, s.FreqMaxMHz = s.FreqMaxMHz, s.FreqMinMHz
|
||||
}
|
||||
for k, v := range map[string]string{
|
||||
keyUltrabeamEnabled: boolStr(s.Enabled),
|
||||
keyUltrabeamHost: strings.TrimSpace(s.Host),
|
||||
@@ -12320,6 +12355,8 @@ func (a *App) SaveUltrabeamSettings(s UltrabeamSettings) error {
|
||||
keyMotorCOM: strings.TrimSpace(s.COM),
|
||||
keyMotorBaud: strconv.Itoa(s.Baud),
|
||||
keyMotorTXInhibit: boolStr(s.TXInhibit),
|
||||
keyMotorFreqMin: strconv.Itoa(s.FreqMinMHz),
|
||||
keyMotorFreqMax: strconv.Itoa(s.FreqMaxMHz),
|
||||
} {
|
||||
if err := a.settings.Set(a.ctx, k, v); err != nil {
|
||||
return err
|
||||
@@ -12341,7 +12378,7 @@ func newMotorClient(s UltrabeamSettings) motorAntenna {
|
||||
if tr.Mode != "serial" && tr.Host == "" {
|
||||
return nil
|
||||
}
|
||||
return steppirAdapter{steppir.New(tr)}
|
||||
return steppirAdapter{c: steppir.New(tr), freqMin: s.FreqMinMHz, freqMax: s.FreqMaxMHz}
|
||||
}
|
||||
// Ultrabeam is TCP only.
|
||||
if strings.TrimSpace(s.Host) == "" {
|
||||
|
||||
Reference in New Issue
Block a user