feat(cat): distinct Elecraft K3/K4 backend in the rig list

The Elecraft handling was buried in the Kenwood "data mode" option, so users
couldn't find it. Add "Elecraft K3/K4" as its own entry in the CAT backend
selector. It reuses the Kenwood-dialect transport and the Kenwood USB/network
settings (the K3 emulates the Kenwood command set — a separate transport would be
a near-total duplicate), with an `elecraft` flag on the client that forces
digital modes to DATA A (MD6+DT0) — no data-mode dropdown needed. RigState still
reports Backend "kenwood" so the CW-over-CAT keyer capability keeps working.
This commit is contained in:
2026-08-08 14:15:55 +02:00
parent 626edca8ba
commit 9c757e5175
5 changed files with 56 additions and 15 deletions
+15
View File
@@ -13140,6 +13140,21 @@ func (a *App) reloadCAT() {
kw.SetDataMode(s.KenwoodDataMode)
a.cat.Start(kw)
}
case "elecraft":
// Elecraft K3/K4: the Kenwood-dialect client with the Elecraft specifics on
// (digital modes → DATA A via MD6+DT0). Reuses the Kenwood port/baud/host
// settings — the K3 emulates the Kenwood command set, so a separate transport
// would be a near-total duplicate.
if h := strings.TrimSpace(s.KenwoodHost); h != "" {
kw := cat.NewKenwoodTCP(h, s.DigitalDefault)
kw.SetElecraft(true)
a.cat.Start(kw)
} else {
kw := cat.NewKenwood(s.KenwoodPort, s.KenwoodBaud, s.DigitalDefault)
kw.SetLowerLines(s.KenwoodLowLines)
kw.SetElecraft(true)
a.cat.Start(kw)
}
case "icom":
// Native Icom CI-V over the radio's USB serial port (local control).
// Same civ protocol the network backend reuses for remote.
+2 -2
View File
@@ -10,7 +10,7 @@
"UDP outbound: a QSO logged from WSJT-X / JTDX / MSHV (via the inbound UDP listener) is now forwarded to the outbound ADIF integrations too — previously only QSOs typed directly into OpsLog were re-emitted.",
"Backup: with \"back up on every exit\", a new \"Keep every backup\" sub-option writes a distinct time-stamped file per exit (opslog-YYYY-MM-DD-HHMMSS.db) instead of overwriting the day's file. Rotation still keeps the newest N — raise it for more history.",
"Awards editor: the PRIMARY QSOFIELDS search now has a Prefix field too (prepended to each found reference, e.g. captured 01 → D01) — previously only the OR fallback searches could add a prefix, so a DOK-style award couldn't match on its main rule.",
"CAT (Elecraft K3/K4): the DATA data-mode now sends MD6 + DT0 (DATA A), the sub-mode FT8 audio actually needs — MD6 alone could leave the rig in an FSK/PSK sub-mode where it keyed but sent no audio. Pick \"DATA A — MD6+DT0 (Elecraft K3/K4)\" under CAT data modes."
"CAT: new Elecraft K3/K4 backend in the rig list. Select it (it reuses the Kenwood USB/network settings) and digital modes automatically use DATA A (MD6+DT0) — the sub-mode FT8 audio needs, so the K3 no longer keys the transmitter without modulating."
],
"fr": [
"Sauvegarde : nouvelle option \"Sauvegarder à chaque fermeture\" (Réglages → Sauvegarde). Activée, OpsLog sauvegarde la base à chaque fois que tu quittes au lieu d'une seule fois par jour — les QSO d'une seconde session sont ainsi toujours pris.",
@@ -20,7 +20,7 @@
"UDP sortant : un QSO enregistré depuis WSJT-X / JTDX / MSHV (via l'écouteur UDP entrant) est désormais aussi transmis aux intégrations ADIF sortantes — avant, seuls les QSO saisis directement dans OpsLog étaient réémis.",
"Sauvegarde : avec \"sauvegarder à chaque fermeture\", une nouvelle sous-option \"Garder chaque sauvegarde\" écrit un fichier horodaté distinct par fermeture (opslog-AAAA-MM-JJ-HHMMSS.db) au lieu d'écraser le fichier du jour. La rotation garde les N plus récents — augmente-la pour plus d'historique.",
"Éditeur de diplômes : la recherche PRINCIPALE QSOFIELDS a aussi un champ Préfixe (ajouté devant chaque référence trouvée, ex. 01 capturé → D01) — avant, seules les recherches OR de repli pouvaient ajouter un préfixe, empêchant un diplôme type DOK de matcher sur sa règle principale.",
"CAT (Elecraft K3/K4) : le mode data DATA envoie désormais MD6 + DT0 (DATA A), le sous-mode dont l'audio FT8 a réellement besoin — MD6 seul pouvait laisser le rig dans un sous-mode FSK/PSK où il passait en émission sans audio. Choisis \"DATA A — MD6+DT0 (Elecraft K3/K4)\" dans les modes data CAT."
"CAT : nouveau backend Elecraft K3/K4 dans la liste des rigs. Sélectionne-le (il réutilise les réglages USB/réseau Kenwood) et les modes numériques passent automatiquement en DATA A (MD6+DT0) — le sous-mode dont l'audio FT8 a besoin, donc le K3 ne passe plus en émission sans moduler."
]
},
{
+8 -2
View File
@@ -2527,6 +2527,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
<SelectItem value="flex">{t('cat.optFlex')}</SelectItem>
<SelectItem value="yaesu">{t('cat.optYaesu')}</SelectItem>
<SelectItem value="kenwood">{t('cat.optKenwood')}</SelectItem>
<SelectItem value="elecraft">{t('cat.optElecraft')}</SelectItem>
<SelectItem value="xiegu">{t('cat.optXiegu')}</SelectItem>
<SelectItem value="icom">{t('cat.optIcom')}</SelectItem>
<SelectItem value="icom-net">{t('cat.optIcomNet')}</SelectItem>
@@ -2688,7 +2689,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
{t('cat.civTrace')}
</label> </div>
)}
{catCfg.backend === 'kenwood' && (
{(catCfg.backend === 'kenwood' || catCfg.backend === 'elecraft') && (
<>
<div className="space-y-1">
<Label>{t('cat.kenwoodPort')}</Label>
@@ -2725,6 +2726,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</label>
<span className="text-xs text-muted-foreground">{t('cat.lowerLinesHint')}</span>
</div>
{catCfg.backend === 'kenwood' ? (
<div className="space-y-1">
<Label>{t('cat.kwDataMode')}</Label>
<Select value={(catCfg as any).kenwood_data_mode || 'usb'} onValueChange={(v) => setCatCfg((s) => ({ ...s, kenwood_data_mode: v } as any))}>
@@ -2734,7 +2736,11 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
<SelectItem value="data">{t('cat.kwDataMd6')}</SelectItem>
<SelectItem value="keep">{t('cat.kwDataKeep')}</SelectItem>
</SelectContent>
</Select> </div>
</Select>
</div>
) : (
<span className="text-xs text-muted-foreground">{t('cat.elecraftHint')}</span>
)}
</>
)}
{catCfg.backend === 'icom' && (
File diff suppressed because one or more lines are too long
+20
View File
@@ -62,6 +62,10 @@ type Kenwood struct {
// leave the rig's current mode untouched (safest for a TS-590SG/TS-990S whose
// data mode is a USB modifier the operator sets on the rig). See SetMode.
dataMode string
// elecraft marks a K3/K4 (the "Elecraft" backend). It reuses this Kenwood-dialect
// client but, being an Elecraft, always drives digital modes to DATA A (MD6+DT0)
// — the sub-mode FT8 audio needs — instead of going through the dataMode option.
elecraft bool
mu sync.Mutex
port serial.Port
@@ -391,6 +395,14 @@ func (k *Kenwood) SetMode(mode string) error {
// mode digit fits every rig, so the operator picks: keep the rig's mode, use
// MD6 (K3/K4 DATA), or fall through to the default USB from kenwoodModeDigit.
if isKenwoodDataMode(mode) {
// An Elecraft always uses DATA A for a soundcard digital mode — that's what
// the "Elecraft" backend means, so the dataMode option doesn't apply.
if k.elecraft {
if err := k.write("MD6;"); err != nil {
return err
}
return k.write("DT0;")
}
switch k.dataMode {
case "keep":
return nil // leave whatever data mode the operator set on the rig
@@ -424,6 +436,14 @@ func (k *Kenwood) SetDataMode(m string) {
k.mu.Unlock()
}
// SetElecraft marks this client as driving a K3/K4 (the "Elecraft" backend), so
// digital modes always land in DATA A (MD6+DT0). Set before Start.
func (k *Kenwood) SetElecraft(v bool) {
k.mu.Lock()
k.elecraft = v
k.mu.Unlock()
}
// isKenwoodDataMode reports whether a mode name is a soundcard/data mode (FT8,
// PSK, JT…) rather than a voice/CW/RTTY mode the rig sets natively.
func isKenwoodDataMode(mode string) bool {