feat(tci): key CW through the radio's own macro keyer

The sixth CW engine, and the one the TCI backend was left without. A SunSDR
keys from the WebSocket that already carries the CAT: no WinKeyer, no second
serial port, nothing to wire.

    CW_MACROS:0,<text>;     send
    CW_MACROS_SPEED:<wpm>;  speed
    CW_MACROS_STOP;         abort

Confirmed against the TCI command table in ars-ka0s/eesdr-tci — the source that
settled SPOT. CW_MACROS rather than TCI 2.0's CW_MSG: it exists from 1.6, and
OpsLog resolves its own variables, so the before/after callsign fields have
nothing to carry.

Commas and semicolons are stripped before sending. They are the protocol's own
separators: a comma would become another argument, a semicolon would end the
command with the message half keyed and the rest parsed as a command.

Only the MACRO speed is set, not the paddle keyer's — an operator who set their
paddle to 28 wpm did not ask for it to change because a macro went out at 25.
And there is no backspace: TCI can stop a message but cannot un-type one, so the
type-ahead correction the Flex engine offers is absent rather than faked.
This commit is contained in:
2026-08-28 13:01:55 +02:00
parent 8bc4ed68d4
commit 766b0f95a4
11 changed files with 200 additions and 10 deletions
+23
View File
@@ -96,3 +96,26 @@ func (m *Manager) TCIPanelDo(fn func(TCIPanelController) error) error {
return fn(tc)
})
}
// TCICWController is the radio's CW keyer, as the CW engine uses it.
//
// Three methods, and no backspace: TCI can stop a message but cannot un-type one
// (see tci_cw.go). Kept as its own interface rather than folded into the console
// one so a CW engine does not have to depend on forty panel setters to key a
// message.
type TCICWController interface {
SendCW(text string) error
StopCW() error
SetCWSpeed(wpm int) error
}
// TCICWDo dispatches one keyer command onto the CAT goroutine.
func (m *Manager) TCICWDo(fn func(TCICWController) error) error {
return m.exec(func(b Backend) error {
tc, ok := b.(TCICWController)
if !ok {
return fmt.Errorf("the active CAT backend is not a TCI radio")
}
return fn(tc)
})
}