feat(rigctld): split that actually reaches the radio, or an honest refusal
set_split_vfo and set_split_freq both answered RPRT 0 and did nothing. WSJT-X and JTDX in "Split Operating: Rig" send exactly that pair, believed both, and transmitted on the RECEIVE frequency — on a pileup, straight onto the DX, while showing the operator precisely what they had asked for. A lie that leaves no trace in any log is the worst kind of bug this program can have. The two commands are honoured as a PAIR. Arming alone does nothing on the radio, because WSJT-X sends the frequency second and split armed on whatever the transmit VFO happened to hold is worse than no split at all: it transmits somewhere the operator never chose. The request is remembered and set_split_freq does the work. Kenwood gains SetSplit — FB to place the dial, then FR0/FT1 to arm, in that order for the same reason. It writes what State() already knows how to read. Everything else REFUSES, and that is the feature, not a shortfall. Only Flex and Icom could even toggle split before, neither could set the transmit frequency, and Yaesu, TCI and OmniRig have nothing at all. A refusal WSJT-X can report — and act on, by falling back to Fake It — is worth far more than a success it has no way to check. Both paths are pinned: split reaching the rig as one armed call with the right frequency, and a backend that cannot do it producing an error rather than RPRT 0.
This commit is contained in:
@@ -49,6 +49,10 @@ type Rig interface {
|
||||
SetFreq(hz int64) error
|
||||
SetMode(mode string) error
|
||||
SetPTT(on bool) error
|
||||
// SetSplit arms or clears split and places the transmit frequency. Returns an
|
||||
// error on a rig that cannot: a refusal the client can report is worth far
|
||||
// more than a success it has no way to check.
|
||||
SetSplit(on bool, txHz int64) error
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
@@ -76,6 +80,9 @@ type Server struct {
|
||||
// be told from the very first call — where the radio's state is unknown and
|
||||
// the command must go through.
|
||||
pttKnown atomic.Bool
|
||||
// splitWanted remembers a set_split_vfo that arrived before the frequency it
|
||||
// needs, so the pair can be honoured in the order the client sends them.
|
||||
splitWanted atomic.Bool
|
||||
}
|
||||
|
||||
func New(port int, rig Rig, logf func(string, ...any)) *Server {
|
||||
@@ -383,7 +390,31 @@ func (s *Server) handle(line string) (resp string, quit bool) {
|
||||
}
|
||||
return fmt.Sprintf("%d\nVFOB\n", n), false
|
||||
case "S", "\\set_split_vfo":
|
||||
return rprt(0), false // see set_vfo — split is driven from the rig
|
||||
// "S <0|1> <VFO>". The VFO argument is ignored: which dial transmits is the
|
||||
// rig's own business, and every backend here puts it on the second one.
|
||||
//
|
||||
// This used to answer RPRT 0 and do NOTHING. WSJT-X in "Split Operating:
|
||||
// Rig" sends this and set_split_freq, believed both, and transmitted on the
|
||||
// RECEIVE frequency — on a pileup, straight onto the DX, while the software
|
||||
// showed exactly what the operator had asked for. A lie that leaves no
|
||||
// trace anywhere is the worst kind of bug, so it now works or says so.
|
||||
if len(args) < 1 {
|
||||
return rprt(-1), false
|
||||
}
|
||||
if args[0] != "0" {
|
||||
// Arming needs a frequency, and WSJT-X sends set_split_freq AFTER this.
|
||||
// Remember the request and let that command do the work: alone, this
|
||||
// would arm split on whatever the transmit VFO happens to hold.
|
||||
s.splitWanted.Store(true)
|
||||
return rprt(0), false
|
||||
}
|
||||
s.splitWanted.Store(false)
|
||||
if err := s.rig.SetSplit(false, 0); err != nil {
|
||||
s.log("rigctld: split off failed: %v", err)
|
||||
return rprt(-9), false
|
||||
}
|
||||
s.log("rigctld: split off")
|
||||
return rprt(0), false
|
||||
case "i", "\\get_split_freq":
|
||||
_, tx := s.rig.Split()
|
||||
if tx <= 0 {
|
||||
@@ -391,6 +422,20 @@ func (s *Server) handle(line string) (resp string, quit bool) {
|
||||
}
|
||||
return fmt.Sprintf("%d\n", tx), false
|
||||
case "I", "\\set_split_freq":
|
||||
if len(args) < 1 {
|
||||
return rprt(-1), false
|
||||
}
|
||||
// Hamlib sends a float ("14075300.000000"), so parse as one.
|
||||
hz, err := strconv.ParseFloat(args[0], 64)
|
||||
if err != nil || hz <= 0 {
|
||||
return rprt(-1), false
|
||||
}
|
||||
if err := s.rig.SetSplit(true, int64(hz)); err != nil {
|
||||
s.log("rigctld: split TX %.0f Hz failed: %v", hz, err)
|
||||
return rprt(-9), false
|
||||
}
|
||||
s.splitWanted.Store(true)
|
||||
s.log("rigctld: split ON, TX %.0f Hz", hz)
|
||||
return rprt(0), false
|
||||
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user