feat: FlexRadio CWX CW keyer (send/stop/speed) — no WinKeyer/SmartCAT needed

Adds a third CW engine alongside WinKeyer and Icom CI-V: FlexRadio's CWX keyer
over the existing SmartSDR CAT connection.

- cat.FlexController: SendCW (cwx send), StopCW (cwx clear); flex.go implements
  them, escaping the quoted text form. Speed reuses SetCWSpeed (cw wpm).
- App bindings FlexSendCW/FlexStopCW/FlexSetKeySpeed via FlexDo.
- Frontend: cwSource gains 'flex'; macros/auto-call/<LOGQSO>/send-on-type route to
  Flex when the CAT backend is a Flex (estimate-based timing like Icom, no busy
  echo). Keyer panel shows 'Flex CWX' + ready/offline; Settings adds the engine
  option with a CAT-backend guard.

Core send/stop/speed first; type-ahead buffer + mid-send backspace (cwx delete)
to follow.
This commit is contained in:
2026-07-20 15:13:24 +02:00
parent cc6411a618
commit 9729ef62ba
9 changed files with 127 additions and 19 deletions
+5
View File
@@ -394,6 +394,11 @@ type FlexController interface {
SetCWSpeed(int) error
SetCWPitch(int) error
SetCWBreakInDelay(int) error
// CWX keyer — buffered CW keying via SmartSDR's CWX subsystem, so a Flex needs
// no WinKeyer / SmartCAT. SendCW queues text (the radio buffers and keys it);
// StopCW clears the buffer, aborting the send.
SendCW(string) error
StopCW() error
SetCWSidetone(bool) error
SetSidetoneLevel(int) error
SetCWFilter(int) error
+20
View File
@@ -1581,6 +1581,26 @@ func (f *Flex) SetCWBreakInDelay(ms int) error {
return nil
}
// SendCW queues text on the radio's CWX keyer. SmartSDR buffers and keys it, so
// no WinKeyer / SmartCAT is needed — and because the radio owns the buffer,
// characters fed while it's already sending append and key in order (the basis
// for type-ahead). Quotes/backslashes are escaped for the "cwx send \"…\"" form.
func (f *Flex) SendCW(text string) error {
text = strings.TrimRight(text, "\r\n")
if strings.TrimSpace(text) == "" {
return nil
}
esc := strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace(text)
f.send(`cwx send "` + esc + `"`)
return nil
}
// StopCW clears the CWX buffer, aborting whatever is currently being keyed.
func (f *Flex) StopCW() error {
f.send("cwx clear")
return nil
}
func (f *Flex) SetCWSidetone(on bool) error {
f.mu.Lock()
f.tx.cwSidetone = on