From c297f91ca815b4db07b1604e6daa72622bff78c8 Mon Sep 17 00:00:00 2001 From: rouggy Date: Wed, 29 Jul 2026 16:07:20 +0200 Subject: [PATCH] fix: a cluster spot dragged the rig from SUB back to MAIN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported on an FTDX101 (F4NBZ): listening on the SUB VFO, clicking a spot moved the radio to MAIN. SetSimplexMode is the first thing SetFrequency tries, and it means "receive and transmit here, simplex" — OmniRig applies that to the MAIN VFO by definition. It is there because several Icoms ignore direct FreqA/FreqB writes, so it stays for the main VFO; on SUB it is now skipped entirely and FreqB carries the change. The generic "Freq" property is skipped on SUB too: on several rig files it IS the main VFO, so writing it would move the VFO the operator is not using — or pull them back to it, which is the bug being fixed. An operator on SUB put themselves there deliberately. A spot click asks for a frequency, not for a change of VFO — that distinction is the whole fix, and a test pins which property each VFO state writes. --- changelog.json | 10 ++++++++ internal/cat/omnirig.go | 34 ++++++++++++++++++++------ internal/cat/omnirig_vfo_test.go | 42 ++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 8 deletions(-) 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) + } + } +}