diff --git a/changelog.json b/changelog.json index bdb9070..32bdb89 100644 --- a/changelog.json +++ b/changelog.json @@ -1,4 +1,14 @@ [ + { + "version": "0.22.1", + "date": "2026-07-29", + "en": [ + "Clicking a cluster spot while listening on the SUB VFO no longer pulls the radio back to MAIN. The command used to tune sets the main VFO by definition; on SUB the sub VFO is now written directly and the VFO selection is left alone." + ], + "fr": [ + "Cliquer sur un spot du cluster en écoutant sur le VFO SUB ne ramène plus la radio sur le VFO principal. La commande utilisée pour s'accorder agit par définition sur le VFO principal ; sur SUB, c'est désormais le VFO secondaire qui est écrit, sans toucher à la sélection." + ] + }, { "version": "0.22.0", "date": "2026-07-28", diff --git a/internal/cat/omnirig.go b/internal/cat/omnirig.go index f4c430a..4f73545 100644 --- a/internal/cat/omnirig.go +++ b/internal/cat/omnirig.go @@ -493,12 +493,24 @@ func (o *OmniRig) SetFrequency(hz int64) error { // method (RX=TX=freq, simplex). It works on rigs — notably Icom (IC-9100) — // where direct FreqA/FreqB writes are accepted but never move the radio. // Clearing split is the right thing when tuning to a spot anyway. + // …but ONLY when the operator is on the main VFO. SetSimplexMode means + // "receive and transmit here, simplex", and OmniRig implements that on the + // MAIN VFO — so on an FTDX101 listening on SUB, clicking a cluster spot + // dragged the radio back to MAIN (F4NBZ, 2026-07-29). The operator picked SUB + // deliberately; a spot click is a request to change frequency, not to change + // VFO. On SUB the direct FreqB write below does the whole job. + onSubVFO := vfo == "B" || vfo == "BB" || vfo == "BA" simplexOK := false - if _, err := oleutil.CallMethod(o.rig, "SetSimplexMode", int32(hz32)); err == nil { - simplexOK = true - debugLog.Printf("OmniRig.SetFrequency: SetSimplexMode(%d) OK", hz32) - } else { - debugLog.Printf("OmniRig.SetFrequency: SetSimplexMode unavailable (%v)", err) + switch { + case onSubVFO: + debugLog.Printf("OmniRig.SetFrequency: on VFO %q — skipping SetSimplexMode so the rig stays on SUB", vfo) + default: + if _, err := oleutil.CallMethod(o.rig, "SetSimplexMode", int32(hz32)); err == nil { + simplexOK = true + debugLog.Printf("OmniRig.SetFrequency: SetSimplexMode(%d) OK", hz32) + } else { + debugLog.Printf("OmniRig.SetFrequency: SetSimplexMode unavailable (%v)", err) + } } // On Yaesu / Kenwood (and anything non-Icom), SetSimplexMode frequently returns @@ -511,12 +523,18 @@ func (o *OmniRig) SetFrequency(hz int64) error { isIcom := strings.HasPrefix(strings.ToUpper(strings.TrimSpace(rigType)), "IC") if !isIcom || !simplexOK { prop := "FreqA" - switch vfo { - case "B", "BB", "BA": + if onSubVFO { prop = "FreqB" } okAny := false - for _, p := range []string{prop, "Freq"} { + // The generic "Freq" property is written too — but NOT on SUB, where it is + // the main VFO's frequency on several rig files and would move the VFO the + // operator is not using, or pull them back to it. + props := []string{prop, "Freq"} + if onSubVFO { + props = []string{prop} + } + for _, p := range props { if _, e := oleutil.PutProperty(o.rig, p, hz32); e != nil { debugLog.Printf("OmniRig.SetFrequency: PutProperty(%s) error: %v", p, e) } else { diff --git a/internal/cat/omnirig_vfo_test.go b/internal/cat/omnirig_vfo_test.go index eb719c3..bcdcd86 100644 --- a/internal/cat/omnirig_vfo_test.go +++ b/internal/cat/omnirig_vfo_test.go @@ -83,3 +83,45 @@ func TestResolveOmniRigVFOs(t *testing.T) { } } } + +// Which VFO a frequency set writes to, and whether it may call SetSimplexMode. +// +// Reported on an FTDX101 (F4NBZ, 2026-07-29): listening on SUB, clicking a +// cluster spot dragged the radio back to MAIN. SetSimplexMode means "receive and +// transmit here, simplex" and OmniRig applies it to the MAIN VFO — so it must +// not be used when the operator is on SUB. They chose that VFO deliberately; a +// spot click asks for a frequency, not for a VFO change. +func TestOmniRigWriteTarget(t *testing.T) { + // Mirrors the decision made in SetFrequency. + target := func(vfo string) (prop string, simplexAllowed bool) { + onSub := vfo == "B" || vfo == "BB" || vfo == "BA" + if onSub { + return "FreqB", false + } + return "FreqA", true + } + + cases := []struct { + vfo string + prop string + simplex bool + }{ + // On the main VFO nothing changes: SetSimplexMode is what moves the Icoms + // whose direct writes are ignored. + {"", "FreqA", true}, + {"A", "FreqA", true}, + {"AA", "FreqA", true}, + {"AB", "FreqA", true}, + // On SUB, write FreqB and leave the VFO selection alone. + {"B", "FreqB", false}, + {"BB", "FreqB", false}, + {"BA", "FreqB", false}, + } + for _, c := range cases { + prop, simplex := target(c.vfo) + if prop != c.prop || simplex != c.simplex { + t.Errorf("VFO %q → write %s, simplex=%v; want %s, simplex=%v", + c.vfo, prop, simplex, c.prop, c.simplex) + } + } +}