up
This commit is contained in:
@@ -127,6 +127,8 @@ const (
|
||||
keyUltrabeamEnabled = "ultrabeam.enabled"
|
||||
keyUltrabeamHost = "ultrabeam.host"
|
||||
keyUltrabeamPort = "ultrabeam.port"
|
||||
keyUltrabeamFollow = "ultrabeam.follow" // "1" → re-tune to the rig frequency
|
||||
keyUltrabeamStep = "ultrabeam.step_khz" // re-tune hysteresis: 25 | 50 | 100 kHz
|
||||
|
||||
// WinKeyer CW keyer (serial) — Hardware → CW Keyer.
|
||||
keyWKEnabled = "winkeyer.enabled"
|
||||
@@ -349,9 +351,10 @@ type App struct {
|
||||
udpRepo *udp.Repo
|
||||
extsvc *extsvc.Manager
|
||||
winkeyer *winkeyer.Manager
|
||||
clublog *clublog.Manager
|
||||
ultrabeam *ultrabeam.Client // Ultrabeam antenna (TCP); nil when disabled
|
||||
audioMgr *audio.Manager
|
||||
clublog *clublog.Manager
|
||||
ultrabeam *ultrabeam.Client // Ultrabeam antenna (TCP); nil when disabled
|
||||
ubFollowStop chan struct{} // stops the "follow frequency" loop; nil when off
|
||||
audioMgr *audio.Manager
|
||||
qsoRec *audio.Recorder // continuous QSO recorder (rolling pre-roll)
|
||||
dvkRecSlot int // slot currently being recorded (DVKStartRecord → DVKStopRecord)
|
||||
dvkPttKeyed bool // we keyed PTT for a voice message; unkey when it ends
|
||||
@@ -3969,6 +3972,7 @@ func (a *App) pttKey(cfg AudioSettings) error {
|
||||
return fmt.Errorf("CAT not initialized")
|
||||
}
|
||||
if err := a.cat.SetPTT(true); err != nil {
|
||||
applog.Printf("ptt: CAT SetPTT failed: %v", err)
|
||||
return err
|
||||
}
|
||||
a.pttMu.Lock()
|
||||
@@ -4045,6 +4049,11 @@ func (a *App) pttUnkey() {
|
||||
// UI selection), so the user can test a method/port without saving first —
|
||||
// matching TestRotator / TestUltrabeam.
|
||||
func (a *App) TestPTT(cfg AudioSettings) error {
|
||||
if cfg.PTTMethod == "rts" || cfg.PTTMethod == "dtr" {
|
||||
applog.Printf("ptt: TestPTT method=%q port=%q", cfg.PTTMethod, cfg.PTTPort)
|
||||
} else {
|
||||
applog.Printf("ptt: TestPTT method=%q (CAT via OmniRig — serial port not used)", cfg.PTTMethod)
|
||||
}
|
||||
if cfg.PTTMethod == "" || cfg.PTTMethod == "none" {
|
||||
return fmt.Errorf("PTT method is None (VOX) — pick CAT, RTS or DTR first")
|
||||
}
|
||||
@@ -6005,15 +6014,17 @@ type UltrabeamSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
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)
|
||||
}
|
||||
|
||||
// GetUltrabeamSettings returns the persisted Ultrabeam config with defaults.
|
||||
func (a *App) GetUltrabeamSettings() (UltrabeamSettings, error) {
|
||||
out := UltrabeamSettings{Port: 23}
|
||||
out := UltrabeamSettings{Port: 23, StepKHz: 50}
|
||||
if a.settings == nil {
|
||||
return out, fmt.Errorf("db not initialized")
|
||||
}
|
||||
m, err := a.settings.GetMany(a.ctx, keyUltrabeamEnabled, keyUltrabeamHost, keyUltrabeamPort)
|
||||
m, err := a.settings.GetMany(a.ctx, keyUltrabeamEnabled, keyUltrabeamHost, keyUltrabeamPort, keyUltrabeamFollow, keyUltrabeamStep)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
@@ -6022,6 +6033,10 @@ func (a *App) GetUltrabeamSettings() (UltrabeamSettings, error) {
|
||||
if p, _ := strconv.Atoi(m[keyUltrabeamPort]); p > 0 && p <= 65535 {
|
||||
out.Port = p
|
||||
}
|
||||
out.Follow = m[keyUltrabeamFollow] == "1"
|
||||
if st, _ := strconv.Atoi(m[keyUltrabeamStep]); st == 25 || st == 50 || st == 100 {
|
||||
out.StepKHz = st
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -6034,10 +6049,15 @@ func (a *App) SaveUltrabeamSettings(s UltrabeamSettings) error {
|
||||
if s.Port <= 0 || s.Port > 65535 {
|
||||
s.Port = 23
|
||||
}
|
||||
if s.StepKHz != 25 && s.StepKHz != 50 && s.StepKHz != 100 {
|
||||
s.StepKHz = 50
|
||||
}
|
||||
for k, v := range map[string]string{
|
||||
keyUltrabeamEnabled: boolStr(s.Enabled),
|
||||
keyUltrabeamHost: strings.TrimSpace(s.Host),
|
||||
keyUltrabeamPort: strconv.Itoa(s.Port),
|
||||
keyUltrabeamFollow: boolStr(s.Follow),
|
||||
keyUltrabeamStep: strconv.Itoa(s.StepKHz),
|
||||
} {
|
||||
if err := a.settings.Set(a.ctx, k, v); err != nil {
|
||||
return err
|
||||
@@ -6051,6 +6071,11 @@ func (a *App) SaveUltrabeamSettings(s UltrabeamSettings) error {
|
||||
// antenna is enabled and configured. Safe to call repeatedly (on startup and
|
||||
// after a settings save).
|
||||
func (a *App) startUltrabeam() {
|
||||
// Stop any running follow loop first.
|
||||
if a.ubFollowStop != nil {
|
||||
close(a.ubFollowStop)
|
||||
a.ubFollowStop = nil
|
||||
}
|
||||
if a.ultrabeam != nil {
|
||||
a.ultrabeam.Stop()
|
||||
a.ultrabeam = nil
|
||||
@@ -6061,6 +6086,61 @@ func (a *App) startUltrabeam() {
|
||||
}
|
||||
a.ultrabeam = ultrabeam.New(s.Host, s.Port)
|
||||
_ = a.ultrabeam.Start()
|
||||
if s.Follow {
|
||||
stop := make(chan struct{})
|
||||
a.ubFollowStop = stop
|
||||
go a.ultrabeamFollowLoop(a.ultrabeam, s.StepKHz, stop)
|
||||
}
|
||||
}
|
||||
|
||||
// ultrabeamFollowLoop re-tunes the antenna to the rig's current frequency
|
||||
// whenever it drifts at least stepKHz from what the antenna is set to — so the
|
||||
// elements track the band without the motors chasing every small QSY. Runs
|
||||
// until stop is closed (a settings change or shutdown).
|
||||
func (a *App) ultrabeamFollowLoop(c *ultrabeam.Client, stepKHz int, stop <-chan struct{}) {
|
||||
if stepKHz <= 0 {
|
||||
stepKHz = 50
|
||||
}
|
||||
ticker := time.NewTicker(1500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if a.cat == nil {
|
||||
continue
|
||||
}
|
||||
rs := a.cat.State()
|
||||
if !rs.Connected || rs.FreqHz <= 0 {
|
||||
continue
|
||||
}
|
||||
st, err := c.GetStatus()
|
||||
if err != nil || st == nil || !st.Connected {
|
||||
continue
|
||||
}
|
||||
rigKHz := int(rs.FreqHz / 1000)
|
||||
// Skip frequencies outside the antenna's tunable range (other band).
|
||||
if st.FreqMin > 0 && st.FreqMax > 0 {
|
||||
rigMHz := rs.FreqHz / 1_000_000
|
||||
if rigMHz < int64(st.FreqMin) || rigMHz > int64(st.FreqMax) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
diff := rigKHz - st.Frequency
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
if st.Frequency > 0 && diff < stepKHz {
|
||||
continue // within the deadband — leave the motors alone
|
||||
}
|
||||
if err := c.SetFrequency(rigKHz, st.Direction); err != nil {
|
||||
applog.Printf("ultrabeam: follow re-tune to %d kHz failed: %v", rigKHz, err)
|
||||
} else {
|
||||
applog.Printf("ultrabeam: followed rig → %d kHz (dir %d)", rigKHz, st.Direction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UltrabeamStatusInfo is the live antenna status for the UI (status bar +
|
||||
|
||||
Reference in New Issue
Block a user