refactor(winkeyer): learn the slow boot instead of naming the keyer

The K3NG entry added an hour ago is gone. It named one clone among many —
WKmini, home-built Arduinos, unbranded boxes — for hardware that speaks
exactly the same protocol, and it was the only line in the engine list that
picked a boot delay rather than a protocol. An operator with an unlabelled
clone would have had to guess.

The delay is now learnt per port. The first connect finds out by failing the
quick attempt and succeeding on the slow one; that fact is written to a
global setting keyed by the port, and every connect afterwards goes straight
to the slow attempt. Global rather than per profile on purpose: which keyer
is plugged into COM3 belongs to the computer, and switching profiles for a
different rig does not change the keyer on the desk.

Two tests hold the contract from both sides — a slow keyer must be reported
as slow, and a keyer that answers at once must not be, or every K1EL connect
would inherit seconds it never needed.
This commit is contained in:
2026-08-14 13:05:13 +02:00
parent aca4dc5678
commit 3ce930e9cc
7 changed files with 94 additions and 30 deletions
+40 -6
View File
@@ -1326,6 +1326,7 @@ func (a *App) startup(ctx context.Context) {
}
},
)
a.winkeyer.OnSlowBoot(a.noteKeyerSlowBoot)
// Live space-weather (solar flux, sunspots, A/K indices) for the header strip
// and per-QSO stamping. Refreshes in the background; pushes a UI event on each
@@ -16677,6 +16678,43 @@ func (a *App) CIVTraceEnabled() bool { return cat.CIVTraceEnabled() }
// WinkeyerTraceEnabled — same, for the keyer trace.
func (a *App) WinkeyerTraceEnabled() bool { return winkeyer.TraceEnabled() }
// keyerSlowBootKey names the "this port holds a keyer that reboots when opened"
// flag. GLOBAL, not per profile: which keyer is plugged into COM3 is a property
// of the computer, and an operator who switches profiles for a different rig has
// not changed the keyer on the desk.
func keyerSlowBootKey(port string) string {
return "winkeyer.slow_boot." + strings.ToUpper(strings.TrimSpace(port))
}
// keyerNeedsSlowBoot reports whether this port has already been seen to need the
// long opening wait.
//
// A K3NG keyer — an Arduino running the WinKeyer protocol — reboots when the
// port opens, because DTR is wired to its reset pin. The first connect finds
// that out by failing the quick attempt and succeeding on the slow one; every
// connect afterwards reads this and goes straight to the slow one.
//
// Learnt rather than configured. The alternative was a "K3NG" entry beside
// "WinKeyer" in the engine list, which would name one clone among many — WKmini,
// home-built Arduinos, unbranded boxes — and leave every other operator guessing
// which of the two to pick for hardware that speaks exactly the same protocol.
func (a *App) keyerNeedsSlowBoot(port string) bool {
if a.settings == nil || strings.TrimSpace(port) == "" {
return false
}
v, _ := a.settings.GetGlobal(a.ctx, keyerSlowBootKey(port))
return v == "1"
}
// noteKeyerSlowBoot records that this port's keyer needed the long wait.
func (a *App) noteKeyerSlowBoot(port string) {
if a.settings == nil || strings.TrimSpace(port) == "" {
return
}
_ = a.settings.SetGlobal(a.ctx, keyerSlowBootKey(port), "1")
applog.Printf("winkeyer: %s remembered as a slow-booting keyer — it will open directly next time", port)
}
// WinkeyerConnect opens the serial link using the saved config.
func (a *App) WinkeyerConnect() error {
if a.winkeyer == nil {
@@ -16689,14 +16727,10 @@ func (a *App) WinkeyerConnect() error {
cfg := s.Config
// The "serial" engine keys CW on the port's DTR/RTS lines (SCU-17 style)
// instead of talking the K1EL WinKeyer protocol — flag it for the manager.
// "k3ng" speaks the same protocol as a K1EL and differs only in how long it
// takes to answer after the port opens; the manager needs to know which.
switch s.Engine {
case "serial":
if s.Engine == "serial" {
cfg.Type = "serial"
case "k3ng":
cfg.Type = "k3ng"
}
cfg.SlowBoot = a.keyerNeedsSlowBoot(cfg.Port)
return a.winkeyer.Connect(cfg)
}