package rigctld import ( "strings" "testing" ) // WSJT-X in "Split Operating: Rig" sends set_split_vfo then set_split_freq. Both // used to answer RPRT 0 and do NOTHING: the software believed it was // transmitting up the band while the radio stayed on the receive frequency — // on a pileup, straight onto the DX, with no trace anywhere. func TestSetSplitReachesTheRig(t *testing.T) { rig := &fakeRig{freq: 14074000, mode: "FT8"} s := New(0, rig, func(string, ...any) {}) if got, _ := s.handle("S 1 VFOB"); !strings.HasPrefix(got, "RPRT 0") { t.Fatalf("set_split_vfo answered %q", got) } // Arming alone must NOT touch the rig: without a frequency it would transmit // on whatever the second VFO happened to hold. if len(rig.splitCalls) != 0 { t.Errorf("split was armed before a frequency arrived: %v", rig.splitCalls) } if got, _ := s.handle("I 14075300.000000"); !strings.HasPrefix(got, "RPRT 0") { t.Fatalf("set_split_freq answered %q", got) } if len(rig.splitCalls) != 1 || rig.splitCalls[0] != "true:14075300" { t.Fatalf("rig saw %v, want one call arming split on 14075300", rig.splitCalls) } if got, _ := s.handle("S 0 VFOA"); !strings.HasPrefix(got, "RPRT 0") { t.Fatalf("split off answered %q", got) } if len(rig.splitCalls) != 2 || !strings.HasPrefix(rig.splitCalls[1], "false:") { t.Errorf("rig saw %v, want split cleared", rig.splitCalls) } } // A backend that cannot do split must produce an ERROR the client can report. // Answering success and doing nothing is what caused the original fault, and it // is the one outcome that must never come back. func TestSetSplitRefusalIsReported(t *testing.T) { rig := &fakeRig{freq: 14074000, noSplit: true} s := New(0, rig, func(string, ...any) {}) s.handle("S 1 VFOB") got, _ := s.handle("I 14075300.000000") if strings.HasPrefix(got, "RPRT 0") { t.Errorf("a rig that cannot split answered %q — the client will transmit on the wrong frequency", got) } } // "Fake It" uses NO split, and JTDX still sends "S 0" to make sure. A backend // that cannot SET split used to answer an error to that — a refusal of a // request that was already satisfied — and JTDX read it as rig control failing // and abandoned the transmission a second into a 13.8-second frame. // // Reported against a Yaesu whose backend cannot arm split from software. func TestSplitOffOnASimplexRigSucceeds(t *testing.T) { rig := &fakeRig{freq: 14074000, mode: "FT8", noSplit: true} s := New(0, rig, func(string, ...any) {}) if got, _ := s.handle("S 0 VFOA"); !strings.HasPrefix(got, "RPRT 0") { t.Errorf("split off on a simplex rig answered %q — nothing needed doing", got) } // And it must not have bothered the rig at all. if len(rig.splitCalls) != 0 { t.Errorf("rig was asked to clear a split it did not have: %v", rig.splitCalls) } // Arming, however, must still fail loudly: there the request is real and // unmet, and a client that believes it is transmitting up the band while the // radio sits on the DX's frequency is the bug this refusal exists to prevent. if got, _ := s.handle("S 1 VFOB"); !strings.HasPrefix(got, "RPRT 0") { t.Fatalf("arming is deferred to set_split_freq: %q", got) } if got, _ := s.handle("I 14075300.000000"); strings.HasPrefix(got, "RPRT 0") { t.Error("arming split on a backend that cannot must report a failure") } } // X (set_split_mode) is sent by WSJT-X and JTDX during ordinary setup, Fake It // included — where there is no second VFO to give a mode to. Answering "not // implemented" made JTDX give up on rig control mid-transmission. func TestSplitModeIsAccepted(t *testing.T) { rig := &fakeRig{freq: 14074000, mode: "FT8"} s := New(0, rig, func(string, ...any) {}) // No split: nothing to target, and succeeding is honest — the transmit VFO // already has that mode because it is the same VFO. if got, _ := s.handle("X PKTUSB -1"); !strings.HasPrefix(got, "RPRT 0") { t.Errorf("set_split_mode answered %q", got) } if rig.mode != "FT8" { t.Errorf("the mode was changed with no split in force: %q", rig.mode) } // Reading it back must answer a mode and a passband, never an error. got, _ := s.handle("x") if !strings.Contains(got, "FT8") { t.Errorf("get_split_mode = %q, want the current mode", got) } }