fix: a CW macro knocked the Yaesu CAT link over
Clicking a macro dropped the CAT for a few seconds, keyed nothing, then came back. ask() returned the first ';'-terminated frame it saw, whatever command it belonged to. KY produces NO reply, so the next query — FA; from the poll loop — collected a leftover frame, failed to parse it as a frequency, and ReadState reported an error. The Manager reads that as "lost the rig": disconnect, wait, reconnect. Hence the drop and the automatic recovery a few seconds later. Replies are now matched to the command that asked for them: anything else is discarded and logged, so a stray frame costs one log line instead of the link. This also explains the silence — the send never got a clean run at the port while the backend was being torn down under it.
This commit is contained in:
+33
-4
@@ -297,8 +297,16 @@ func (y *Yaesu) ask(cmd string) (string, error) {
|
||||
if err := y.write(cmd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := make([]byte, 0, 32)
|
||||
tmp := make([]byte, 32)
|
||||
// Match the reply to the COMMAND, and drop anything else.
|
||||
//
|
||||
// Returning the first ';'-terminated string whatever it was is what made a CW
|
||||
// macro knock the CAT link over: KY produces no reply, so the next query —
|
||||
// FA; from the poll loop — collected a leftover frame, failed to parse as a
|
||||
// frequency, and the Manager treated that as "lost the rig" and reconnected.
|
||||
// The operator saw the CAT drop for a few seconds on every macro click.
|
||||
want := cmdPrefix(cmd)
|
||||
buf := make([]byte, 0, 64)
|
||||
tmp := make([]byte, 64)
|
||||
deadline := time.Now().Add(600 * time.Millisecond)
|
||||
for time.Now().Before(deadline) {
|
||||
n, err := y.port.Read(tmp)
|
||||
@@ -309,8 +317,17 @@ func (y *Yaesu) ask(cmd string) (string, error) {
|
||||
continue // read timeout — the rig may still be composing its answer
|
||||
}
|
||||
buf = append(buf, tmp[:n]...)
|
||||
if i := strings.IndexByte(string(buf), ';'); i >= 0 {
|
||||
return string(buf[:i+1]), nil
|
||||
for {
|
||||
i := strings.IndexByte(string(buf), ';')
|
||||
if i < 0 {
|
||||
break
|
||||
}
|
||||
frame := string(buf[:i+1])
|
||||
buf = buf[i+1:]
|
||||
if want == "" || strings.HasPrefix(strings.ToUpper(frame), want) {
|
||||
return frame, nil
|
||||
}
|
||||
debugLog.Printf("yaesu: discarding %q while waiting for %s (asked %q)", frame, want, cmd)
|
||||
}
|
||||
if len(buf) > 512 {
|
||||
return "", fmt.Errorf("yaesu: no ';' in %d bytes answering %q", len(buf), cmd)
|
||||
@@ -319,6 +336,18 @@ func (y *Yaesu) ask(cmd string) (string, error) {
|
||||
return "", fmt.Errorf("yaesu: timeout answering %q", cmd)
|
||||
}
|
||||
|
||||
// cmdPrefix is the leading letters of a command — what its reply starts with.
|
||||
// "FA;" → "FA", "MD0;" → "MD", "KY;" → "KY".
|
||||
func cmdPrefix(cmd string) string {
|
||||
c := strings.ToUpper(strings.TrimSpace(cmd))
|
||||
for i := 0; i < len(c); i++ {
|
||||
if c[i] < 'A' || c[i] > 'Z' {
|
||||
return c[:i]
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// parseYaesuFreq reads "FA014074000;" into Hz.
|
||||
func parseYaesuFreq(reply, prefix string) (int64, bool) {
|
||||
r := strings.TrimSpace(reply)
|
||||
|
||||
@@ -103,3 +103,30 @@ func TestYaesuModeDigit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A reply belongs to the command that asked for it.
|
||||
//
|
||||
// Without this, a CW macro knocked the CAT link over: KY produces no reply, so
|
||||
// the poll loop's next FA; collected a leftover frame, failed to parse it as a
|
||||
// frequency, and the Manager treated that as "lost the rig" and reconnected —
|
||||
// the CAT dropping for a few seconds on every macro click.
|
||||
func TestYaesuCmdPrefix(t *testing.T) {
|
||||
cases := []struct{ cmd, want string }{
|
||||
{"FA;", "FA"},
|
||||
{"FB;", "FB"},
|
||||
{"MD0;", "MD"},
|
||||
{"KY;", "KY"},
|
||||
{"KY CQ TEST;", "KY"},
|
||||
{"SM0;", "SM"},
|
||||
{"RM4;", "RM"},
|
||||
{"AG0;", "AG"},
|
||||
{"TX;", "TX"},
|
||||
{"", ""},
|
||||
{";", ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := cmdPrefix(c.cmd); got != c.want {
|
||||
t.Errorf("cmdPrefix(%q) = %q, want %q", c.cmd, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user