fix(sat): the per-band antennas reach the satellite slices

Settings ▸ FlexRadio stores the band→antenna map keyed by the band name
in capitals — that is what the panel writes and what the entry form reads
back. applySatRadio looked its two bands up through bandForHz, which
returns the band plan's own spelling ("70cm"), so both lookups missed,
both antennas came back empty, and the early return left the pass on
whichever antenna the radio was last used on. An operator with XVTA on
2 m and XVTB on 70 cm had configured exactly the thing being ignored, and
nothing said so: the downlink ran through the wrong transverter in
silence.

The key is now computed by flexBandAntKey, which exists so the next
caller cannot make the same mistake, and a test pins the case in the
direction that broke. Both outcomes are logged — the resolved antennas,
or the bands nothing was configured for — because from the outside a
setting never made and a lookup that missed look identical.

Alongside, three things the same pass made obvious:

- Tracking shows the satellite's own azimuth, elevation, distance and
  altitude beside the frequencies. The strip held where the ANTENNA was
  pointing but not where the bird was, which is what says whether a pass
  is worth calling on. Elevation dims below the horizon so a satellite
  followed before its lever cannot be read as workable.

- Both satellite lists in the settings are sorted by name with the
  numbers taken as numbers. The available column followed the order
  birds.json happens to be written in and the followed column the order
  of the clicks, so finding one bird among sixteen meant reading all
  sixteen.

- A followed satellite that has since been renamed is resolved through
  the plan's aliases. LILACSAT-2 became LO-90, and the followed list is
  stored as plain text, so the bird the operator had chosen appeared as
  having no elements while the same satellite sat in the available
  column under its new name. Resolved in GetSatSettings rather than
  satSettings, which is read at startup before the plan is loaded.
This commit is contained in:
2026-09-10 11:00:24 +02:00
parent 580e5782f8
commit 2615365684
7 changed files with 145 additions and 18 deletions
+30 -6
View File
@@ -96,8 +96,13 @@ type SatTrackStatus struct {
Az float64 `json:"az"`
El float64 `json:"el"`
Visible bool `json:"visible"`
Radio string `json:"radio"` // what the rig is doing: "sat", "downlink-only", ""
Error string `json:"error"`
// Where the satellite is, as opposed to where to point: an operator reads
// the distance to know whether a pass is worth calling on, and the altitude
// to know how long it will last.
RangeKm float64 `json:"range_km"`
AltKm float64 `json:"alt_km"`
Radio string `json:"radio"` // what the rig is doing: "sat", "downlink-only", ""
Error string `json:"error"`
// Where the antenna is. RotLive distinguishes a reading from the controller
// from the last position it was TOLD to go to — a stuck rotator must not be
@@ -350,6 +355,7 @@ func (a *App) satTrackStep(t *satTracker) {
st.NominalDown, st.NominalUp = nominal, nomUp
st.DownHz, st.UpHz = down, up
st.Az, st.El, st.Visible = pos.Az, pos.El, visible
st.RangeKm, st.AltKm = pos.RangeKm, pos.AltKm
t.status = st
t.mu.Unlock()
@@ -649,8 +655,20 @@ func satBandLetter(hz int64) string {
return "K" // 24 GHz and above
}
// applySatAntennas puts each satellite slice on the antenna configured for ITS
// band.
// flexBandAntKey is the key a frequency has in the per-band antenna and power
// maps.
//
// Those maps are keyed by the band name UPPERCASED ("70CM"), because that is
// how the settings panel writes them; bandForHz returns the band plan's own
// spelling ("70cm"). Every other caller happened to uppercase on the way in,
// the satellite tracker did not, and so it read an empty antenna out of a map
// the operator had filled in — which is not a mistake worth making twice.
func flexBandAntKey(hz int64) string {
return strings.ToUpper(bandForHz(hz))
}
// applySatRadio puts each satellite slice on the antenna configured for ITS
// band, and sets the uplink tone.
//
// Settings ▸ FlexRadio already holds a per-band RX/TX antenna map, and it was
// only ever applied by the entry form on a band change — to the active slice.
@@ -681,11 +699,17 @@ func (a *App) applySatRadio(tp sat.Transponder) {
}
// The downlink is received, so it takes that band's RX antenna; the uplink
// is transmitted, so it takes that band's TX antenna.
rxAnt := m[bandForHz(tp.DownLo)].RX
txAnt := m[bandForHz(tp.UpLo)].TX
downBand, upBand := flexBandAntKey(tp.DownLo), flexBandAntKey(tp.UpLo)
rxAnt := m[downBand].RX
txAnt := m[upBand].TX
if strings.TrimSpace(rxAnt) == "" && strings.TrimSpace(txAnt) == "" {
// Worth a line: an operator who HAS configured the pair and still sees
// the wrong antenna has no other way to tell a setting he never made
// from a lookup that missed.
applog.Printf("sat: no antenna configured for this pass (down %s, up %s)", downBand, upBand)
return
}
applog.Printf("sat: antennas rx=%q (%s) tx=%q (%s)", rxAnt, downBand, txAnt, upBand)
if err := a.cat.FlexDo(func(fc cat.FlexController) error {
return fc.SatAntennas(rxAnt, txAnt)
}); err != nil {