feat(station): drive the motorized antenna from its widget
It showed pattern, elements and Retract — everything except where the antenna is pointed, which is the thing an operator changes most. Tuning it meant opening Settings or moving the rig. A button per band, taken from the bands configured in Settings rather than a list invented here: a band dropped there cannot be clicked here. They point at where people actually work, not the arithmetic centre — 20 m centres on 14175 and nobody lives there, 10 m spans 1.7 MHz of which the top half is empty. Up and down move 25 kHz, which is also the finest tracking threshold: a smaller nudge would be undone by the next poll while tracking is on. They work from the ANTENNA's frequency, not the rig's, or walking the antenna across a band while the rig stays put would be undone on the first press. Tracking moved within reach because it is an operating decision — off to park the antenna, on to resume — not something set up once. Its step only appears when it is on: a threshold for something switched off is a question the operator cannot act on. MotorTuneKHz carries the CURRENT direction rather than resetting it. Both controllers set frequency and pattern in one command, so a bare frequency would silently drop a 180° or bidirectional pattern — a beam turning round is not an acceptable side effect of clicking a band.
This commit is contained in:
@@ -14986,6 +14986,14 @@ type UltrabeamStatusInfo struct {
|
||||
Band int `json:"band"`
|
||||
Moving bool `json:"moving"`
|
||||
Elements []int `json:"elements"` // per-element lengths (mm); empty when unsupported
|
||||
// Follow and StepKHz are mirrored here so the Station Control widget can show
|
||||
// and change tracking without loading the whole settings block for a poll.
|
||||
Follow bool `json:"follow"`
|
||||
StepKHz int `json:"step_khz"`
|
||||
// Bands the antenna is configured to cover — the widget offers exactly these
|
||||
// as buttons rather than inventing its own list, so a band dropped in Settings
|
||||
// cannot be clicked here.
|
||||
Bands []string `json:"bands"`
|
||||
}
|
||||
|
||||
// GetUltrabeamStatus returns the antenna's current state for the UI poll.
|
||||
@@ -14994,6 +15002,9 @@ func (a *App) GetUltrabeamStatus() UltrabeamStatusInfo {
|
||||
s, _ := a.GetUltrabeamSettings()
|
||||
out.Enabled = s.Enabled
|
||||
out.Type = s.Type
|
||||
out.Follow = s.Follow
|
||||
out.StepKHz = s.StepKHz
|
||||
out.Bands = append(out.Bands, s.Bands...)
|
||||
if a.motorAnt == nil {
|
||||
return out
|
||||
}
|
||||
@@ -15051,6 +15062,71 @@ func (a *App) SetUltrabeamDirection(direction int) error {
|
||||
return a.motorAnt.SetDirection(direction)
|
||||
}
|
||||
|
||||
// MotorTuneKHz points the antenna at a frequency, in kHz.
|
||||
//
|
||||
// The direction is carried over from the antenna's current state rather than
|
||||
// reset: both controllers set frequency and pattern in the same command, so
|
||||
// sending a bare frequency would silently drop a 180° or bidirectional pattern
|
||||
// the operator had chosen — a beam quietly turning round is not an acceptable
|
||||
// side effect of clicking a band.
|
||||
func (a *App) MotorTuneKHz(khz int) error {
|
||||
if a.motorAnt == nil {
|
||||
return fmt.Errorf("antenna not connected — enable it in Settings → Antenna")
|
||||
}
|
||||
// A generous envelope rather than a band table: the antennas differ, some
|
||||
// carry extensions, and refusing a frequency the hardware would have accepted
|
||||
// is worse than letting the controller say no itself.
|
||||
if khz < 1000 || khz > 60000 {
|
||||
return fmt.Errorf("frequency %d kHz is outside anything these antennas tune", khz)
|
||||
}
|
||||
a.noteMotorMoveCommanded()
|
||||
return a.motorAnt.SetFrequency(khz, a.motorAnt.Status().Direction)
|
||||
}
|
||||
|
||||
// MotorNudgeKHz moves the antenna's commanded frequency by a step, up or down.
|
||||
//
|
||||
// Works from the ANTENNA's frequency, not the rig's: this exists so an operator
|
||||
// can walk the antenna across a band while the rig stays where it is, and
|
||||
// starting from the rig would undo that on the first press. Falls back to the
|
||||
// rig only when the antenna has not reported a frequency yet.
|
||||
func (a *App) MotorNudgeKHz(deltaKHz int) error {
|
||||
if a.motorAnt == nil {
|
||||
return fmt.Errorf("antenna not connected — enable it in Settings → Antenna")
|
||||
}
|
||||
cur := a.motorAnt.Status().Frequency
|
||||
if cur <= 0 && a.cat != nil {
|
||||
if rs := a.cat.State(); rs.Connected && rs.FreqHz > 0 {
|
||||
cur = int(rs.FreqHz / 1000)
|
||||
}
|
||||
}
|
||||
if cur <= 0 {
|
||||
return fmt.Errorf("the antenna has not reported a frequency yet")
|
||||
}
|
||||
return a.MotorTuneKHz(cur + deltaKHz)
|
||||
}
|
||||
|
||||
// SetMotorFollow turns frequency tracking on or off and sets its step, without
|
||||
// opening Settings. Both are ordinary operating decisions — an operator turns
|
||||
// tracking off to park the antenna and back on to resume — and a preferences
|
||||
// dialog is the wrong place for something changed that often.
|
||||
func (a *App) SetMotorFollow(on bool, stepKHz int) error {
|
||||
s, err := a.GetUltrabeamSettings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch stepKHz {
|
||||
case 25, 50, 100:
|
||||
s.StepKHz = stepKHz
|
||||
case 0: // leave the step alone
|
||||
default:
|
||||
return fmt.Errorf("step must be 25, 50 or 100 kHz")
|
||||
}
|
||||
s.Follow = on
|
||||
// SaveUltrabeamSettings restarts the client, which is what actually starts or
|
||||
// stops the follow loop — the setting alone would not.
|
||||
return a.SaveUltrabeamSettings(s)
|
||||
}
|
||||
|
||||
// UltrabeamRetract retracts all elements (storage / safe position).
|
||||
func (a *App) UltrabeamRetract() error {
|
||||
if a.motorAnt == nil {
|
||||
|
||||
Reference in New Issue
Block a user