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": [
|
"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.",
|
"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.",
|
"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": [
|
"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.",
|
"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.",
|
"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"
|
// That is the "discarding \" 000000000010000000;\" while waiting for IF"
|
||||||
// a TS-480 reported, followed by connected=false for ever.
|
// a TS-480 reported, followed by connected=false for ever.
|
||||||
rx []byte
|
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
|
// 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.
|
// its question for the rest of the session.
|
||||||
k.rx = nil
|
k.rx = nil
|
||||||
k.drain(250 * time.Millisecond)
|
k.drain(250 * time.Millisecond)
|
||||||
|
k.heard = ""
|
||||||
|
|
||||||
answered := false
|
answered := false
|
||||||
if id, err := k.ask("ID;"); err == nil && strings.HasPrefix(id, "ID") {
|
if id, err := k.ask("ID;"); err == nil && strings.HasPrefix(id, "ID") {
|
||||||
@@ -159,10 +174,22 @@ func (k *Kenwood) Connect() error {
|
|||||||
}
|
}
|
||||||
if !answered {
|
if !answered {
|
||||||
k.model = ""
|
k.model = ""
|
||||||
if k.host != "" {
|
if k.heard == "" && len(k.rx) > 0 {
|
||||||
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)
|
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"
|
// 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
|
// 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
|
return frame, nil
|
||||||
}
|
}
|
||||||
debugLog.Printf("kenwood: discarding %q while waiting for %s", frame, want)
|
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) {
|
if !time.Now().Before(deadline) {
|
||||||
return "", fmt.Errorf("kenwood: timeout answering %q", cmd)
|
return "", fmt.Errorf("kenwood: timeout answering %q", cmd)
|
||||||
@@ -536,20 +568,18 @@ func (k *Kenwood) openPort() (serial.Port, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// They were briefly deasserted here, to stop an interface that reads them as
|
||||||
// interfaces read them as PTT: a Xiegu G90 behind a DE-19 goes into
|
// PTT from keying the rig on connect. That silenced radios instead: a TS-990
|
||||||
// transmit the moment OpsLog connects and STAYS there — Xiegu's own
|
// on COM3 opened fine and answered nothing, because a great many USB-serial
|
||||||
// documentation asks for RTS and DTR low. The same applies to an Icom with
|
// interfaces will not transmit with RTS low — hardware flow control, or an
|
||||||
// USB SEND mapped to a line (which is why the CI-V backend has done this
|
// output stage the line enables. "Opened but the rig sent nothing" was this,
|
||||||
// from the start) and to any rig keyed by a home-made cable.
|
// and it arrived as "CAT stopped working after the update".
|
||||||
//
|
//
|
||||||
// PTT on this backend is a CAT command, so neither line should ever be
|
// The fault that change was written for was a Xiegu G90 behind a DE-19, and
|
||||||
// asserted here. A station keying by RTS/DTR configures that separately,
|
// that backend now has an explicit setting for which line keys it. A rig
|
||||||
// on its own port.
|
// whose PTT is a CAT command has no business touching DTR or RTS at all.
|
||||||
_ = p.SetDTR(false)
|
|
||||||
_ = p.SetRTS(false)
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -265,8 +265,8 @@ func TestKenwoodSilentRigIsNotConnected(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("a silent port was reported as a connected rig")
|
t.Fatal("a silent port was reported as a connected rig")
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "not answering") {
|
if !strings.Contains(err.Error(), "sent nothing") {
|
||||||
t.Errorf("error was %q — it should say the rig is not answering", err)
|
t.Errorf("error was %q — a silent port should be reported as silence", err)
|
||||||
}
|
}
|
||||||
if k.model != "" {
|
if k.model != "" {
|
||||||
t.Errorf("a stale model survived a failed connect: %q", 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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("yaesu: open %s @ %d baud: %w", y.portName, y.baud, err)
|
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
|
// Deasserting them to keep an interface from keying the rig silenced radios
|
||||||
// interfaces read them as PTT: a Xiegu G90 behind a DE-19 goes into
|
// instead: many USB-serial interfaces will not transmit with RTS low. The
|
||||||
// transmit the moment OpsLog connects and STAYS there — Xiegu's own
|
// Xiegu fault that change was written for is handled in that backend, which
|
||||||
// documentation asks for RTS and DTR low. The same applies to an Icom with
|
// now has an explicit setting for which line keys the rig.
|
||||||
// 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)
|
|
||||||
p.SetReadTimeout(300 * time.Millisecond)
|
p.SetReadTimeout(300 * time.Millisecond)
|
||||||
y.port = p
|
y.port = p
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user