fix(rigctld): stop refusing two commands JTDX sends in Fake It

From a user's log, repeating throughout the session:

	rigctld: split off failed: cat: this radio's backend cannot set split
	rigctld: unimplemented command "X PKTUSB -1"

Both are answered with an error, and JTDX treats a rig-control error as fatal:
it abandoned a transmission 0.86 seconds into a 13.8-second frame.

"S 0" — split OFF. Fake It uses no split, and JTDX still sends this to be sure.
A backend that cannot SET split was refusing a request that was ALREADY
satisfied. It now succeeds when the rig is simplex, without touching the rig at
all. Arming still fails loudly: there the request is real and unmet, and a
client that believes it transmits up the band while the radio sits on the DX's
frequency is exactly what that refusal exists to prevent.

"X <mode>" — set_split_mode. Sent during ordinary setup, Fake It included,
where there is no second VFO to give a mode to. Accepted now: applied when a
split is in force, and a plain success otherwise, because the transmit VFO
already has that mode — it is the same VFO. get_split_mode answers to match.

Both regressions fail without the fix with the exact codes from that log,
RPRT -9 and RPRT -11.
This commit is contained in:
2026-08-13 15:57:58 +02:00
parent d44a971acf
commit 4e2a5877d6
4 changed files with 95 additions and 18904 deletions
+39
View File
@@ -421,6 +421,17 @@ func (s *Server) handle(line string) (resp string, quit bool) {
return rprt(0), false
}
s.splitWanted.Store(false)
// Already simplex? Then there is nothing to do and the request is
// satisfied. Reporting a failure here is what broke JTDX in "Fake It":
// Fake It uses no split, JTDX still sends "S 0" to be sure, and a backend
// that cannot SET split answered an error to a request that was already
// true. JTDX read that as rig control failing and abandoned the
// transmission a second into the frame.
//
// A refusal is only honest when something actually needed doing.
if on, _ := s.rig.Split(); !on {
return rprt(0), false
}
if err := s.rig.SetSplit(false, 0); err != nil {
s.log("rigctld: split off failed: %v", err)
return rprt(-9), false
@@ -450,6 +461,34 @@ func (s *Server) handle(line string) (resp string, quit bool) {
s.log("rigctld: split ON, TX %.0f Hz", hz)
return rprt(0), false
case "X", "\\set_split_mode":
// "X <mode> <passband>" — the mode of the TRANSMIT VFO.
//
// Accepted rather than refused. WSJT-X and JTDX send it as part of their
// normal setup even in "Fake It", where there is no split and therefore no
// second VFO to give a mode to; answering "not implemented" made JTDX give
// up on rig control mid-transmission.
//
// The mode is applied when there IS a split — the transmit VFO is a real
// one then. Without split the request has no target and succeeding is the
// honest answer: the transmit VFO already has that mode, because it is the
// same VFO.
if len(args) < 1 {
return rprt(-1), false
}
if on, _ := s.rig.Split(); !on {
return rprt(0), false
}
if err := s.rig.SetMode(args[0]); err != nil {
s.log("rigctld: split mode %q failed: %v", args[0], err)
return rprt(-9), false
}
return rprt(0), false
case "x", "\\get_split_mode":
// Mirrors X: the transmit VFO's mode and passband. Without split that is
// simply the current mode.
return s.rig.Mode() + "\n2400\n", false
default:
// A frame ending in ';' is not a rigctl command at all — it is raw rig
// dialect (Kenwood/Elecraft/Yaesu), which means the client is configured