fix(cat): stop deasserting DTR/RTS on Kenwood and Yaesu
d4bfd30 fixed a Xiegu G90 behind a DE-19 that keyed on connect, and I
applied the same line-lowering to Kenwood and Yaesu with no evidence that
either needed it. It silenced them: many USB-serial interfaces will not
transmit with RTS low — hardware flow control, or an output stage the
line enables. A TS-990 on COM3 opened cleanly and answered nothing, which
reached us as "CAT stopped working after the update".
Xiegu keeps the behaviour: that is where the fault was reported, and that
backend now has an explicit setting for which line keys the rig. The CI-V
backend keeps its own, which predates all of this.
Kenwood also distinguishes a silent port from one sending data that never
answers, and quotes what arrived — "the rig is not answering" sent an
operator checking the power switch on a radio whose frames were visibly
on the wire.
This commit is contained in:
+6
-2
@@ -5,12 +5,16 @@
|
||||
"en": [
|
||||
"Main tab: drag the divider between the two panes to resize them. The setting is remembered and travels with the data folder; double-click the divider to even them out.",
|
||||
"Kenwood: bytes arriving glued to the previous answer are no longer discarded, and the link starts from a clean buffer — a rig whose replies ran together could end up one answer behind and never report connected.",
|
||||
"The CAT log line now carries the error explaining a disconnection."
|
||||
"The CAT log line now carries the error explaining a disconnection.",
|
||||
"Kenwood and Yaesu CAT connect again: 0.22.7 lowered the DTR and RTS lines on those ports, which stops many USB-serial interfaces from transmitting. Only Xiegu, where the problem was reported, still does it.",
|
||||
"Kenwood: a port that opens but sends nothing is now reported differently from one sending data that does not answer, and the error quotes what arrived."
|
||||
],
|
||||
"fr": [
|
||||
"Onglet Principal : faites glisser le séparateur entre les deux volets pour les redimensionner. Le réglage est mémorisé et suit le dossier de données ; double-clic pour les égaliser.",
|
||||
"Kenwood : les octets reçus collés à la réponse précédente ne sont plus jetés, et la liaison démarre sur un tampon vide — une radio dont les réponses s’enchaînent pouvait rester une réponse en retard et ne jamais apparaître connectée.",
|
||||
"La ligne de journal CAT indique désormais l’erreur expliquant une déconnexion."
|
||||
"La ligne de journal CAT indique désormais l’erreur expliquant une déconnexion.",
|
||||
"Le CAT Kenwood et Yaesu se connecte de nouveau : la 0.22.7 abaissait les lignes DTR et RTS sur ces ports, ce qui empêche beaucoup d’interfaces USB-série d’émettre. Seul Xiegu, où le problème avait été signalé, le fait encore.",
|
||||
"Kenwood : un port qui s’ouvre sans rien émettre est désormais distingué d’un port qui émet sans répondre, et l’erreur cite ce qui est arrivé."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+45
-15
@@ -83,6 +83,20 @@ type Kenwood struct {
|
||||
// That is the "discarding \" 000000000010000000;\" while waiting for IF"
|
||||
// a TS-480 reported, followed by connected=false for ever.
|
||||
rx []byte
|
||||
|
||||
// heard is whatever arrived during Connect that was not a reply we wanted.
|
||||
// Kept only to put it in the error message: "not answering" and "answering
|
||||
// something unreadable" are different faults with different fixes.
|
||||
heard string
|
||||
}
|
||||
|
||||
// where names the link for a message, so an error does not read "COM @ 0 baud"
|
||||
// after a network connect.
|
||||
func (k *Kenwood) where() string {
|
||||
if k.host != "" {
|
||||
return k.host
|
||||
}
|
||||
return k.portName
|
||||
}
|
||||
|
||||
// NewKenwoodTCP builds a backend that reaches the rig over a socket instead of
|
||||
@@ -140,6 +154,7 @@ func (k *Kenwood) Connect() error {
|
||||
// its question for the rest of the session.
|
||||
k.rx = nil
|
||||
k.drain(250 * time.Millisecond)
|
||||
k.heard = ""
|
||||
|
||||
answered := false
|
||||
if id, err := k.ask("ID;"); err == nil && strings.HasPrefix(id, "ID") {
|
||||
@@ -159,10 +174,22 @@ func (k *Kenwood) Connect() error {
|
||||
}
|
||||
if !answered {
|
||||
k.model = ""
|
||||
if k.host != "" {
|
||||
return fmt.Errorf("kenwood: %s accepted the connection but the rig is not answering — check that the serial bridge points at the radio and that the radio is switched on", k.host)
|
||||
if k.heard == "" && len(k.rx) > 0 {
|
||||
k.heard = string(k.rx) // an unterminated fragment is evidence too
|
||||
}
|
||||
return fmt.Errorf("kenwood: %s opened but the rig is not answering — check that it is switched on and set to %d baud", k.portName, k.baud)
|
||||
// Distinguish silence from noise. "The rig is not answering" sent an
|
||||
// operator checking the power switch and the baud rate on a radio that was
|
||||
// visibly talking — its frames were arriving, they just did not match what
|
||||
// was asked (wrong baud garbles them; an interface echoing our own
|
||||
// commands back produces the same). Say which of the two it is, and quote
|
||||
// what came back, because that is the fact that decides where to look.
|
||||
if seen := k.heard; seen != "" {
|
||||
return fmt.Errorf("kenwood: %s is sending data but no reply to ID; or IF; — got %q. Check the baud rate (set to %d here) and that nothing else is echoing the port", k.where(), seen, k.baud)
|
||||
}
|
||||
if k.host != "" {
|
||||
return fmt.Errorf("kenwood: %s accepted the connection but the rig sent nothing — check that the serial bridge points at the radio and that the radio is switched on", k.host)
|
||||
}
|
||||
return fmt.Errorf("kenwood: %s opened but the rig sent nothing — check that it is switched on and set to %d baud", k.portName, k.baud)
|
||||
}
|
||||
// Name what was actually connected to. A log reading "connected on @ 0 baud"
|
||||
// after a network connect is the kind of line that sends someone hunting a
|
||||
@@ -372,6 +399,11 @@ func (k *Kenwood) ask(cmd string) (string, error) {
|
||||
return frame, nil
|
||||
}
|
||||
debugLog.Printf("kenwood: discarding %q while waiting for %s", frame, want)
|
||||
// Remember the first unexpected frame: if the whole handshake fails, this
|
||||
// is what tells the operator the radio was talking after all.
|
||||
if k.heard == "" {
|
||||
k.heard = frame
|
||||
}
|
||||
}
|
||||
if !time.Now().Before(deadline) {
|
||||
return "", fmt.Errorf("kenwood: timeout answering %q", cmd)
|
||||
@@ -536,20 +568,18 @@ func (k *Kenwood) openPort() (serial.Port, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Deassert DTR and RTS.
|
||||
// The modem lines are LEFT ALONE.
|
||||
//
|
||||
// Windows raises both when a serial port is opened, and a great many
|
||||
// interfaces read them as PTT: a Xiegu G90 behind a DE-19 goes into
|
||||
// transmit the moment OpsLog connects and STAYS there — Xiegu's own
|
||||
// documentation asks for RTS and DTR low. The same applies to an Icom with
|
||||
// USB SEND mapped to a line (which is why the CI-V backend has done this
|
||||
// from the start) and to any rig keyed by a home-made cable.
|
||||
// They were briefly deasserted here, to stop an interface that reads them as
|
||||
// PTT from keying the rig on connect. That silenced radios instead: a TS-990
|
||||
// on COM3 opened fine and answered nothing, because a great many USB-serial
|
||||
// interfaces will not transmit with RTS low — hardware flow control, or an
|
||||
// output stage the line enables. "Opened but the rig sent nothing" was this,
|
||||
// and it arrived as "CAT stopped working after the update".
|
||||
//
|
||||
// PTT on this backend is a CAT command, so neither line should ever be
|
||||
// asserted here. A station keying by RTS/DTR configures that separately,
|
||||
// on its own port.
|
||||
_ = p.SetDTR(false)
|
||||
_ = p.SetRTS(false)
|
||||
// The fault that change was written for was a Xiegu G90 behind a DE-19, and
|
||||
// that backend now has an explicit setting for which line keys it. A rig
|
||||
// whose PTT is a CAT command has no business touching DTR or RTS at all.
|
||||
return p, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -265,8 +265,8 @@ func TestKenwoodSilentRigIsNotConnected(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("a silent port was reported as a connected rig")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not answering") {
|
||||
t.Errorf("error was %q — it should say the rig is not answering", err)
|
||||
if !strings.Contains(err.Error(), "sent nothing") {
|
||||
t.Errorf("error was %q — a silent port should be reported as silence", err)
|
||||
}
|
||||
if k.model != "" {
|
||||
t.Errorf("a stale model survived a failed connect: %q", k.model)
|
||||
@@ -355,3 +355,26 @@ func TestKenwoodSplitWhenFRFTRejected(t *testing.T) {
|
||||
t.Errorf("IF-reported split broke: split=%v tx=%d rx=%d", s.Split, s.FreqHz, s.RxFreqHz)
|
||||
}
|
||||
}
|
||||
|
||||
// A rig that talks but never answers what was asked must not be reported as a
|
||||
// silent one.
|
||||
//
|
||||
// The two faults need opposite responses: silence means the radio is off, the
|
||||
// wrong port, or a dead cable; noise means the baud rate is wrong or something
|
||||
// is echoing the line. "The rig is not answering" sent an operator checking the
|
||||
// power switch on a radio whose frames were visibly arriving.
|
||||
func TestKenwoodNoisyRigIsReportedAsNoiseNotSilence(t *testing.T) {
|
||||
k := NewKenwood("COM-TEST", 9600, "FT8")
|
||||
k.dialPort = func() (serial.Port, error) {
|
||||
return &fakeSerial{toRig: &strings.Builder{}, answer: func(cmd string) string {
|
||||
return "XX9999;" // something, but never the reply to ID; or IF;
|
||||
}}, nil
|
||||
}
|
||||
err := k.Connect()
|
||||
if err == nil {
|
||||
t.Fatal("a rig answering gibberish was reported as connected")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "sending data") || !strings.Contains(err.Error(), "XX9999;") {
|
||||
t.Errorf("error was %q — it should say data arrived, and quote it", err)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-13
@@ -150,20 +150,12 @@ func (y *Yaesu) Connect() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("yaesu: open %s @ %d baud: %w", y.portName, y.baud, err)
|
||||
}
|
||||
// Deassert DTR and RTS.
|
||||
// The modem lines are LEFT ALONE — see the same note in kenwood.go.
|
||||
//
|
||||
// Windows raises both when a serial port is opened, and a great many
|
||||
// interfaces read them as PTT: a Xiegu G90 behind a DE-19 goes into
|
||||
// transmit the moment OpsLog connects and STAYS there — Xiegu's own
|
||||
// documentation asks for RTS and DTR low. The same applies to an Icom with
|
||||
// USB SEND mapped to a line (which is why the CI-V backend has done this
|
||||
// from the start) and to any rig keyed by a home-made cable.
|
||||
//
|
||||
// PTT on this backend is a CAT command, so neither line should ever be
|
||||
// asserted here. A station keying by RTS/DTR configures that separately,
|
||||
// on its own port.
|
||||
_ = p.SetDTR(false)
|
||||
_ = p.SetRTS(false)
|
||||
// Deasserting them to keep an interface from keying the rig silenced radios
|
||||
// instead: many USB-serial interfaces will not transmit with RTS low. The
|
||||
// Xiegu fault that change was written for is handled in that backend, which
|
||||
// now has an explicit setting for which line keys the rig.
|
||||
p.SetReadTimeout(300 * time.Millisecond)
|
||||
y.port = p
|
||||
|
||||
|
||||
Reference in New Issue
Block a user