fix(kpa): stop switching the KPA500 off, and answer its operator instantly
Three faults, one report. The slow poll asks ^TP — the KPA1500's ATU, which a KPA500 (no ATU) never answers — and ask() dropped the whole connection on any read timeout: a two-second stall and a reconnect every slow cycle, which is why buttons lagged and the status read wrong. Worse, every serial reopen toggled DTR/RTS — and those lines are the KPA500's POWER SWITCH (that is how the Elecraft utility turns it on), so the amplifier obediently switched off twenty seconds after its operator pressed nothing but Standby. Silence is no longer a dead link (write errors still are), ^TP is never asked again after one silence, the control lines are asserted once and held, and the baud field becomes a list of the rates these amplifiers actually speak.
This commit is contained in:
+27
-4
@@ -79,9 +79,10 @@ type Config struct {
|
||||
type Client struct {
|
||||
cfg Config
|
||||
|
||||
mu sync.Mutex // serialises the connection: one question at a time
|
||||
conn io.ReadWriteCloser
|
||||
rd *bufio.Reader
|
||||
mu sync.Mutex // serialises the connection: one question at a time
|
||||
conn io.ReadWriteCloser
|
||||
rd *bufio.Reader
|
||||
skipTP bool // ^TP went unanswered once — a KPA500, no ATU; never ask again
|
||||
|
||||
statusMu sync.RWMutex
|
||||
status Status
|
||||
@@ -183,6 +184,13 @@ func (c *Client) connectLocked() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open %s: %w", c.cfg.ComPort, err)
|
||||
}
|
||||
// The KPA500 is POWER-CONTROLLED by these lines: the Elecraft utility
|
||||
// switches the amplifier on by raising them. Held asserted, once, and
|
||||
// never touched again — reconnect cycles that toggled them were
|
||||
// switching a KPA500 OFF twenty seconds after its operator pressed
|
||||
// nothing but Standby.
|
||||
_ = p.SetDTR(true)
|
||||
_ = p.SetRTS(true)
|
||||
_ = p.SetReadTimeout(ioTimeout)
|
||||
c.conn = p
|
||||
}
|
||||
@@ -214,7 +222,13 @@ func (c *Client) ask(cmd string) (string, error) {
|
||||
// the frame.
|
||||
line, err := c.rd.ReadString(';')
|
||||
if err != nil {
|
||||
c.dropLocked()
|
||||
// NOT dropped. A command this model simply does not know (^TP is the
|
||||
// KPA1500's ATU — a KPA500 never answers it) is silence, not a dead
|
||||
// link, and dropping here tore the connection down on every slow poll
|
||||
// cycle: two seconds of stalled commands, a reconnect, and a DTR
|
||||
// toggle the amplifier read as the off switch. Nothing arrived, so
|
||||
// nothing is left to desynchronise the next exchange. Write errors —
|
||||
// the genuinely dead link — still drop, above.
|
||||
return "", fmt.Errorf("no answer to %s: %w", cmd, err)
|
||||
}
|
||||
return strings.TrimSpace(line), nil
|
||||
@@ -364,12 +378,21 @@ func (c *Client) pollOnce(n uint64) {
|
||||
c.statusMu.Unlock()
|
||||
}
|
||||
}
|
||||
if c.skipTP {
|
||||
return
|
||||
}
|
||||
if reply, err := c.ask("^TP;"); err == nil {
|
||||
if v, err := parseInt(reply, "^TP"); err == nil {
|
||||
c.statusMu.Lock()
|
||||
c.status.Tuning = v == 1
|
||||
c.statusMu.Unlock()
|
||||
}
|
||||
} else {
|
||||
// One silence is the model's answer for good: a KPA500 has no ATU and
|
||||
// will never answer ^TP — asking again every cycle cost a two-second
|
||||
// stall each time.
|
||||
c.skipTP = true
|
||||
applog.Printf("kpa: ^TP unanswered — no ATU on this model, not asking again")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user