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:
@@ -513,6 +513,43 @@ func isKenwoodDataMode(mode string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SetSplit arms or clears split, and when arming puts txHz on the transmit VFO.
|
||||
//
|
||||
// Both halves in one call on purpose. WSJT-X sends "split on, VFO B" and "VFO B
|
||||
// to 14075300" as two commands, and honouring only the first is worse than
|
||||
// honouring neither: split would arm on whatever VFO B happened to hold, so the
|
||||
// operator transmits somewhere they never chose while the software reports
|
||||
// exactly what they asked for. Nothing is armed here until the frequency is on
|
||||
// the dial.
|
||||
//
|
||||
// FR selects the receive VFO, FT the transmit one — the same pair the poll loop
|
||||
// already reads to detect split, so this writes what State() knows how to read.
|
||||
func (k *Kenwood) SetSplit(on bool, txHz int64) error {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
if k.port == nil {
|
||||
return fmt.Errorf("kenwood: not connected")
|
||||
}
|
||||
if !on {
|
||||
// Transmit follows receive again. FR is left alone: which VFO the operator
|
||||
// listens on is theirs to choose, and clearing split should not move them.
|
||||
return k.write("FT0;")
|
||||
}
|
||||
if txHz <= 0 || txHz > 99_999_999_999 {
|
||||
return fmt.Errorf("kenwood: split TX frequency %d out of the 11-digit CAT range", txHz)
|
||||
}
|
||||
// The transmit dial FIRST, then arm. Arming first would transmit on the old
|
||||
// contents of VFO B for however long the next command takes to arrive — brief,
|
||||
// but on the wrong frequency, and this runs the instant before a transmission.
|
||||
if err := k.write(fmt.Sprintf("FB%011d;", txHz)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := k.write("FR0;"); err != nil { // receive on A
|
||||
return err
|
||||
}
|
||||
return k.write("FT1;") // transmit on B
|
||||
}
|
||||
|
||||
func (k *Kenwood) SetPTT(on bool) error {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user