fix: serial CW keyer PTT dropping between words on CP210x (SCU-17)
Confirmed from HB9HBY's log: ptt=true, yet the FT-891 dropped to RX between words during a CQ macro — while the same SCU-17 works in N1MM. Root cause: go.bug.st/serial drives DTR/RTS through GetCommState/SetCommState, which on the SCU-17's Silicon Labs CP210x drops the asserted RTS (PTT) the moment DTR (CW) is toggled. N1MM/WSJT use EscapeCommFunction, which sets one line without disturbing the other. OpsLog now drives the lines the same way: a new lineCtl abstraction with a Windows implementation (linectl_windows.go) that opens the COM port via CreateFile and toggles DTR/RTS with windows.EscapeCommFunction (SETDTR/CLRDTR/ SETRTS/CLRRTS). linectl_other.go keeps go.bug.st/serial for non-Windows builds. The serial keyer holds a lineCtl instead of a serial.Port. RTS (PTT) now stays asserted for the whole macro, so the rig holds TX between words. Promotes golang.org/x/sys to a direct dependency.
This commit is contained in:
@@ -17,11 +17,21 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.bug.st/serial"
|
||||
|
||||
"hamlog/internal/applog"
|
||||
)
|
||||
|
||||
// lineCtl drives a serial port's DTR and RTS control lines. On Windows it uses
|
||||
// EscapeCommFunction (SETDTR/CLRDTR/SETRTS/CLRRTS) — the direct method N1MM/WSJT
|
||||
// use, reliable on USB-serial adapters (CP210x/FTDI). go.bug.st/serial drives the
|
||||
// lines via SetCommState instead, which on a CP210x drops the held RTS (PTT) as
|
||||
// soon as DTR (CW) is toggled — the "rig drops to RX between words" bug. See
|
||||
// linectl_windows.go / linectl_other.go.
|
||||
type lineCtl interface {
|
||||
setDTR(on bool) error
|
||||
setRTS(on bool) error
|
||||
close() error
|
||||
}
|
||||
|
||||
// morseTable maps a keyable character to its Morse element string (. = dit,
|
||||
// - = dah). Mirrors winkeyer.allowedCW.
|
||||
var morseTable = map[rune]string{
|
||||
@@ -42,7 +52,7 @@ var morseTable = map[rune]string{
|
||||
// (key line, invert, PTT, lead/tail) are atomic so ApplyConfig can update them
|
||||
// LIVE — e.g. ticking "Key PTT line" takes effect without reconnecting the keyer.
|
||||
type serialKeyer struct {
|
||||
port serial.Port
|
||||
line lineCtl
|
||||
keyDTR atomic.Bool // true: CW on DTR, PTT on RTS (N1MM default). false: swapped.
|
||||
invert atomic.Bool // true: active-LOW — asserting a line means driving it LOW
|
||||
usePTT atomic.Bool // hold the PTT line for the whole transmission
|
||||
@@ -60,9 +70,9 @@ type serialKeyer struct {
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newSerialKeyer(port serial.Port, cfg Config, onBusy func(bool)) *serialKeyer {
|
||||
func newSerialKeyer(line lineCtl, cfg Config, onBusy func(bool)) *serialKeyer {
|
||||
k := &serialKeyer{
|
||||
port: port,
|
||||
line: line,
|
||||
onBusy: onBusy,
|
||||
wpm: cfg.WPM,
|
||||
farns: cfg.Farnsworth,
|
||||
@@ -104,9 +114,9 @@ func (k *serialKeyer) level(active bool) bool { return active != k.invert.Load()
|
||||
// setKey raises/lowers the CW key line; setPTT the PTT line (only when enabled).
|
||||
func (k *serialKeyer) setKey(down bool) {
|
||||
if k.keyDTR.Load() {
|
||||
_ = k.port.SetDTR(k.level(down))
|
||||
_ = k.line.setDTR(k.level(down))
|
||||
} else {
|
||||
_ = k.port.SetRTS(k.level(down))
|
||||
_ = k.line.setRTS(k.level(down))
|
||||
}
|
||||
}
|
||||
func (k *serialKeyer) setPTT(on bool) {
|
||||
@@ -114,17 +124,17 @@ func (k *serialKeyer) setPTT(on bool) {
|
||||
return
|
||||
}
|
||||
if k.keyDTR.Load() {
|
||||
_ = k.port.SetRTS(k.level(on))
|
||||
_ = k.line.setRTS(k.level(on))
|
||||
} else {
|
||||
_ = k.port.SetDTR(k.level(on))
|
||||
_ = k.line.setDTR(k.level(on))
|
||||
}
|
||||
}
|
||||
|
||||
// idle drives BOTH lines to their inactive level (key up, PTT off), respecting
|
||||
// the invert option — so an active-LOW interface isn't left keyed at rest.
|
||||
func (k *serialKeyer) idle() {
|
||||
_ = k.port.SetDTR(k.level(false))
|
||||
_ = k.port.SetRTS(k.level(false))
|
||||
_ = k.line.setDTR(k.level(false))
|
||||
_ = k.line.setRTS(k.level(false))
|
||||
}
|
||||
|
||||
// SetSpeed / Send / Stop mirror the WinKeyer manager's control surface.
|
||||
@@ -160,6 +170,7 @@ func (k *serialKeyer) Stop() {
|
||||
func (k *serialKeyer) Close() {
|
||||
close(k.stop)
|
||||
<-k.done
|
||||
_ = k.line.close()
|
||||
}
|
||||
|
||||
func (k *serialKeyer) run() {
|
||||
|
||||
Reference in New Issue
Block a user