From 7759e09b4e960cac2cd04f226e8d63704feebd0d Mon Sep 17 00:00:00 2001 From: rouggy Date: Thu, 30 Jul 2026 10:10:43 +0200 Subject: [PATCH] fix: Yaesu mode commands always addressed the main receiver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported, and reasoned out from the half that already worked: a spot click now tunes the right VFO, but it set the mode with MD0 — main — whatever VFO the operator was on. Working from SUB, the spot changed the mode of the VFO NOT in use and left the one in use as it was. The author inferred the sub half from seeing main change, and was right. The read had the same fault, so the console displayed main's mode while showing the sub VFO's frequency. Mode now addresses the receiver in use — MD0 for main, MD1 for sub — in the poll read, in SetMode, and in the console's raw-mode setter. A rig without a sub receiver never sees MD1: curVFO only becomes B where the rig reports its receive VFO in the first place. --- changelog.json | 6 ++++-- internal/cat/yaesu.go | 24 +++++++++++++++++++++--- internal/cat/yaesu_panel.go | 4 +++- internal/cat/yaesu_test.go | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/changelog.json b/changelog.json index 7553303..65e49b2 100644 --- a/changelog.json +++ b/changelog.json @@ -16,7 +16,8 @@ "Yaesu: moving RX and TX together to the SUB receiver is no longer shown as split. That is simplex on the sub VFO, but the rig reports its split flag anyway, so OpsLog now works split out from which VFO transmits and which receives.", "Auto-call CW now waits the gap you set AFTER the message, not from the moment it starts. With a WinKeyer or serial keyer the wait could end before the call had finished, so a 6 s gap gave about 2 s of silence after a 4 s CQ.", "The Antenna Genius and Tuner Genius settings entries carry a small 4O3A mark, so the panels driving that hardware are recognisable at a glance in the sidebar.", - "The database shown in the status bar is the LOGBOOK file. On a local SQLite setup it named the settings database instead, so anyone checking where their QSOs live — or which file to back up — was pointed at the wrong one." + "The database shown in the status bar is the LOGBOOK file. On a local SQLite setup it named the settings database instead, so anyone checking where their QSOs live — or which file to back up — was pointed at the wrong one.", + "Yaesu: the mode follows the VFO you are on. A spot clicked while working the SUB receiver tuned the sub VFO but set the mode on MAIN, leaving the VFO in use on its old mode; the console read main mode too." ], "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.", @@ -32,7 +33,8 @@ "Yaesu : passer RX et TX ensemble sur le récepteur SUB n'est plus affiché comme un split. C'est du simplex sur le VFO secondaire, mais la radio lève quand même son indicateur de split ; OpsLog le déduit désormais du VFO qui émet et de celui qui reçoit.", "L'appel automatique en CW attend désormais l'intervalle réglé APRÈS le message, et non depuis son début. Avec un WinKeyer ou un keyer série, l'attente pouvait se terminer avant la fin de l'appel : un intervalle de 6 s ne laissait qu'environ 2 s de silence après un CQ de 4 s.", "Les entrées de réglages Antenna Genius et Tuner Genius portent une petite marque 4O3A : les panneaux pilotant ce matériel se repèrent d'un coup d'œil dans le menu.", - "La base affichée dans la barre d'état est bien le fichier du CARNET. En SQLite local, elle désignait la base des réglages : qui vérifiait où résident ses QSO — ou quel fichier sauvegarder — était orienté vers le mauvais." + "La base affichée dans la barre d'état est bien le fichier du CARNET. En SQLite local, elle désignait la base des réglages : qui vérifiait où résident ses QSO — ou quel fichier sauvegarder — était orienté vers le mauvais.", + "Yaesu : le mode suit le VFO utilisé. Un spot cliqué en travaillant sur le récepteur SUB accordait bien le VFO secondaire mais réglait le mode sur le MAIN, laissant le VFO en service sur son ancien mode ; la console lisait également le mode du principal." ] }, { diff --git a/internal/cat/yaesu.go b/internal/cat/yaesu.go index 4479a35..7632bf6 100644 --- a/internal/cat/yaesu.go +++ b/internal/cat/yaesu.go @@ -17,7 +17,7 @@ package cat // // FA; → FA014074000; VFO A frequency, 9 digits, Hz // FB; → FB014100000; VFO B frequency -// MD0; → MD02; operating mode of the main receiver +// MD0;/MD1; → MD02; operating mode — 0 = main receiver, 1 = sub // FR; → FR1; RECEIVE VFO (0=main/A, 1=sub/B) // VS; → VS0; selected VFO, on models without FR // ST; → ST1; split (FTDX10/FTDX101) @@ -304,7 +304,7 @@ func (y *Yaesu) ReadState() (RigState, error) { y.curRXFreq = s.RxFreqHz } - if r, err := y.ask("MD0;"); err == nil && len(r) >= 4 { + if r, err := y.ask("MD" + y.modeVFOSuffix() + ";"); err == nil && len(r) >= 4 { // Keep the RAW mode too: ADIF folds CW-U/CW-L and DATA-U/DATA-L together, // but the panel has to show which sideband the rig is actually on. y.panel.RawMode = yaesuRawModeName(r[3]) @@ -351,7 +351,25 @@ func (y *Yaesu) SetMode(mode string) error { if d == 0 { return fmt.Errorf("yaesu: no CAT mode for %q", mode) } - return y.write(fmt.Sprintf("MD0%c;", d)) + return y.write(fmt.Sprintf("MD%s%c;", y.modeVFOSuffix(), d)) +} + +// modeVFOSuffix picks which receiver the mode commands address: "0" is main, +// "1" is sub. +// +// Both the read and the write used 0 unconditionally. A spot click already tuned +// the right VFO — that part was fixed — but then set the MODE on main, so an +// operator working from the sub receiver had a spot change the mode of the VFO +// they were not using and leave the one they were on untouched (F4NBZ, +// 2026-07-29). The rig reads the same way, so the panel showed main's mode too. +// +// Rigs without a sub receiver simply never see "1": curVFO only becomes B on a +// rig that reports its receive VFO. +func (y *Yaesu) modeVFOSuffix() string { + if y.curVFO == "B" { + return "1" + } + return "0" } func (y *Yaesu) SetPTT(on bool) error { diff --git a/internal/cat/yaesu_panel.go b/internal/cat/yaesu_panel.go index ae7a616..5c8d496 100644 --- a/internal/cat/yaesu_panel.go +++ b/internal/cat/yaesu_panel.go @@ -430,7 +430,9 @@ func (y *Yaesu) SetYaesuModeRaw(name string) error { if !ok { return fmt.Errorf("yaesu: unknown rig mode %q", name) } - return y.setAndRefresh(fmt.Sprintf("MD0%c;", d)) + // Same receiver as everywhere else — the console must not set main's mode + // while the operator is working the sub VFO. + return y.setAndRefresh(fmt.Sprintf("MD%s%c;", y.modeVFOSuffix(), d)) } // yaesuRawModeDigit maps a rig-mode name to its MD digit. diff --git a/internal/cat/yaesu_test.go b/internal/cat/yaesu_test.go index 37a7fb8..3cfff4e 100644 --- a/internal/cat/yaesu_test.go +++ b/internal/cat/yaesu_test.go @@ -347,3 +347,36 @@ func TestYaesuSplitProbeOrder(t *testing.T) { t.Error("ST1 is a flag and reads as split whatever the VFO — that is the trap") } } + +// The mode commands address the receiver the operator is ON. +// +// Reported on an FTDX101 (F4NBZ, 2026-07-29): once a spot click tuned the right +// VFO, it still changed the mode of MAIN while the operator worked from SUB — so +// the VFO in use kept its old mode and the idle one was altered. The read had the +// same fault, so the panel showed main's mode as well. +func TestYaesuModeVFOSuffix(t *testing.T) { + cases := []struct{ vfo, want string }{ + {"A", "0"}, // main + {"", "0"}, // not yet read — main is the safe assumption + {"B", "1"}, // sub + {"AB", "0"}, // pair enums start with the receive VFO + } + for _, c := range cases { + y := &Yaesu{} + y.curVFO = c.vfo + if got := y.modeVFOSuffix(); got != c.want { + t.Errorf("curVFO=%q → MD%s, want MD%s", c.vfo, got, c.want) + } + } + + // Spelled out as the commands actually sent, which is what the rig sees. + y := &Yaesu{} + y.curVFO = "B" + if cmd := "MD" + y.modeVFOSuffix() + "3;"; cmd != "MD13;" { + t.Errorf("setting CW on the sub receiver sends %q, want MD13;", cmd) + } + y.curVFO = "A" + if cmd := "MD" + y.modeVFOSuffix() + "3;"; cmd != "MD03;" { + t.Errorf("setting CW on main sends %q, want MD03;", cmd) + } +}