fix(winkeyer): send the probe the way a working client sends it
Logger32's WinKeyer debug against the K3NG that will not answer OpsLog is a
capture of the exchange working, on the same keyer and the same port:
Sent: 13 13 13 00 04 55
Rcvd: 55 (72 ms)
Sent: 00 02 Host open
Rcvd: 23 (WK2 v23)
That is the sequence I already send. The difference is the grouping: Logger32
puts the three nulls and the echo probe in ONE write, and we split them with a
50 ms pause and a buffer purge in between. On a keyer that reboots when the
port opens, that pause is a window for it to come up mid-sequence and swallow
half of it — and there was nothing to wait for, since a null produces no reply.
The handshake bytes are now logged unconditionally, not behind the diagnostic
option. "No WinKeyer answered" cannot be told apart from a wrong port, a wrong
baud rate, a keyer still booting, or another program holding the line. The
bytes can, and it is four lines per connect attempt.
This commit is contained in:
@@ -30,10 +30,10 @@ import (
|
||||
// the one to fail on: everything after it assumes a listener.
|
||||
// open 0x00 0x02, and the keyer returns its firmware version.
|
||||
const (
|
||||
cmdNull = 0x13
|
||||
cmdAdmin = 0x00
|
||||
adminOpen = 0x02
|
||||
adminEcho = 0x04
|
||||
cmdNull = 0x13
|
||||
cmdAdmin = 0x00
|
||||
adminOpen = 0x02
|
||||
adminEcho = 0x04
|
||||
echoProbe = 0x55 // K1EL's own choice; any byte works, this one is 0b01010101
|
||||
bootDelay = 400 * time.Millisecond
|
||||
echoTimeout = 2 * time.Second // K1EL: "if a WK doesn't respond within 2 seconds abort"
|
||||
@@ -95,18 +95,25 @@ func hostOpenOnce(p serial.Port, boot time.Duration) (int, error) {
|
||||
time.Sleep(boot)
|
||||
drain(p)
|
||||
|
||||
// Resync the command parser before asking it anything.
|
||||
if _, err := p.Write([]byte{cmdNull, cmdNull, cmdNull}); err != nil {
|
||||
return 0, fmt.Errorf("resync: %w", err)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
drain(p)
|
||||
|
||||
// Is anything actually there?
|
||||
if _, err := p.Write([]byte{cmdAdmin, adminEcho, echoProbe}); err != nil {
|
||||
// The resync nulls and the echo probe go out as ONE write.
|
||||
//
|
||||
// Copied byte for byte from a Logger32 capture against the K3NG keyer that
|
||||
// would not answer OpsLog: "Sent: 13 13 13 00 04 55 / Rcvd: 55". Same keyer,
|
||||
// same port, same six bytes — the only difference was that we sent them as
|
||||
// two writes with a pause and a buffer purge in between, and Logger32 sends
|
||||
// them as one. On a keyer that reboots when the port opens, that pause is a
|
||||
// window for it to come up mid-sequence and swallow half of it.
|
||||
//
|
||||
// There is nothing to wait for between the two halves anyway: a null produces
|
||||
// no reply, so the pause was only ever giving the keyer a chance to change
|
||||
// its mind.
|
||||
probe := []byte{cmdNull, cmdNull, cmdNull, cmdAdmin, adminEcho, echoProbe}
|
||||
traceHandshake("TX", probe, 0, false)
|
||||
if _, err := p.Write(probe); err != nil {
|
||||
return 0, fmt.Errorf("echo test: %w", err)
|
||||
}
|
||||
b, ok := readByte(p, echoTimeout)
|
||||
traceHandshake("RX", nil, b, ok)
|
||||
if !ok {
|
||||
return 0, errNoKeyer
|
||||
}
|
||||
@@ -116,16 +123,37 @@ func hostOpenOnce(p serial.Port, boot time.Duration) (int, error) {
|
||||
return 0, fmt.Errorf("echo test: expected 0x%02X, got 0x%02X — is this the keyer's port?", echoProbe, b)
|
||||
}
|
||||
|
||||
if _, err := p.Write([]byte{cmdAdmin, adminOpen}); err != nil {
|
||||
open := []byte{cmdAdmin, adminOpen}
|
||||
traceHandshake("TX", open, 0, false)
|
||||
if _, err := p.Write(open); err != nil {
|
||||
return 0, fmt.Errorf("host open: %w", err)
|
||||
}
|
||||
ver, ok := readByte(p, openTimeout)
|
||||
traceHandshake("RX", nil, ver, ok)
|
||||
if !ok {
|
||||
return 0, errors.New("host open: the keyer echoed but did not return its firmware version")
|
||||
}
|
||||
return int(ver), nil
|
||||
}
|
||||
|
||||
// traceHandshake puts the opening exchange in the log, ALWAYS — unlike the
|
||||
// running trace beside it, which is behind the diagnostic option.
|
||||
//
|
||||
// A failure that says only "no WinKeyer answered" cannot be told apart from a
|
||||
// wrong port, a wrong baud rate, a keyer still rebooting, or another program
|
||||
// holding the line. The bytes can. It is four lines per connect, and only when
|
||||
// the connect is attempted.
|
||||
func traceHandshake(dir string, b []byte, got byte, ok bool) {
|
||||
switch {
|
||||
case dir == "TX":
|
||||
applog.Printf("winkeyer: handshake TX % 02X", b)
|
||||
case ok:
|
||||
applog.Printf("winkeyer: handshake RX %02X", got)
|
||||
default:
|
||||
applog.Printf("winkeyer: handshake RX — nothing came back")
|
||||
}
|
||||
}
|
||||
|
||||
// readByte waits up to d for one byte. The serial read timeout is per-call and
|
||||
// can return 0 bytes without an error, so this loops until the deadline rather
|
||||
// than trusting a single Read.
|
||||
|
||||
@@ -102,6 +102,8 @@ func TestHostOpenFollowsK1ELSequence(t *testing.T) {
|
||||
if ver != 23 {
|
||||
t.Errorf("version = %d, want 23", ver)
|
||||
}
|
||||
// One write for the six probe bytes, then Host Open — the order and the
|
||||
// grouping of a Logger32 capture against a real K3NG.
|
||||
want := []byte{
|
||||
cmdNull, cmdNull, cmdNull,
|
||||
cmdAdmin, adminEcho, echoProbe,
|
||||
|
||||
Reference in New Issue
Block a user