fix: a cluster spot dragged the rig from SUB back to MAIN
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.
This commit is contained in:
+26
-8
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user