diff --git a/app.go b/app.go index f44da69..eecdfd9 100644 --- a/app.go +++ b/app.go @@ -121,6 +121,12 @@ const ( keyCATOmniRigNum = "cat.omnirig.rig" // 1 or 2 // Which VFO to believe when OmniRig names one. "" = trust the rig file. keyCATOmniRigVFO = "cat.omnirig.vfo" // "" | "A" | "B" + // OmniRig's model has two CW modes — PM_CW_U and PM_CW_L — and nothing says + // which one an .ini file calls plain CW. Icom files disagree with each other: + // on some, PM_CW_U is CI-V mode 0x03 (CW) and on others it is 0x07 (CW-R). + // An operator clicking a CW spot on an IC-7610 landed in CW-R and had to edit + // the rig file to get out of it. This says which bit means CW on THIS rig. + keyCATOmniRigCWLower = "cat.omnirig.cw_lower" // "1" → send PM_CW_L for CW // Put the RADIO in USB for a digital mode instead of asking for the mode by // name. Every backend, because the reason is the radio, not the link. keyCATDigiUSB = "cat.digi_usb" @@ -483,6 +489,9 @@ type CATSettings struct { // the .ini: an IC-7610 file was seen declaring VFO B permanently while the // operator worked on the main VFO, so OpsLog wrote to A and read B. OmniRigVFO string `json:"omnirig_vfo"` // "" | "A" | "B" + // OmniRigCWLower sends PM_CW_L instead of PM_CW_U when the mode is CW — for + // the rig files whose PM_CW_U is the REVERSE sideband. See the key comment. + OmniRigCWLower bool `json:"omnirig_cw_lower"` // DigiAsUSB puts the radio in USB when a digital mode is selected, rather // than naming the mode. What the QSO is LOGGED as never changes. DigiAsUSB bool `json:"digi_as_usb"` @@ -8366,7 +8375,7 @@ func (a *App) GetCATSettings() (CATSettings, error) { if a.settings == nil { return CATSettings{Backend: "omnirig", OmniRigNum: 1, PollMs: 250}, fmt.Errorf("db not initialized") } - m, err := a.settings.GetMany(a.ctx, keyCATEnabled, keyCATBackend, keyCATOmniRigNum, keyCATOmniRigVFO, keyCATFlexHost, keyCATFlexPort, keyCATFlexSpots, keyCATFlexDVKDax, keyCATFlexDecodeSpots, keyCATFlexDecodeSecs, keyCATXieguPort, keyCATXieguBaud, keyCATXieguAddr, keyCATXieguPTTLine, keyCATYaesuPort, keyCATYaesuBaud, keyCATYaesuRTTYUSB, keyCATKenwoodPort, keyCATKenwoodBaud, keyCATKenwoodHost, keyCATKenwoodLink, keyCATYaesuLowLines, keyCATKenwoodLowLines, keyCATKenwoodDataMode, keyCATIcomPort, keyCATIcomBaud, keyCATIcomAddr, keyCATIcomNetHost, keyCATIcomNetUser, keyCATIcomNetPass, keyCATIcomNetAudio, keyCATTCIHost, keyCATTCIPort, keyCATTCISpots, keyCATPttHotkeyEnabled, keyCATPttHotkey, keyCATPttHotkeyToggle, keyCATPollMs, keyCATDelayMs, keyCATOffsetOn, keyCATOffsetHz, keyCATDigitalDefault, keyCATShareEnabled, keyCATSharePort, keyCATShareProto, keyCATShareTCIPort, keyCATDigiUSB) + m, err := a.settings.GetMany(a.ctx, keyCATEnabled, keyCATBackend, keyCATOmniRigNum, keyCATOmniRigVFO, keyCATOmniRigCWLower, keyCATFlexHost, keyCATFlexPort, keyCATFlexSpots, keyCATFlexDVKDax, keyCATFlexDecodeSpots, keyCATFlexDecodeSecs, keyCATXieguPort, keyCATXieguBaud, keyCATXieguAddr, keyCATXieguPTTLine, keyCATYaesuPort, keyCATYaesuBaud, keyCATYaesuRTTYUSB, keyCATKenwoodPort, keyCATKenwoodBaud, keyCATKenwoodHost, keyCATKenwoodLink, keyCATYaesuLowLines, keyCATKenwoodLowLines, keyCATKenwoodDataMode, keyCATIcomPort, keyCATIcomBaud, keyCATIcomAddr, keyCATIcomNetHost, keyCATIcomNetUser, keyCATIcomNetPass, keyCATIcomNetAudio, keyCATTCIHost, keyCATTCIPort, keyCATTCISpots, keyCATPttHotkeyEnabled, keyCATPttHotkey, keyCATPttHotkeyToggle, keyCATPollMs, keyCATDelayMs, keyCATOffsetOn, keyCATOffsetHz, keyCATDigitalDefault, keyCATShareEnabled, keyCATSharePort, keyCATShareProto, keyCATShareTCIPort, keyCATDigiUSB) if err != nil { return CATSettings{}, err } @@ -8472,6 +8481,7 @@ func (a *App) GetCATSettings() (CATSettings, error) { if out.DigitalDefault == "" { out.DigitalDefault = "FT8" } + out.OmniRigCWLower = m[keyCATOmniRigCWLower] == "1" if v := strings.ToUpper(strings.TrimSpace(m[keyCATOmniRigVFO])); v == "A" || v == "B" { out.OmniRigVFO = v } @@ -8588,6 +8598,7 @@ func (a *App) SaveCATSettings(s CATSettings) error { keyCATBackend: s.Backend, keyCATOmniRigNum: strconv.Itoa(s.OmniRigNum), keyCATOmniRigVFO: strings.ToUpper(strings.TrimSpace(s.OmniRigVFO)), + keyCATOmniRigCWLower: boolStr(s.OmniRigCWLower), keyCATDigiUSB: boolStr(s.DigiAsUSB), keyCATFlexHost: strings.TrimSpace(s.FlexHost), keyCATFlexPort: strconv.Itoa(s.FlexPort), @@ -16338,6 +16349,9 @@ func (a *App) reloadCAT() { if s.Enabled && s.Backend == "yaesu" && a.cat != nil { _ = a.cat.YaesuDo(func(y cat.YaesuController) error { y.SetRTTYUpper(s.YaesuRTTYUSB); return nil }) } + if s.Enabled && s.Backend == "omnirig" && a.cat != nil { + _ = a.cat.OmniRigDo(func(o cat.OmniRigController) error { o.SetCWLower(s.OmniRigCWLower); return nil }) + } // Nothing about the link changed → leave it connected. See catLinkSig. if sig := catLinkSig(s); sig == a.catSig { applog.Printf("cat: settings saved, link unchanged — staying connected") @@ -16362,7 +16376,7 @@ func (a *App) reloadCAT() { // Spawning OmniRig.exe ourselves (even with /Embedding) on every // reloadCAT raised the existing instance's window to the front, // which is what Log4OM avoids by relying entirely on COM activation. - a.cat.Start(cat.NewOmniRig(s.OmniRigNum, s.OmniRigVFO)) + a.cat.Start(cat.NewOmniRig(s.OmniRigNum, s.OmniRigVFO, s.OmniRigCWLower)) case "flex": // Native FlexRadio (SmartSDR) TCP API — no OmniRig needed. fb := cat.NewFlex(s.FlexHost, s.FlexPort, s.FlexSpots) diff --git a/changelog.json b/changelog.json index ee0cd93..1336d7a 100644 --- a/changelog.json +++ b/changelog.json @@ -5,12 +5,14 @@ "en": [ "After an update, OpsLog starts again. The fix that stopped Defender calling the updater a trojan removed the helper that waited for the old process to die, and nothing took over the job: the new instance was patient with the single-instance lock for twenty seconds while the old one is allowed thirty to shut down — closing a remote logbook, a CAT session, sometimes a backup. Where that ran long the new process gave up in silence, leaving no window and a leftover OpsLog in the task manager. It now waits for the previous process itself, ending the instant it does; and if it really has not gone, it says so instead of claiming OpsLog is already running.", "A UDP row set to multicast on an address that is not one now listens anyway. 127.0.0.1 in the group box is the common mistake — it is the address every other field in every other program wants — but a multicast group runs 224.0.0.0 to 239.255.255.255, and joining anything else failed on every interface with a Windows error naming nothing the operator had typed. The row simply did not run. It now listens on unicast, which is what such an address means, and says so in the log.", - "The confirmation defaults added for the newest services were blank. HAMLOG.online arrived after most profiles were set up, so it had no default at all — and blank is not a status anybody chose. Every service now starts the same way: the sent side at R (waiting to go out), the received side at N. A blank left by a service that did not exist when you last saved is filled in; a status you chose yourself is untouched." + "The confirmation defaults added for the newest services were blank. HAMLOG.online arrived after most profiles were set up, so it had no default at all — and blank is not a status anybody chose. Every service now starts the same way: the sent side at R (waiting to go out), the received side at N. A blank left by a service that did not exist when you last saved is filled in; a status you chose yourself is untouched.", + "OmniRig: a setting for rig files whose CW is the reverse one. OmniRig has two CW modes and nothing says which one a rig file calls plain CW — some Icom files map PM_CW_U to CW, others to CW-R — so clicking a CW spot on an IC-7610 landed the radio in CW-R, and the only way out was to edit the rig file. Settings → CAT → OmniRig now has a tick box for it, applied at once without dropping the link. (If your VFOs read the wrong way round on the same rig, the VFO override beside it is the answer: rig files disagree there too.)" ], "fr": [ "Après une mise à jour, OpsLog redémarre. Le correctif qui a fait cesser la détection en cheval de Troie a supprimé l'assistant qui attendait la mort de l'ancien processus, et rien n'a repris ce travail : la nouvelle instance patientait vingt secondes sur le verrou d'instance unique alors que l'ancienne dispose de trente pour se fermer — elle referme un journal distant, une session CAT, parfois une sauvegarde. Quand cela durait, le nouveau processus abandonnait en silence : pas de fenêtre, et un OpsLog restant dans le gestionnaire des tâches. Il attend désormais l'ancien processus lui-même, et repart à l'instant où celui-ci s'arrête ; et s'il n'est vraiment pas parti, il le dit au lieu d'annoncer qu'OpsLog tourne déjà.", "Une ligne UDP réglée en multicast sur une adresse qui n'en est pas une écoute désormais quand même. 127.0.0.1 dans le champ groupe est l'erreur classique — c'est l'adresse que réclame tout autre champ de tout autre programme — mais un groupe multicast va de 224.0.0.0 à 239.255.255.255, et rejoindre autre chose échouait sur toutes les interfaces avec une erreur Windows ne nommant rien de ce que l'opérateur avait saisi. La ligne ne tournait tout simplement pas. Elle écoute maintenant en unicast, ce que veut dire une telle adresse, et le dit dans le journal.", - "Les statuts par défaut des services les plus récents étaient vides. HAMLOG.online est arrivé après la configuration de la plupart des profils : il n'avait donc aucun défaut — et vide n'est pas un statut que quelqu'un a choisi. Chaque service démarre désormais pareil : côté envoi R (en attente de départ), côté réception N. Un vide laissé par un service qui n'existait pas lors de votre dernier enregistrement est comblé ; un statut que vous avez choisi n'est pas touché." + "Les statuts par défaut des services les plus récents étaient vides. HAMLOG.online est arrivé après la configuration de la plupart des profils : il n'avait donc aucun défaut — et vide n'est pas un statut que quelqu'un a choisi. Chaque service démarre désormais pareil : côté envoi R (en attente de départ), côté réception N. Un vide laissé par un service qui n'existait pas lors de votre dernier enregistrement est comblé ; un statut que vous avez choisi n'est pas touché.", + "OmniRig : un réglage pour les fichiers de rig dont la CW est l'inverse. OmniRig a deux modes CW et rien ne dit lequel un fichier appelle CW tout court — certains fichiers Icom associent PM_CW_U à CW, d'autres à CW-R — si bien qu'un clic sur un spot CW mettait un IC-7610 en CW-R, sans autre issue que de modifier le fichier de rig. Réglages → CAT → OmniRig a désormais une case pour cela, appliquée aussitôt sans couper la liaison. (Si vos VFO sont inversés sur la même radio, le sélecteur de VFO juste à côté est la réponse : les fichiers de rig divergent là aussi.)" ] }, { diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index 3fc0576..46cf42b 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -1859,7 +1859,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged // IC-7610 the moment they chose Other. const [icomCustom, setIcomCustom] = useState(false); const [catCfg, setCatCfg] = useState({ - enabled: false, backend: 'omnirig', omnirig_rig: 1, omnirig_vfo: '', flex_host: '', flex_port: 4992, flex_spots: false, flex_decode_spots: false, flex_decode_secs: 120, flex_dvk_dax: false, + enabled: false, backend: 'omnirig', omnirig_rig: 1, omnirig_vfo: '', omnirig_cw_lower: false, flex_host: '', flex_port: 4992, flex_spots: false, flex_decode_spots: false, flex_decode_secs: 120, flex_dvk_dax: false, yaesu_port: '', yaesu_baud: 38400, yaesu_low_lines: false, yaesu_rtty_usb: false, kenwood_low_lines: false, kenwood_port: '', kenwood_baud: 9600, kenwood_host: '', kenwood_link: 'usb', kenwood_data_mode: 'usb', xiegu_port: '', xiegu_baud: 19200, xiegu_addr: 0x70, xiegu_ptt_line: '', icom_port: '', icom_baud: 115200, icom_addr: 0x98, icom_net_host: '', icom_net_user: '', icom_net_pass: '', icom_net_audio: false, tci_host: '', tci_port: 40001, tci_spots: false, poll_ms: 250, delay_ms: 0, offset_on: false, offset_hz: 0, @@ -3640,6 +3640,16 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged

{t('cat.omnirigVfoHint')}

)} + {catCfg.backend === 'omnirig' && ( +
+ +

{t('cat.omnirigCwLowerHint')}

+
+ )} {catCfg.backend === 'flex' && ( <>
diff --git a/frontend/src/lib/i18n.tsx b/frontend/src/lib/i18n.tsx index 54d3f0b..8507d25 100644 --- a/frontend/src/lib/i18n.tsx +++ b/frontend/src/lib/i18n.tsx @@ -421,7 +421,7 @@ const en: Dict = { 'cat.icomNetHint': "Connects to the rig's built-in LAN server directly — no RS-BA1 or Remote Utility needed (close them first). Use the Network User1 ID/Password set in the rig's Network menu. A rig in standby is powered on automatically.", 'cat.icomNetListen': 'Play it through the speakers', 'cat.icomNetListenHint': 'Untick to keep the stream silent — recordings and the voice keyer still work. Same switch as the speaker button on the Icom console.', 'cat.icomNetAudio': 'Stream RX audio over the network (experimental)', 'cat.icomNetAudioHint': 'Play the rig’s received audio through your Listening device (Settings → Audio) over the 50003 stream. Experimental — the audio framing is pending on-rig verification; leave off if control misbehaves.', - 'cat.omnirigRig': 'OmniRig rig slot', 'cat.omnirigVfo': 'VFO to read', 'cat.omnirigVfoAuto': 'As reported by the rig file', 'cat.omnirigVfoA': 'Always VFO A (main)', 'cat.omnirigVfoB': 'Always VFO B (sub)', 'cat.omnirigVfoHint': 'OmniRig reports the active VFO from the rig file, and some files get it wrong — the frequency then follows the other VFO and appears frozen. Force one here if that happens.', 'cat.flexIp': 'FlexRadio IP', 'cat.port': 'Port', 'cat.flexSpots': 'Show cluster spots on the panadapter', 'cat.flexSpotsHint': "(spots from OpsLog's DX cluster appear on the radio, auto-expire after 30 min)", 'cat.flexDecodeSpots': 'Show WSJT-X decodes on the panadapter', 'cat.flexDecodeSpotsHint': '(heard FT8/FT4 stations from your WSJT-X/JTDX UDP feed, one spot per call)', 'cat.flexDecodeSecs': 'Display for', 'cat.flexDecodeSecsHint': 'seconds before a station is removed', + 'cat.omnirigRig': 'OmniRig rig slot', 'cat.omnirigVfo': 'VFO to read', 'cat.omnirigVfoAuto': 'As reported by the rig file', 'cat.omnirigVfoA': 'Always VFO A (main)', 'cat.omnirigVfoB': 'Always VFO B (sub)', 'cat.omnirigCwLower': 'This rig file calls CW “PM_CW_L” — tick if clicking a CW spot lands the radio in CW-R', 'cat.omnirigCwLowerHint': 'OmniRig has two CW modes and nothing says which one a rig file calls plain CW: some Icom files map PM_CW_U to CW, others to CW-R. Tick this and OpsLog asks for the other one. Applied at once — the radio does not have to reconnect.', 'cat.omnirigVfoHint': 'OmniRig reports the active VFO from the rig file, and some files get it wrong — the frequency then follows the other VFO and appears frozen. Force one here if that happens.', 'cat.flexIp': 'FlexRadio IP', 'cat.port': 'Port', 'cat.flexSpots': 'Show cluster spots on the panadapter', 'cat.flexSpotsHint': "(spots from OpsLog's DX cluster appear on the radio, auto-expire after 30 min)", 'cat.flexDecodeSpots': 'Show WSJT-X decodes on the panadapter', 'cat.flexDecodeSpotsHint': '(heard FT8/FT4 stations from your WSJT-X/JTDX UDP feed, one spot per call)', 'cat.flexDecodeSecs': 'Display for', 'cat.flexDecodeSecsHint': 'seconds before a station is removed', 'cat.flexOptions': 'What OpsLog does with the radio', 'flexzoom.title': 'Zoom the panadapter on a spot click', 'flexzoom.enable': 'Resize the panadapter when a spot is clicked', 'flexzoom.cw': 'CW', 'flexzoom.ssb': 'Phone', 'flexzoom.digi': 'Digital', 'flexzoom.centre': 'Re-centre on the spot as well', 'flexzoom.centreHint': '(off keeps your window where you put it and only changes its width)', 'spotcol.title': 'Spot colours on the panadapter', 'spotcol.enable': 'Colour spots by status', 'spotcol.reset': 'restore defaults', 'spotcol.send': 'Send this status to the panadapter', 'spotcol.text': 'Text colour', 'spotcol.bg': 'Background colour', 'spotcol.s_new': 'Entity never worked', 'spotcol.s_new-band-mode': 'Entity new on this band AND mode', 'spotcol.s_new-band': 'Entity new on this band', 'spotcol.s_new-mode': 'Entity new in this mode', 'spotcol.s_new-slot': 'New slot', 'spotcol.s_new-pota': 'New POTA park', 'spotcol.s_new-county': 'New US county', 'spotcol.s_new-pfx': 'New WPX prefix', 'spotcol.s_my-call': 'My own callsign', 'spotcol.s_worked': 'Callsign already worked', 'spotcol.s_none': 'Nothing new', 'cat.flexDvkDax': 'Switch transmit audio to DAX for voice messages', 'cat.flexDvkDaxHint': '(pressed while a DVK message or a QSO recording goes out, and put back afterwards so your microphone works again)', 'cat.icomPort': 'Icom CI-V port', 'cat.selectCom': 'Select COM port', 'cat.noPorts': 'No ports found', 'cat.baud': 'Baud rate', 'cat.icomModel': 'Rig model', 'cat.icomModelOther': 'Other (custom address)', 'cat.civAddr': 'CI-V address (hex)', 'cat.civHint': 'Pick your model to set the CI-V address automatically (or choose "Other" and type it). Set "CI-V USB Echo Back" OFF and CI-V baud to match on the rig.', @@ -1030,7 +1030,7 @@ const fr: Dict = { 'cat.icomNetHint': "Se connecte directement au serveur LAN intégré du poste — sans RS-BA1 ni Remote Utility (ferme-les d'abord). Utilise l'ID/mot de passe Network User1 configurés dans le menu Network du poste. Un poste en veille est allumé automatiquement.", 'cat.icomNetListen': 'Écouter dans les enceintes', 'cat.icomNetListenHint': 'Décochez pour garder le flux silencieux — enregistrements et voice keyer fonctionnent toujours. Même interrupteur que le bouton haut-parleur de la console Icom.', 'cat.icomNetAudio': 'Diffuser l’audio RX par le réseau (expérimental)', 'cat.icomNetAudioHint': 'Écoute l’audio reçu du poste sur ton périphérique d’écoute (Réglages → Audio) via le flux 50003. Expérimental — le format audio reste à vérifier sur le poste ; laisse désactivé si le contrôle se comporte mal.', - 'cat.omnirigRig': 'Slot OmniRig', 'cat.omnirigVfo': 'VFO à lire', 'cat.omnirigVfoAuto': 'Selon le fichier radio', 'cat.omnirigVfoA': 'Toujours le VFO A (principal)', 'cat.omnirigVfoB': 'Toujours le VFO B (secondaire)', 'cat.omnirigVfoHint': "OmniRig indique le VFO actif d'après le fichier radio, et certains fichiers se trompent — la fréquence suit alors l'autre VFO et paraît figée. Forcez-en un ici le cas échéant.", 'cat.flexIp': 'IP FlexRadio', 'cat.port': 'Port', 'cat.flexSpots': 'Afficher les spots cluster sur le panadapter', 'cat.flexSpotsHint': "(les spots du cluster DX d'OpsLog apparaissent sur la radio, expirent après 30 min)", 'cat.flexDecodeSpots': 'Afficher les décodes WSJT-X sur le panadapter', 'cat.flexDecodeSpotsHint': '(stations FT8/FT4 entendues via ton flux UDP WSJT-X/JTDX, un spot par station)', 'cat.flexDecodeSecs': 'Affichage pendant', 'cat.flexDecodeSecsHint': 'secondes avant retrait d\'une station', + 'cat.omnirigRig': 'Slot OmniRig', 'cat.omnirigVfo': 'VFO à lire', 'cat.omnirigVfoAuto': 'Selon le fichier radio', 'cat.omnirigVfoA': 'Toujours le VFO A (principal)', 'cat.omnirigVfoB': 'Toujours le VFO B (secondaire)', 'cat.omnirigCwLower': 'Ce fichier de rig appelle la CW « PM_CW_L » — cocher si un clic sur un spot CW met la radio en CW-R', 'cat.omnirigCwLowerHint': 'OmniRig a deux modes CW et rien ne dit lequel un fichier de rig appelle CW tout court : certains fichiers Icom associent PM_CW_U à CW, d’autres à CW-R. Cochez et OpsLog demande l’autre. Appliqué aussitôt — la radio n’a pas besoin de se reconnecter.', 'cat.omnirigVfoHint': "OmniRig indique le VFO actif d'après le fichier radio, et certains fichiers se trompent — la fréquence suit alors l'autre VFO et paraît figée. Forcez-en un ici le cas échéant.", 'cat.flexIp': 'IP FlexRadio', 'cat.port': 'Port', 'cat.flexSpots': 'Afficher les spots cluster sur le panadapter', 'cat.flexSpotsHint': "(les spots du cluster DX d'OpsLog apparaissent sur la radio, expirent après 30 min)", 'cat.flexDecodeSpots': 'Afficher les décodes WSJT-X sur le panadapter', 'cat.flexDecodeSpotsHint': '(stations FT8/FT4 entendues via ton flux UDP WSJT-X/JTDX, un spot par station)', 'cat.flexDecodeSecs': 'Affichage pendant', 'cat.flexDecodeSecsHint': 'secondes avant retrait d\'une station', 'cat.flexOptions': "Ce qu’OpsLog fait avec la radio", 'flexzoom.title': 'Zoomer le panadapter au clic sur un spot', 'flexzoom.enable': 'Redimensionner le panadapter au clic sur un spot', 'flexzoom.cw': 'CW', 'flexzoom.ssb': 'Phonie', 'flexzoom.digi': 'Numérique', 'flexzoom.centre': 'Recentrer aussi sur le spot', 'flexzoom.centreHint': '(décoché, ta fenêtre reste où tu l’as mise et seule sa largeur change)', 'spotcol.title': 'Couleurs des spots sur le panadapter', 'spotcol.enable': 'Colorer les spots selon leur statut', 'spotcol.reset': 'valeurs par défaut', 'spotcol.send': 'Envoyer ce statut au panadapter', 'spotcol.text': 'Couleur du texte', 'spotcol.bg': 'Couleur de fond', 'spotcol.s_new': 'Entité jamais contactée', 'spotcol.s_new-band-mode': 'Entité nouvelle sur cette bande ET ce mode', 'spotcol.s_new-band': 'Entité nouvelle sur cette bande', 'spotcol.s_new-mode': 'Entité nouvelle dans ce mode', 'spotcol.s_new-slot': 'Nouveau slot', 'spotcol.s_new-pota': 'Nouveau parc POTA', 'spotcol.s_new-county': 'Nouveau comté US', 'spotcol.s_new-pfx': 'Nouveau préfixe WPX', 'spotcol.s_my-call': 'Mon propre indicatif', 'spotcol.s_worked': 'Indicatif déjà contacté', 'spotcol.s_none': 'Rien de neuf', 'cat.flexDvkDax': "Basculer l'audio d'émission sur DAX pour les messages vocaux", 'cat.flexDvkDaxHint': "(enfoncé le temps d'un message DVK ou d'un enregistrement de QSO, puis remis comme avant pour retrouver ton micro)", diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index b773f16..4f2bcd3 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -2360,6 +2360,7 @@ export namespace main { backend: string; omnirig_rig: number; omnirig_vfo: string; + omnirig_cw_lower: boolean; digi_as_usb: boolean; flex_host: string; flex_port: number; @@ -2414,6 +2415,7 @@ export namespace main { this.backend = source["backend"]; this.omnirig_rig = source["omnirig_rig"]; this.omnirig_vfo = source["omnirig_vfo"]; + this.omnirig_cw_lower = source["omnirig_cw_lower"]; this.digi_as_usb = source["digi_as_usb"]; this.flex_host = source["flex_host"]; this.flex_port = source["flex_port"]; diff --git a/internal/cat/cat.go b/internal/cat/cat.go index 009871a..35dfada 100644 --- a/internal/cat/cat.go +++ b/internal/cat/cat.go @@ -1145,6 +1145,23 @@ func (m *Manager) YaesuDo(fn func(YaesuController) error) error { }) } +// OmniRigController is the handful of OmniRig preferences that can be changed +// without dropping the rig link. +type OmniRigController interface { + SetCWLower(bool) // which of OmniRig's two CW bits means plain CW +} + +// OmniRigDo dispatches an OmniRig preference onto the CAT goroutine. +func (m *Manager) OmniRigDo(fn func(OmniRigController) error) error { + return m.exec(func(b Backend) error { + oc, ok := b.(OmniRigController) + if !ok { + return fmt.Errorf("active CAT backend is not OmniRig") + } + return fn(oc) + }) +} + // KenwoodController is the Kenwood/Elecraft CW-over-CAT capability (the KY keyer), // so a K3 can key CW through its single CAT link instead of a second COM port. type KenwoodController interface { diff --git a/internal/cat/flex.go b/internal/cat/flex.go index 1d4d037..c194cd8 100644 --- a/internal/cat/flex.go +++ b/internal/cat/flex.go @@ -71,10 +71,10 @@ type Flex struct { // Satellite pair: slice A is the downlink, slice B the uplink. -1 when not // armed. satCreatedTX marks an uplink slice OpsLog opened, and is the only // one it will close again. - satOn bool - satRX int - satTX int - satCreatedTX bool + satOn bool + satRX int + satTX int + satCreatedTX bool spotCall map[int]string // spot index → callsign (to fill the call on a panadapter click) spotMode map[int]string // spot index → ADIF mode, so a click can also set the slice mode (SmartSDR tunes the spot's freq but not its mode) spotFreq map[int]int64 // spot index → Hz, so a click can report where it was (the trigger message carries only the index) diff --git a/internal/cat/omnirig.go b/internal/cat/omnirig.go index a31e5e8..cc58ff3 100644 --- a/internal/cat/omnirig.go +++ b/internal/cat/omnirig.go @@ -36,6 +36,14 @@ type OmniRig struct { // on the main VFO) and read B — the frequency "never followed the knob", // while it was following the other one all along. ForceVFO string + // CWLower sends PM_CW_L rather than PM_CW_U when asked for CW. + // + // OmniRig has two CW modes and nothing says which one an .ini file calls + // plain CW. Icom rig files disagree: on some PM_CW_U is CI-V mode 0x03 (CW), + // on others it is 0x07 (CW-R). An operator clicking a CW spot on an IC-7610 + // landed in CW-R every time and had to edit the rig file to get out of it. + // This is the setting that means he does not have to. + CWLower bool omnirig *ole.IDispatch rig *ole.IDispatch @@ -76,7 +84,7 @@ type OmniRig struct { // NewOmniRig creates a non-connected backend. Call Connect before use. // NewOmniRig builds the backend. forceVFO is "" to follow whatever the rig file // reports, or "A"/"B" to override it — see the ForceVFO field. -func NewOmniRig(rigNum int, forceVFO string) *OmniRig { +func NewOmniRig(rigNum int, forceVFO string, cwLower bool) *OmniRig { if rigNum < 1 || rigNum > 2 { rigNum = 1 } @@ -84,7 +92,7 @@ func NewOmniRig(rigNum int, forceVFO string) *OmniRig { if v != "A" && v != "B" { v = "" } - return &OmniRig{RigNum: rigNum, ForceVFO: v} + return &OmniRig{RigNum: rigNum, ForceVFO: v, CWLower: cwLower} } func (o *OmniRig) Name() string { return "omnirig" } @@ -602,6 +610,13 @@ func (o *OmniRig) SetFrequency(hz int64) error { return nil } +// SetCWLower chooses which of OmniRig's two CW bits means plain CW. +// +// Applied to the RUNNING backend, because the CAT link does not depend on it: +// dropping the rig — and with it WSJT-X's rigctl session — to change which bit +// a mode maps to would cost far more than it fixes. +func (o *OmniRig) SetCWLower(on bool) { o.CWLower = on } + // SetMode maps an ADIF mode to the OmniRig PM_* bit and pushes it to the rig. // For SSB, the USB/LSB side is chosen from the rig's current frequency // following worldwide convention (LSB below 14 MHz, USB above). @@ -625,7 +640,13 @@ func (o *OmniRig) SetMode(mode string) error { ) switch strings.ToUpper(strings.TrimSpace(mode)) { case "CW": - bit, bitName = pmCWU, "PM_CW_U" + // Which bit means plain CW is a property of the RIG FILE, not of CW — + // see the CWLower field. + if o.CWLower { + bit, bitName = pmCWL, "PM_CW_L" + } else { + bit, bitName = pmCWU, "PM_CW_U" + } case "SSB": // Decide USB vs LSB from the frequency. Prefer the freq we just COMMANDED // (a clicked spot sets freq then mode ~150ms later): OmniRig's Freq