fix: Kenwood split read from FR/FT when the status frame omits it
Reported from a Flex in Kenwood CAT mode: frequency read perfectly, split never appeared. Not every rig speaking this dialect fills IF's split bit. Rather than guess which column that firmware populates, ask the question that DEFINES split — is the transmit VFO a different VFO from the receive one — which is exactly what FR and FT answer, and the same rule the Yaesu backend settled on after several wrong turns. FR is also trusted over IF for which VFO is in use: they are asked in the same breath, and a rig vague about the split bit may be just as vague about the VFO field. Cost is bounded: a rig that rejects FR/FT answers "?;" once and is never asked again, so this is two short commands per poll only where it works. Both paths are tested against the emulator — split found through FR/FT with IF silent, and IF-reported split still working on a rig that refuses FR/FT without re-asking. Also extends the CAT wire trace to the Kenwood backend, ASCII quoted so an empty reply is visible as such: "" and ";" look identical unquoted, and telling them apart is the whole question when a rig half-supports a command. If this fix is not the whole story on real hardware, the trace is what will say so.
This commit is contained in:
@@ -160,6 +160,34 @@ func (k *Kenwood) ReadState() (RigState, error) {
|
||||
// IF reports the frequency of the VFO in USE (what the operator hears).
|
||||
rx := f.FreqHz
|
||||
tx := rx
|
||||
|
||||
// IF's split bit is not filled in by every rig that speaks this dialect —
|
||||
// reported on a Flex through its Kenwood CAT emulation, where the frequency
|
||||
// reads perfectly and split never appears. So ask the question directly as
|
||||
// well: split IS "the transmit VFO differs from the receive VFO", which is
|
||||
// what FR/FT answer, and it is the same rule the Yaesu backend settled on.
|
||||
//
|
||||
// A rig that rejects FR/FT answers "?;" once and is never asked again, so
|
||||
// this costs two short commands per poll only where it actually works.
|
||||
split := f.Split
|
||||
if !split {
|
||||
rxv, rxOK := k.askVFO("FR;")
|
||||
txv, txOK := k.askVFO("FT;")
|
||||
if rxOK && txOK && rxv != txv {
|
||||
split = true
|
||||
// Trust FR over IF for which VFO is in use: they were asked in the
|
||||
// same breath, and a rig that leaves the split bit empty may be just
|
||||
// as vague about the VFO field.
|
||||
// f.VFO too, not just the reported state: the block below picks the
|
||||
// TRANSMIT VFO as "the other one" from f.VFO, and leaving the two
|
||||
// disagreeing would read the transmit frequency off the wrong dial.
|
||||
if rxv == "A" || rxv == "B" {
|
||||
k.curVFO, s.Vfo, f.VFO = rxv, rxv, rxv
|
||||
}
|
||||
}
|
||||
}
|
||||
f.Split = split
|
||||
|
||||
if f.Split {
|
||||
// The transmit VFO is the other one. Read it rather than assume, and fall
|
||||
// back to simplex if it cannot be read: a wrong TX frequency is written
|
||||
@@ -235,6 +263,7 @@ func (k *Kenwood) write(cmd string) error {
|
||||
if k.port == nil {
|
||||
return fmt.Errorf("kenwood: not connected")
|
||||
}
|
||||
traceText("kenwood", "TX", cmd)
|
||||
_, err := k.port.Write([]byte(cmd))
|
||||
return err
|
||||
}
|
||||
@@ -270,6 +299,7 @@ func (k *Kenwood) ask(cmd string) (string, error) {
|
||||
}
|
||||
frame := string(buf[:i+1])
|
||||
buf = buf[i+1:]
|
||||
traceText("kenwood", "RX", frame)
|
||||
if frame == "?;" {
|
||||
// The rig rejected the command. Remember it so the poll loop stops
|
||||
// paying a 600 ms timeout for it on every cycle.
|
||||
@@ -425,3 +455,30 @@ func (k *Kenwood) openPort() (serial.Port, error) {
|
||||
}
|
||||
return serial.Open(k.portName, &serial.Mode{BaudRate: k.baud})
|
||||
}
|
||||
|
||||
// askVFO asks FR; or FT; and returns "A" or "B".
|
||||
//
|
||||
// The reply is FR0; / FR1; — the digit right after the two-letter command.
|
||||
// Anything else (a rig that answers with more fields, or not at all) returns
|
||||
// false, and the caller keeps whatever IF said rather than inventing a split.
|
||||
func (k *Kenwood) askVFO(cmd string) (string, bool) {
|
||||
r, err := k.ask(cmd)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
want := cmdPrefix(cmd)
|
||||
body := strings.TrimSuffix(strings.TrimPrefix(r, want), ";")
|
||||
if body == "" {
|
||||
return "", false
|
||||
}
|
||||
switch body[0] {
|
||||
case '0':
|
||||
return "A", true
|
||||
case '1':
|
||||
return "B", true
|
||||
}
|
||||
// 2 is "sub receiver" on a TS-2000 — real, but not a VFO we track. Saying
|
||||
// nothing is better than mapping it onto A or B and reporting a split that
|
||||
// does not exist.
|
||||
return "", false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user