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
+17 -1
View File
@@ -4867,7 +4867,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
<SelectItem value="yaesu">{t('wk.engYaesu')}</SelectItem>
<SelectItem value="kenwood">{t('wk.engKenwood')}</SelectItem>
<SelectItem value="flex">{t('wk.engFlex')}</SelectItem>
<SelectItem value="tci" disabled>{t('wk.engTci')}</SelectItem>
<SelectItem value="tci">{t('wk.engTci')}</SelectItem>
</SelectContent>
</Select>
</div>
@@ -4928,6 +4928,22 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
</div>
</div>
</>
) : wk.engine === 'tci' ? (
<>
{(!catCfg.enabled || catCfg.backend !== 'tci') && (
<p className="text-xs font-medium text-warning -mt-1 flex items-start gap-1.5">
<span aria-hidden></span>
<span>{t('wk.catWarnTci', { backend: catCfg.enabled ? (catCfg.backend || 'none') : 'disabled' })}</span>
</p>
)}
<p className="text-xs text-muted-foreground -mt-1">{t('wk.tciHint')}</p>
<div className="grid grid-cols-4 gap-3">
<div className="space-y-1">
<Label>{t('wk.speed')}</Label>
<Input type="number" min={5} max={60} value={wk.wpm} onChange={(e) => setWkField({ wpm: num(e.target.value, 25) })} className="font-mono" />
</div>
</div>
</>
) : wk.engine === 'flex' ? (
<>
{(!catCfg.enabled || catCfg.backend !== 'flex') && (