fix: the Yaesu keyer refused to send — a status reply we read too strictly
"the keyer buffer stayed full for 4s" and nothing keyed, on a rig whose CAT is working. The buffer was not full: the check demanded the reply be exactly "KY0;" with the digit at byte 2, so anything the FTDX10 phrases differently — a space before the digit, another command's reply arriving first — read as "still full" on every poll until the deadline. The test is now asymmetric on purpose: only a clear "1" holds the send back. Anything unrecognised goes ahead. Refusing to transmit because a status line was phrased unexpectedly is the worse failure — the operator gets silence with no explanation, where at worst sending early truncates a long message, which the chunk loop then recovers from. The reply is also logged once per run, since we have no verified sample of it — that line is what turns the next surprise into a fact instead of a theory.
This commit is contained in:
@@ -27,6 +27,10 @@ import (
|
|||||||
// split and fed as the rig drains its buffer.
|
// split and fed as the rig drains its buffer.
|
||||||
const yaesuCWChunk = 24
|
const yaesuCWChunk = 24
|
||||||
|
|
||||||
|
// cwStatusLogged makes the KY status reply appear in the log once per run: it
|
||||||
|
// is a line we have no verified sample of, so the first one is worth keeping.
|
||||||
|
var cwStatusLogged bool
|
||||||
|
|
||||||
// yaesuCWAllowed is what the rig's keyer can actually send. Anything else is
|
// yaesuCWAllowed is what the rig's keyer can actually send. Anything else is
|
||||||
// dropped rather than passed through: an unsupported byte can abort the whole
|
// dropped rather than passed through: an unsupported byte can abort the whole
|
||||||
// buffer, losing the rest of the message with no error anywhere.
|
// buffer, losing the rest of the message with no error anywhere.
|
||||||
@@ -97,16 +101,57 @@ func (y *Yaesu) waitCWBuffer(within time.Duration) error {
|
|||||||
debugLog.Printf("yaesu cw: KY status query failed (%v) — sending anyway", err)
|
debugLog.Printf("yaesu cw: KY status query failed (%v) — sending anyway", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(r) >= 3 && r[2] == '0' {
|
// Only an explicit "full" holds us back.
|
||||||
return nil // room in the buffer
|
//
|
||||||
|
// The first version required the reply to be exactly "KY0;" at byte 2, and
|
||||||
|
// on a real FTDX10 that refused to send at all: anything the rig phrases
|
||||||
|
// differently — a space before the digit, a longer status — read as "still
|
||||||
|
// full" and the send died after four seconds having keyed nothing. The rig
|
||||||
|
// is the one that knows; when its answer is not a clear "1", the right move
|
||||||
|
// is to go ahead rather than to sit and time out.
|
||||||
|
// Log the shape once per session: this is a reply we have no verified
|
||||||
|
// sample of, and the log is what turns the next surprise into a fact.
|
||||||
|
if !cwStatusLogged {
|
||||||
|
cwStatusLogged = true
|
||||||
|
debugLog.Printf("yaesu cw: KY status reply is %q (full=%v)", r, yaesuCWBufferFull(r))
|
||||||
|
}
|
||||||
|
if !yaesuCWBufferFull(r) {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if time.Now().After(deadline) {
|
if time.Now().After(deadline) {
|
||||||
return fmt.Errorf("yaesu: the keyer buffer stayed full for %s", within)
|
return fmt.Errorf("yaesu: the keyer buffer stayed full for %s (last reply %q)", within, r)
|
||||||
}
|
}
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// yaesuCWBufferFull reads the KY status reply.
|
||||||
|
//
|
||||||
|
// Deliberately asymmetric: only a clear "1" means full. Anything else — an
|
||||||
|
// unexpected shape, a reply from another command that arrived first, an empty
|
||||||
|
// string — is treated as "go ahead". Refusing to send because a status line was
|
||||||
|
// phrased unexpectedly is the worse failure: the operator gets silence with no
|
||||||
|
// explanation, where at worst sending early truncates a long message.
|
||||||
|
func yaesuCWBufferFull(reply string) bool {
|
||||||
|
r := strings.TrimSpace(reply)
|
||||||
|
if !strings.HasPrefix(strings.ToUpper(r), "KY") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range r[2:] {
|
||||||
|
switch c {
|
||||||
|
case '0':
|
||||||
|
return false
|
||||||
|
case '1':
|
||||||
|
return true
|
||||||
|
case ' ', ';':
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return false // not a status digit — don't block on it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// filterYaesuCW upper-cases and strips what the keyer cannot send.
|
// filterYaesuCW upper-cases and strips what the keyer cannot send.
|
||||||
func filterYaesuCW(text string) string {
|
func filterYaesuCW(text string) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
|
|||||||
@@ -61,3 +61,34 @@ func TestYaesuCWChunking(t *testing.T) {
|
|||||||
t.Errorf("chunks rejoin to %q, want %q", joined, msg)
|
t.Errorf("chunks rejoin to %q, want %q", joined, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reading the KY status reply.
|
||||||
|
//
|
||||||
|
// The first version demanded exactly "KY0;" with the digit at byte 2, and on a
|
||||||
|
// real FTDX10 that refused to send at all: the reply was phrased differently,
|
||||||
|
// every poll read as "still full", and after four seconds the send gave up
|
||||||
|
// having keyed nothing. Hence the asymmetry — only a clear "1" blocks.
|
||||||
|
func TestYaesuCWBufferFull(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
reply string
|
||||||
|
full bool
|
||||||
|
}{
|
||||||
|
{"KY0;", false},
|
||||||
|
{"KY1;", true},
|
||||||
|
{"KY 0;", false}, // a space before the digit
|
||||||
|
{"KY 1;", true},
|
||||||
|
{"ky1;", true}, // lower case
|
||||||
|
{" KY0; ", false},
|
||||||
|
// Shapes we have no sample of must NOT block: silence with no explanation
|
||||||
|
// is a worse failure than sending a moment early.
|
||||||
|
{"KY;", false},
|
||||||
|
{"", false},
|
||||||
|
{"FA014074000;", false}, // another command's reply arriving first
|
||||||
|
{"KYX;", false},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := yaesuCWBufferFull(c.reply); got != c.full {
|
||||||
|
t.Errorf("yaesuCWBufferFull(%q) = %v, want %v", c.reply, got, c.full)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user