feat(sat): PstRotator can point the antenna too
It handles azimuth and elevation, and a great many stations already run it in front of a controller OpsLog has never heard of. For those, OpsLog talking to the controller itself would be a second program fighting PstRotator over the same cable — so it hands over the bearing instead, and lets PstRotator turn the mast. Both kinds sit behind one small interface, chosen in Settings. Neither is more correct than the other: the right one is whichever the station already has working. The 450° overlap is deliberately NOT applied on the PstRotator path. PstRotator knows which machine is on the other end and does its own; two programs each deciding to go the long way round is exactly how an antenna unwinds in the middle of a pass. Position queries are asked at most every three seconds rather than on every tick. A PstRotator query binds a socket and waits up to a second and a half, and many setups answer nothing at all — so one silence is enough and it stops asking, reporting the commanded position instead and saying that is what it is.
This commit is contained in:
+26
-24
@@ -29,7 +29,6 @@ import (
|
||||
"hamlog/internal/applog"
|
||||
"hamlog/internal/cat"
|
||||
"hamlog/internal/qso"
|
||||
"hamlog/internal/rotator/easycomm"
|
||||
"hamlog/internal/sat"
|
||||
)
|
||||
|
||||
@@ -66,13 +65,14 @@ type satTracker struct {
|
||||
|
||||
// The az/el rotator, built once at the start of the pass so a serial port is
|
||||
// opened once rather than on every command. nil when none is configured.
|
||||
rot *easycomm.Client
|
||||
rotStep float64
|
||||
rotMinE float64
|
||||
rotPark bool
|
||||
rotAz float64 // last commanded, so a step smaller than the beamwidth costs nothing
|
||||
rotEl float64
|
||||
rotSent bool
|
||||
rot satRotator
|
||||
rotStep float64
|
||||
rotMinE float64
|
||||
rotPark bool
|
||||
rotAz float64 // last commanded, so a step smaller than the beamwidth costs nothing
|
||||
rotEl float64
|
||||
rotSent bool
|
||||
rotReadAt time.Time // when the controller was last asked where it is
|
||||
|
||||
stop chan struct{}
|
||||
done chan struct{}
|
||||
@@ -131,12 +131,14 @@ func (a *App) StartSatelliteTracking(name string, transponder int) error {
|
||||
// left alone, so it gets one command rather than a loop.
|
||||
set := a.satSettings()
|
||||
if set.RotOn {
|
||||
if set.RotTransport == "tcp" {
|
||||
t.rot = easycomm.New(set.RotHost, set.RotPort, set.RotMaxAz)
|
||||
r, rerr := newSatRotator(set)
|
||||
if rerr != nil {
|
||||
applog.Printf("sat: no rotator: %v", rerr)
|
||||
t.status.Error = rerr.Error()
|
||||
} else {
|
||||
t.rot = easycomm.NewSerial(set.RotCOM, set.RotBaud, set.RotMaxAz)
|
||||
t.rot = r
|
||||
t.rotStep, t.rotMinE, t.rotPark = float64(set.RotStep), float64(set.RotMinEl), set.RotPark
|
||||
}
|
||||
t.rotStep, t.rotMinE, t.rotPark = float64(set.RotStep), float64(set.RotMinEl), set.RotPark
|
||||
}
|
||||
|
||||
// Arm the radio for the pair. A rig that cannot hold one is NOT a failure:
|
||||
@@ -191,17 +193,9 @@ func (a *App) TestSatelliteRotator() (string, error) {
|
||||
if !set.RotOn {
|
||||
return "", fmt.Errorf("the satellite rotator is switched off")
|
||||
}
|
||||
var c *easycomm.Client
|
||||
if set.RotTransport == "tcp" {
|
||||
if strings.TrimSpace(set.RotHost) == "" {
|
||||
return "", fmt.Errorf("no address for the rotator")
|
||||
}
|
||||
c = easycomm.New(set.RotHost, set.RotPort, set.RotMaxAz)
|
||||
} else {
|
||||
if strings.TrimSpace(set.RotCOM) == "" {
|
||||
return "", fmt.Errorf("no COM port for the rotator")
|
||||
}
|
||||
c = easycomm.NewSerial(set.RotCOM, set.RotBaud, set.RotMaxAz)
|
||||
c, err := newSatRotator(set)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer c.Close()
|
||||
az, el, live, err := c.Heading()
|
||||
@@ -209,7 +203,7 @@ func (a *App) TestSatelliteRotator() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
if !live {
|
||||
return "The controller accepted the command but does not report its position — normal for many EasyComm controllers. It will still be driven.", nil
|
||||
return "The controller accepted the command but does not report its position — normal for many controllers. It will still be driven.", nil
|
||||
}
|
||||
return fmt.Sprintf("The rotator is at %.1f° azimuth, %.1f° elevation.", az, el), nil
|
||||
}
|
||||
@@ -441,6 +435,14 @@ func (t *satTracker) readRotator() {
|
||||
if t.rot == nil {
|
||||
return
|
||||
}
|
||||
// Not on every tick. A PstRotator query binds a socket and waits up to a
|
||||
// second and a half for an answer, and a held serial port still costs a
|
||||
// round trip; three seconds is often enough to watch an antenna slew and
|
||||
// rare enough not to sit in the way of the tuning.
|
||||
if time.Since(t.rotReadAt) < 3*time.Second {
|
||||
return
|
||||
}
|
||||
t.rotReadAt = time.Now()
|
||||
az, el, live, err := t.rot.Heading()
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user