feat(sat): follow the azimuth only
A satellite tracker that insists on an elevation motor is a tracker switched off for nearly everybody. A pass at the edge of the footprint — which is most of them — never climbs above ten or fifteen degrees for its whole length, and a yagi's beamwidth swallows that: the bearing alone is enough, and it is how most stations that work satellites are actually built. The same switch rescues an az/el station whose elevation motor has failed. So it is an option, not a silent fallback, because it does cost something: a bird straight overhead is a moving azimuth and a bearing that means nothing, and whether to accept that is the operator's call. With it on, any rotor in the list can be chosen — the PstRotator, the Rotator Genius, the ARCO, the tower already turned for HF. That works because the per-backend command dispatch moved out of the three RotatorGoTo/Stop/Heading methods into linkGoTo/linkStop/linkHeading, so the satellite tracker drives any of the seven backends through the same code the compass uses instead of a second implementation of each. GetRotatorHeading loses sixty lines of near-duplicate switch in the process, and a rotor with no elevation axis now says so (HasElevation) rather than reporting a zero that looks like a real bearing. One trap, with a test on it: the step check compared both axes, so with the elevation never commanded its difference stayed above the step for the whole pass and every tick ordered the antenna to the bearing it was already on. A mast has a finite number of turns in it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
+26
-10
@@ -40,10 +40,12 @@ const (
|
||||
// flattenRotors gives it. How to reach it is that list's business, not
|
||||
// this page's: describing one mast in two places is how a station ends up
|
||||
// working on HF and not on a pass.
|
||||
keySatRotID = "sat.rot_id"
|
||||
keySatRotMinEl = "sat.rot_min_el" // don't drive the rotator below this elevation
|
||||
keySatRotStep = "sat.rot_step" // degrees of change worth a command
|
||||
keySatRotPark = "sat.rot_park" // park at az 0 / el 0 when tracking stops
|
||||
keySatRotID = "sat.rot_id"
|
||||
// Follow the azimuth and leave the elevation alone. See SatSettings.RotAzOnly.
|
||||
keySatRotAzOnly = "sat.rot_az_only"
|
||||
keySatRotMinEl = "sat.rot_min_el" // don't drive the rotator below this elevation
|
||||
keySatRotStep = "sat.rot_step" // degrees of change worth a command
|
||||
keySatRotPark = "sat.rot_park" // park at az 0 / el 0 when tracking stops
|
||||
|
||||
// The satellite page used to configure its own EasyComm or PstRotator link.
|
||||
// These keys are read once by migrateSatRotator, which turns what they hold
|
||||
@@ -86,11 +88,23 @@ type SatSettings struct {
|
||||
// business and means nothing to a rotor turned by hand: below which
|
||||
// elevation not to bother, how far the antenna must be off before a command
|
||||
// is worth sending, and whether to park at the end.
|
||||
RotOn bool `json:"rot_on"`
|
||||
RotID string `json:"rot_id"`
|
||||
RotMinEl int `json:"rot_min_el"`
|
||||
RotStep int `json:"rot_step"`
|
||||
RotPark bool `json:"rot_park"`
|
||||
RotOn bool `json:"rot_on"`
|
||||
RotID string `json:"rot_id"`
|
||||
// RotAzOnly follows the satellite in azimuth and leaves the elevation
|
||||
// alone — which is how most stations that work satellites actually do it.
|
||||
//
|
||||
// A pass at the far edge of the footprint never climbs above ten or fifteen
|
||||
// degrees, and a beam on a plain azimuth rotator points straight through it:
|
||||
// the beamwidth covers the whole thing. Refusing to track for want of an
|
||||
// elevation motor turned the feature off for every operator who has a tower
|
||||
// and no az/el mast, which is nearly all of them.
|
||||
//
|
||||
// It also rescues an az/el station whose elevation motor has failed, and it
|
||||
// is why the rotor list stops being filtered when this is set.
|
||||
RotAzOnly bool `json:"rot_az_only"`
|
||||
RotMinEl int `json:"rot_min_el"`
|
||||
RotStep int `json:"rot_step"`
|
||||
RotPark bool `json:"rot_park"`
|
||||
}
|
||||
|
||||
// SatTransponder is one path through a satellite, as the UI needs it.
|
||||
@@ -260,12 +274,13 @@ func (a *App) satSettings() SatSettings {
|
||||
}
|
||||
m, err := a.settings.GetMany(a.ctx,
|
||||
keySatFavorites, keySatMinEl, keySatWindowH, keySatAutoTLE, keySatGrid, keySatAltM,
|
||||
keySatRotOn, keySatRotID, keySatRotMinEl, keySatRotStep, keySatRotPark)
|
||||
keySatRotOn, keySatRotID, keySatRotAzOnly, keySatRotMinEl, keySatRotStep, keySatRotPark)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
out.RotOn = m[keySatRotOn] == "1"
|
||||
out.RotID = strings.TrimSpace(m[keySatRotID])
|
||||
out.RotAzOnly = m[keySatRotAzOnly] == "1"
|
||||
if v, err := strconv.Atoi(m[keySatRotMinEl]); err == nil && v >= -10 && v <= 30 {
|
||||
out.RotMinEl = v
|
||||
}
|
||||
@@ -335,6 +350,7 @@ func (a *App) SaveSatSettings(s SatSettings) error {
|
||||
keySatAltM: strconv.Itoa(s.AltM),
|
||||
keySatRotOn: boolStr(s.RotOn),
|
||||
keySatRotID: strings.TrimSpace(s.RotID),
|
||||
keySatRotAzOnly: boolStr(s.RotAzOnly),
|
||||
keySatRotMinEl: strconv.Itoa(s.RotMinEl),
|
||||
keySatRotStep: strconv.Itoa(s.RotStep),
|
||||
keySatRotPark: boolStr(s.RotPark),
|
||||
|
||||
Reference in New Issue
Block a user