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:
+15
-1
@@ -69,6 +69,7 @@ type satTracker struct {
|
||||
rotStep float64
|
||||
rotMinE float64
|
||||
rotPark bool
|
||||
rotAzOnly bool
|
||||
rotAz float64 // last commanded, so a step smaller than the beamwidth costs nothing
|
||||
rotEl float64
|
||||
rotSent bool
|
||||
@@ -101,6 +102,10 @@ type SatTrackStatus struct {
|
||||
RotAz float64 `json:"rot_az"`
|
||||
RotEl float64 `json:"rot_el"`
|
||||
RotLive bool `json:"rot_live"`
|
||||
// RotAzOnly: the elevation is not being driven and RotEl means nothing.
|
||||
// Sent so the panel can leave it out rather than draw an antenna lying on
|
||||
// the horizon, which is what an undriven zero looks like.
|
||||
RotAzOnly bool `json:"rot_az_only"`
|
||||
}
|
||||
|
||||
// StartSatelliteTracking arms the radio and starts following the satellite.
|
||||
@@ -138,6 +143,8 @@ func (a *App) StartSatelliteTracking(name string, transponder int) error {
|
||||
} else {
|
||||
t.rot = r
|
||||
t.rotStep, t.rotMinE, t.rotPark = float64(set.RotStep), float64(set.RotMinEl), set.RotPark
|
||||
t.rotAzOnly = set.RotAzOnly
|
||||
t.status.RotAzOnly = set.RotAzOnly
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,7 +422,14 @@ func (t *satTracker) pointRotator(pos sat.Position, geostationary bool) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if t.rotSent && math.Abs(az-t.rotAz) < t.rotStep && math.Abs(el-t.rotEl) < t.rotStep {
|
||||
// In azimuth-only mode the elevation is never commanded, so comparing it
|
||||
// would find a difference on every tick and send a command for nothing —
|
||||
// the antenna ordered to the same bearing once a second for the whole pass.
|
||||
moved := math.Abs(az-t.rotAz) >= t.rotStep
|
||||
if !t.rotAzOnly {
|
||||
moved = moved || math.Abs(el-t.rotEl) >= t.rotStep
|
||||
}
|
||||
if t.rotSent && !moved {
|
||||
return
|
||||
}
|
||||
if err := t.rot.Point(az, el); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user