diff --git a/app.go b/app.go index 395fcfd..9e6fde0 100644 --- a/app.go +++ b/app.go @@ -12454,6 +12454,7 @@ func (a *App) ultrabeamFollowLoop(c motorAntenna, stepKHz int, stop <-chan struc ticker := time.NewTicker(1500 * time.Millisecond) defer ticker.Stop() lastRigKHz := 0 // only log when the followed rig frequency actually changes + lastCmdKHz := 0 // rig freq we last issued a move for — the deadband reference for { select { case <-stop: @@ -12487,12 +12488,20 @@ func (a *App) ultrabeamFollowLoop(c motorAntenna, stepKHz int, stop <-chan struc continue } } - // Deadband reference = the antenna's reported freq, or (when it hasn't - // reported one yet) the last freq we commanded — so a 0/blank status - // doesn't bypass the deadband and re-tune on every small QSY. - ref := st.Frequency + // Deadband reference = the rig freq we LAST commanded a move for, not the + // antenna's reported freq. A SteppIR reports a flaky/stale status + // frequency (it flips between the commanded freq and its home/6 m value), + // which would trip the deadband and re-issue a SET on almost every poll — + // and each SET refreshes the "recently commanded" window that inhibits TX, + // leaving the operator permanently unable to transmit. Following the rig + // (only re-tune when the RIG actually QSYs beyond the step) is immune to + // that. Falls back to the antenna's own freq only until we've commanded once. + ref := lastCmdKHz if ref <= 0 { - ref = c.LastSetKHz() + ref = st.Frequency + if ref <= 0 { + ref = c.LastSetKHz() + } } diff := rigKHz - ref if diff < 0 { @@ -12505,6 +12514,7 @@ func (a *App) ultrabeamFollowLoop(c motorAntenna, stepKHz int, stop <-chan struc if err := c.SetFrequency(rigKHz, st.Direction); err != nil { applog.Printf("ultrabeam: follow re-tune to %d kHz failed: %v", rigKHz, err) } else { + lastCmdKHz = rigKHz applog.Printf("ultrabeam: followed rig → %d kHz (dir %d, was ref %d kHz, step %d)", rigKHz, st.Direction, ref, stepKHz) } } diff --git a/changelog.json b/changelog.json index 0e08963..6a1c7f0 100644 --- a/changelog.json +++ b/changelog.json @@ -3,6 +3,7 @@ "version": "0.20.12", "date": "2026-07-23", "en": [ + "Fixed a motorized-antenna bug that could leave a FlexRadio permanently unable to transmit (‘Interlock is preventing transmission’). With a SteppIR whose status frequency reads intermittently (it flipped between the commanded frequency and its home value), the ‘follow the rig’ loop re-sent a tune command on almost every poll, and each command re-armed the ‘block TX while the antenna moves’ window — so the interlock never released. The follow loop now keys its deadband off the rig’s frequency (only re-tuning when the RADIO actually QSYs), immune to a flaky antenna status. Also dropped an ‘interlock set reason=’ command that SmartSDR rejects (it’s read-only) and only produced a harmless error line — the transmit-inhibit itself is unchanged and still holds TX safely while the elements move.", "New built-in award: The Helvetia 26 Award (H26) — the 26 cantons of Switzerland (USKA). Ships with the full canton reference list; matches the canton from the QSO's address or QTH, on HF, for Swiss (HB) contacts. Enable it in Awards and rescan to see your standings.", "FlexRadio panel: the RECEIVE card is shorter again. All the noise controls added recently made it tower, so only the everyday ones — NB, NR, ANF — now stay visible; WNB and the SmartSDR v4 DSP block (NRL/NRS/NRF/ANFL and the AI/FFT RNN & ANFT) tuck behind a 'DSP' button you expand when you need them. The button carries a dot and highlights when one of the hidden controls is switched on, so nothing active is ever out of sight, and its open/closed state is remembered.", "ADIF export can now pick exactly which fields to write. The global 'Export to ADIF' dialog gains a third choice, 'Choose fields…', alongside 'Standard ADIF fields' and 'All OpsLog fields'; and right-clicking selected QSOs adds 'Export selected — choose fields…'. The picker separates the official ADIF 3.1.7 fields (grouped by category) from OpsLog / non-standard tags actually present in your log, with All/None per group and a one-click reset to defaults. Your selection is remembered for next time.", @@ -15,6 +16,7 @@ "Fixed the colour theme sometimes resetting to light after an update/relaunch: an update can clear the WebView's localStorage, and the fallback that restores the theme from the local settings database gave up after ~2.4s — occasionally too soon during the brief startup window before that store is ready. It now keeps retrying until the store actually answers, so a dark theme is reliably restored." ], "fr": [ + "Correction d'un bug d'antenne motorisée qui pouvait laisser un FlexRadio définitivement incapable d'émettre (« Interlock is preventing transmission »). Avec une SteppIR dont la fréquence de statut se lit par intermittence (elle alternait entre la fréquence commandée et sa valeur de repos), la boucle de suivi renvoyait un ordre d'accord à presque chaque cycle, et chaque ordre réarmait la fenêtre « bloquer l'émission pendant que l'antenne bouge » — l'interlock ne se relâchait donc jamais. La boucle de suivi se base maintenant sur la fréquence de la RADIO (elle ne réaccorde que quand le poste change réellement de fréquence), insensible à un statut d'antenne erratique. Retrait aussi d'une commande « interlock set reason= » que SmartSDR refuse (champ en lecture seule) et qui ne produisait qu'une ligne d'erreur sans effet — l'inhibition d'émission elle-même est inchangée et protège toujours pendant le mouvement des éléments.", "Nouveau diplôme intégré : The Helvetia 26 Award (H26) — les 26 cantons de Suisse (USKA). Livré avec la liste complète des cantons ; il reconnaît le canton depuis l'adresse ou le QTH du QSO, en HF, pour les contacts suisses (HB). Active-le dans les Diplômes et relance un scan pour voir ton avancement.", "Panneau FlexRadio : la carte RÉCEPTION est de nouveau plus compacte. Tous les contrôles de bruit ajoutés récemment la faisaient s'allonger, donc seuls ceux du quotidien — NB, NR, ANF — restent visibles ; le WNB et le bloc DSP SmartSDR v4 (NRL/NRS/NRF/ANFL ainsi que RNN & ANFT IA/FFT) se replient derrière un bouton « DSP » que tu déplies au besoin. Le bouton porte un point et s'illumine quand l'un des contrôles masqués est activé, pour ne jamais perdre de vue quelque chose d'actif, et son état ouvert/fermé est mémorisé.", "L'export ADIF permet maintenant de choisir précisément les champs à écrire. La fenêtre globale « Exporter en ADIF » gagne un troisième choix, « Choisir les champs… », à côté de « Champs ADIF standard » et « Tous les champs OpsLog » ; et le clic droit sur des QSO sélectionnés ajoute « Exporter la sélection — choisir les champs… ». Le sélecteur sépare les champs ADIF 3.1.7 officiels (regroupés par catégorie) des balises OpsLog / non standard réellement présentes dans ton log, avec Tout/Aucun par groupe et un retour aux valeurs par défaut en un clic. Ta sélection est mémorisée pour la prochaine fois.", diff --git a/internal/cat/flex.go b/internal/cat/flex.go index 8729c4b..a821362 100644 --- a/internal/cat/flex.go +++ b/internal/cat/flex.go @@ -1889,15 +1889,11 @@ func (f *Flex) SetTune(on bool) error { // SmartSDR refuses to transmit while inhibit is set, so it holds even against a // footswitch or an external keyer, which a software PTT block could not. // -// It also labels the interlock reason "OpsLog" so SmartSDR shows WHY TX is -// blocked. The reason is best-effort (a separate interlock object); the inhibit -// is what actually blocks TX, so a rig that ignores the reason still stays safe. +// `transmit set inhibit=` is the whole mechanism. We used to also send +// `interlock set reason=OpsLog` to label WHY TX was blocked, but `reason` is a +// read-only status field on the interlock object — writing it is rejected +// (cmd error 5000002D on SmartSDR v1.4/v3) and does nothing, so it's dropped. func (f *Flex) SetTXInhibit(on bool) error { - if on { - f.send("interlock set reason=OpsLog") - } else { - f.send("interlock set reason=") - } return f.txSet("transmit set inhibit="+boolFlex(on), "inhibit", func(t *flexTX) { t.inhibit = on }) }