fix(cat-share): answer get_freq with the listening VFO, not the transmit one
Reported from a station running an IC-7850 over USB: with split engaged,
turning VFO B moved VFO A slightly. OpsLog writes nothing to that radio on
its own — but it shares it, and what it was sharing was wrong.
Hamlib's 'f' means the VFO IN USE. RigState follows ADIF, where FreqHz is
where we TRANSMIT, and the two are the same number until split is
engaged — so the adapter handed FreqHz over unchanged and every client
asking for the dial was given VFO B. A client that reads the dial and
writes it back, which is what WSJT-X and its like do, then wrote VFO B's
frequency into VFO A. Hence a shift the size of the split offset, and
hence 'it did not do this before': it only happens with split on.
get_split_freq ('i') already answers the transmit frequency and is
untouched. The one-line rule is lifted into shareRXFreq with a test that
includes this case, the simplex case, and a backend that reports split
without ever filling in the receive frequency — which would otherwise
answer a client with 0 Hz.
This commit is contained in:
@@ -20075,7 +20075,27 @@ func (a *App) IsNewUSCounty(state, cnty string) bool {
|
||||
// native ones: an operator on OmniRig or Flex gets the same server.
|
||||
type catShareRig struct{ a *App }
|
||||
|
||||
func (r catShareRig) Freq() int64 { return r.a.cat.State().FreqHz }
|
||||
// Freq is what a rigctl client gets for "f": the frequency of the VFO in use,
|
||||
// which is where we LISTEN.
|
||||
//
|
||||
// This returned FreqHz, and RigState follows ADIF where FreqHz is the TRANSMIT
|
||||
// frequency — so with split on, every client asking "what frequency is the
|
||||
// radio on" was told VFO B. Reported from a station running an IC-7850: turning
|
||||
// VFO B moved VFO A. Nothing in OpsLog was writing to the radio; a client was
|
||||
// reading the dial, being handed the wrong VFO, and writing it back.
|
||||
//
|
||||
// The split TX frequency is a separate question, and Hamlib has a separate
|
||||
// command for it ("i" / get_split_freq) which Split() below answers.
|
||||
func (r catShareRig) Freq() int64 { return shareRXFreq(r.a.cat.State()) }
|
||||
|
||||
// shareRXFreq is that rule on its own, so it can be pinned by a test: it is one
|
||||
// line, it was wrong, and being wrong cost a station its VFO A.
|
||||
func shareRXFreq(st cat.RigState) int64 {
|
||||
if st.Split && st.RxFreqHz > 0 {
|
||||
return st.RxFreqHz
|
||||
}
|
||||
return st.FreqHz
|
||||
}
|
||||
func (r catShareRig) Mode() string { return r.a.cat.State().Mode }
|
||||
|
||||
// Split reports the flag and the OTHER VFO's frequency. RigState follows ADIF —
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"hamlog/internal/cat"
|
||||
)
|
||||
|
||||
// What a rigctl client is told when it asks "what frequency is the radio on".
|
||||
//
|
||||
// Hamlib's "f" means the VFO IN USE — where the operator is listening. RigState
|
||||
// follows ADIF, where FreqHz is where we TRANSMIT, and the two are the same
|
||||
// number until split is engaged. They were handed over unchanged, so with split
|
||||
// on every client asking for the dial was given VFO B.
|
||||
//
|
||||
// Reported from a station running an IC-7850 over USB: turning VFO B moved
|
||||
// VFO A. OpsLog was writing nothing to that radio — a client read the dial, was
|
||||
// handed the transmit VFO, and wrote it back as the dial.
|
||||
func TestShareRXFreqAnswersTheListeningVFO(t *testing.T) {
|
||||
const rx, tx = 14195000, 14200500
|
||||
cases := []struct {
|
||||
name string
|
||||
st cat.RigState
|
||||
want int64
|
||||
}{
|
||||
{
|
||||
name: "simplex — the one frequency there is",
|
||||
st: cat.RigState{FreqHz: rx},
|
||||
want: rx,
|
||||
},
|
||||
{
|
||||
name: "split — the RX VFO, not the TX one",
|
||||
st: cat.RigState{Split: true, FreqHz: tx, RxFreqHz: rx},
|
||||
want: rx,
|
||||
},
|
||||
{
|
||||
// A backend that reports split without ever filling RxFreqHz would
|
||||
// otherwise be answered with 0, and a client told the radio is on
|
||||
// 0 Hz does something worse than nothing with it.
|
||||
name: "split claimed but no RX frequency known",
|
||||
st: cat.RigState{Split: true, FreqHz: tx},
|
||||
want: tx,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if got := shareRXFreq(c.st); got != c.want {
|
||||
t.Errorf("shareRXFreq(%+v) = %d, want %d", c.st, got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// And the pair, stated together: the two questions must not return the same
|
||||
// answer while split is on, or the distinction has been lost somewhere.
|
||||
func TestShareSplitReportsTheOtherVFO(t *testing.T) {
|
||||
st := cat.RigState{Split: true, FreqHz: 14200500, RxFreqHz: 14195000}
|
||||
rxAnswer := shareRXFreq(st)
|
||||
txAnswer := st.FreqHz // what Split() hands back for "i" / get_split_freq
|
||||
if rxAnswer == txAnswer {
|
||||
t.Fatalf("get_freq and get_split_freq both answered %d — a client cannot tell the VFOs apart", rxAnswer)
|
||||
}
|
||||
if rxAnswer != st.RxFreqHz || txAnswer != st.FreqHz {
|
||||
t.Errorf("got rx=%d tx=%d, want rx=%d tx=%d", rxAnswer, txAnswer, st.RxFreqHz, st.FreqHz)
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -4,11 +4,13 @@
|
||||
"date": "",
|
||||
"en": [
|
||||
"Awards, RDA district comparison: each disagreement can now be settled on its own row. Click the district you keep — the log's or the database's — and apply. It is written into the contact's CNTY and its award reference, and the settled rows leave the list.",
|
||||
"The band/mode matrix: a confirmed slot now shows as confirmed. A callsign worked and not confirmed was outranking an entity CONFIRMED on the same band and mode, so a slot that needs nothing was painted as if it still did."
|
||||
"The band/mode matrix: a confirmed slot now shows as confirmed. A callsign worked and not confirmed was outranking an entity CONFIRMED on the same band and mode, so a slot that needs nothing was painted as if it still did.",
|
||||
"CAT sharing: a program connected to OpsLog's rigctl server is told the frequency it is LISTENING on. With split engaged it was given the transmit VFO instead, so a client that reads the dial and writes it back — WSJT-X and its like — could move VFO A when VFO B was turned."
|
||||
],
|
||||
"fr": [
|
||||
"Diplômes, comparaison des districts RDA : chaque divergence se règle désormais sur sa propre ligne. Cliquer le district qu'on garde — celui du log ou celui de la base — puis appliquer. Il est écrit dans le CNTY du contact et dans sa référence de diplôme, et les lignes réglées quittent la liste.",
|
||||
"Matrice bandes/modes : une case confirmée s'affiche enfin comme confirmée. Un indicatif travaillé et non confirmé l'emportait sur une entité CONFIRMÉE sur la même bande et le même mode, si bien qu'une case qui ne demandait plus rien était peinte comme s'il manquait encore quelque chose."
|
||||
"Matrice bandes/modes : une case confirmée s'affiche enfin comme confirmée. Un indicatif travaillé et non confirmé l'emportait sur une entité CONFIRMÉE sur la même bande et le même mode, si bien qu'une case qui ne demandait plus rien était peinte comme s'il manquait encore quelque chose.",
|
||||
"Partage CAT : un programme connecté au serveur rigctl d'OpsLog reçoit la fréquence sur laquelle on ÉCOUTE. En split, c'était le VFO d'émission qui lui était donné, si bien qu'un client qui lit le VFO et le réécrit — WSJT-X et consorts — pouvait déplacer le VFO A quand on tournait le VFO B."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -44,7 +44,11 @@ import (
|
||||
// Rig is what the server needs from OpsLog's CAT manager. An interface, so this
|
||||
// package stays testable without a radio and without importing internal/cat.
|
||||
type Rig interface {
|
||||
Freq() int64 // current TX frequency in Hz, 0 if unknown
|
||||
// Freq is the frequency of the CURRENT VFO — where the operator is
|
||||
// listening. Hamlib's "f" means the VFO in use, not the transmit one; the
|
||||
// split TX frequency is a separate question, asked with "i". Answering "f"
|
||||
// with the TX frequency invites a client to write it back as the dial.
|
||||
Freq() int64 // current (RX) frequency in Hz, 0 if unknown
|
||||
Mode() string // ADIF mode (SSB, CW, FT8…)
|
||||
Split() (bool, int64) // split on?, and the other VFO's frequency
|
||||
SetFreq(hz int64) error
|
||||
|
||||
Reference in New Issue
Block a user