diff --git a/changelog.json b/changelog.json index 78eaf09..8fd1c53 100644 --- a/changelog.json +++ b/changelog.json @@ -12,7 +12,8 @@ "DCU-1 rotor support: OpsLog can now steer controllers that speak the Hy-Gain DCU-1 protocol (RotorCard DXA for Yaesu DXA rotors, Idiom Press Rotor-EZ, Green Heron) over their COM port or a serial-over-IP bridge.", "Motorized antenna: Settings → Antenna now has a Covered bands selector (40 m–6 m) instead of a min/max range, for both the Ultrabeam and the SteppIR. Tick only the bands your antenna does — untick one you can't (e.g. 30 m without its extension) while keeping the rest — and OpsLog leaves the antenna put on every other band. This overrides an unreliable controller, so 80 m no longer clamps the elements down to 30 m. Existing range settings migrate automatically.", "Motorized antenna: on a band you did not tick in Covered bands, OpsLog no longer inhibits FlexRadio transmit for \"antenna moving\". Un-ticking a band now means hands off completely — no tuning and no TX block — so working it on another antenna is never gagged.", - "Awards: a new Settings → Awards picker (under User configuration) lets you choose which awards to follow — a two-column list, all awards on the left, the ones you track on the right. The Awards tab then shows only those you follow (per profile; leave the right side empty to show them all)." + "Awards: a new Settings → Awards picker (under User configuration) lets you choose which awards to follow — a two-column list, all awards on the left, the ones you track on the right. The Awards tab then shows only those you follow (per profile; leave the right side empty to show them all).", + "Kenwood/Elecraft CAT: the link no longer drops while the rig is transmitting. A K3 (and other Kenwood-dialect rigs) answers \"?;\" to a status poll during transmit; OpsLog used to read that as \"rig doesn't support IF\" and disconnect — which tore down the shared CAT that WSJT-X / JTDX key through, so the radio wouldn't transmit properly. OpsLog now pauses status polling while PTT is held and keeps the link up." ], "fr": [ "Édition de QSO : ajout du bouton QRZ ↗ à côté de l'indicatif, comme dans le formulaire de saisie — un clic ouvre le profil qrz.com de la station.", @@ -24,7 +25,8 @@ "Prise en charge des rotors DCU-1 : OpsLog pilote désormais les contrôleurs parlant le protocole Hy-Gain DCU-1 (RotorCard DXA pour rotors Yaesu DXA, Idiom Press Rotor-EZ, Green Heron) via leur port COM ou un pont série-sur-IP.", "Antenne motorisée : Réglages → Antenne propose désormais un sélecteur Bandes couvertes (40 m–6 m) au lieu d'une plage min/max, pour l'Ultrabeam comme pour la SteppIR. Coche seulement les bandes que fait ton antenne — décoche celle que tu ne fais pas (p. ex. le 30 m sans son extension) en gardant les autres — et OpsLog laisse l'antenne en place sur toutes les autres. Ceci prime sur un contrôleur peu fiable : le 80 m ne replie plus les éléments sur le 30 m. Les anciens réglages de plage sont migrés automatiquement.", "Antenne motorisée : sur une bande non cochée dans Bandes couvertes, OpsLog n'inhibe plus l'émission du FlexRadio pour « antenne en mouvement ». Décocher une bande signifie désormais ne plus y toucher du tout — ni accord ni blocage TX — pour ne jamais couper le trafic sur une autre antenne.", - "Diplômes : un nouveau sélecteur Réglages → Diplômes (dans Configuration utilisateur) permet de choisir les diplômes à suivre — une liste à deux colonnes, tous les diplômes à gauche, ceux que tu suis à droite. L'onglet Awards n'affiche alors que ceux-là (par profil ; laisse la colonne de droite vide pour tous les afficher)." + "Diplômes : un nouveau sélecteur Réglages → Diplômes (dans Configuration utilisateur) permet de choisir les diplômes à suivre — une liste à deux colonnes, tous les diplômes à gauche, ceux que tu suis à droite. L'onglet Awards n'affiche alors que ceux-là (par profil ; laisse la colonne de droite vide pour tous les afficher).", + "CAT Kenwood/Elecraft : le lien ne tombe plus pendant que le rig émet. Un K3 (et d'autres rigs en dialecte Kenwood) répond \"?;\" à une interrogation d'état pendant l'émission ; OpsLog le prenait pour \"le rig ne supporte pas IF\" et se déconnectait — ce qui cassait le CAT partagé que WSJT-X / JTDX utilisent pour passer en émission, d'où l'absence d'émission. OpsLog suspend désormais l'interrogation d'état tant que le PTT est actif et garde le lien." ] }, { diff --git a/internal/cat/kenwood.go b/internal/cat/kenwood.go index 4c4fcea..86f9cdc 100644 --- a/internal/cat/kenwood.go +++ b/internal/cat/kenwood.go @@ -115,6 +115,17 @@ type Kenwood struct { // nothing. Off by default: that is how this backend behaved for its whole // life before the question came up. lowerLines bool + + // tx tracks whether we currently hold PTT (SetPTT true). A Kenwood/Elecraft rig + // answers "?;" to a status poll (IF;) WHILE TRANSMITTING; OpsLog used to read + // that as "the rig doesn't support IF;", latch it off and drop the whole CAT + // link — which tore down the shared-CAT PTT that WSJT-X / JTDX key through, so a + // K3 keyed but the logger's transmission fell apart. While PTT is held we skip + // the wire poll and hand back the last good state (lastState) instead. txAt caps + // the skip so a missed SetPTT(false) can't freeze the state for ever. + tx bool + txAt time.Time + lastState RigState } // SetLowerLines chooses whether DTR and RTS are deasserted on connect. Set @@ -255,6 +266,15 @@ func (k *Kenwood) ReadState() (RigState, error) { if k.port == nil { return RigState{}, fmt.Errorf("kenwood: not connected") } + // While transmitting, don't poll: the rig returns "?;" to IF; during TX, and + // treating that as a fault dropped the shared CAT link the digital-mode + // software keys through. Hand back the last known state. The 30 s cap resyncs + // if a SetPTT(false) was somehow missed, so a stuck TX can't freeze state. + if k.tx && !k.txAt.IsZero() && time.Since(k.txAt) < 30*time.Second { + s := k.lastState + s.Connected = true + return s, nil + } raw, err := k.ask("IF;") if err != nil { return RigState{}, err @@ -339,6 +359,7 @@ func (k *Kenwood) ReadState() (RigState, error) { if s.Split { k.curRXFreq = s.RxFreqHz } + k.lastState = s // cache for the transmit window, where we can't poll return s, nil } @@ -409,7 +430,9 @@ func (k *Kenwood) SetPTT(on bool) error { if k.port == nil { return fmt.Errorf("kenwood: not connected") } + k.tx = on if on { + k.txAt = time.Now() return k.write("TX;") } return k.write("RX;") @@ -467,10 +490,15 @@ func (k *Kenwood) ask(cmd string) (string, error) { k.rx = k.rx[i+1:] traceText("kenwood", "RX", frame) if frame == "?;" { - // The rig rejected the command. Remember it so the poll loop stops - // paying a 600 ms timeout for it on every cycle. - k.unsupported[want] = true - debugLog.Printf("kenwood: this rig does not support %q — not asking again", cmd) + // IF; and ID; are universal on Kenwood/Elecraft — a "?;" to them is a + // transient "busy" (typically mid-transmit, or a menu open on the rig), + // NOT "unsupported". Latching them off would blind the poll loop for + // good and read as "lost the rig". Only remember the OPTIONAL commands + // (FR/FT/…) so the poll loop stops paying a 600 ms timeout for those. + if want != "IF" && want != "ID" { + k.unsupported[want] = true + debugLog.Printf("kenwood: this rig does not support %q — not asking again", cmd) + } return "", fmt.Errorf("kenwood: %s rejected", want) } if strings.HasPrefix(frame, want) {