Compare commits

..
4 Commits
Author SHA1 Message Date
rouggy ab68e4a84e chore: release v0.27.9 2026-09-02 20:03:08 +02:00
rouggy f6532b2e85 style(dvk): show the DATA-input option only where it does something
Only Kenwood implements it — TX1 is that family's second transmit
command — and every other backend keys the one way it knows. A checkbox
offered to all of them would change nothing on most, which is the same
dead furniture as ANT2 on a radio with one socket. Gated on the Kenwood
backend, and the label says so.
2026-09-02 19:51:38 +02:00
rouggy 8685dbd6cf feat(dvk): key the DATA input, not the microphone
From a TS-590SG report: the voice keyer played through the rig's USB
codec and the radio transmitted silence. Its manual says why — 'TX P1,
0: SEND (normal transmission using the MIC input), 1: DATA SEND
(ACC2/USB input)' — and OpsLog only ever sent the bare TX, so the radio
dutifully opened a front microphone nobody was speaking into.

An option on the audio page, shown for CAT keying, says where the
keyer's audio actually arrives; the manager routes it to a backend that
draws the distinction and falls back to the ordinary key for every rig
where one PTT is all there is. Test PTT goes down the same path, so it
tests what will happen rather than something adjacent.
2026-09-02 16:17:50 +02:00
rouggy b7c87def5b feat(decodes): say when the decoder's band is not the rig's
A decoding application that loses its CAT link keeps announcing the last
dial frequency it knew, and every decode after that carries a stale
band. Nothing downstream can tell: the entity verdicts, the band filter
and the FT map all believe it. Seen for real — MSHV kept saying 80 m,
and Korea read as a NEW BAND because on 80 m it would have been.

Said, not decided. Taking the rig's band instead would be wrong for
anyone decoding a second receiver on another band, and a warning costs
that setup nothing but a line to read past. Shown only while CAT is
actually connected: an empty band means there is nothing to compare
with, never that the rig is on no band. Opens 0.27.9.
2026-09-02 10:25:17 +02:00
11 changed files with 135 additions and 17 deletions
+9 -2
View File
@@ -195,6 +195,7 @@ const (
keyAudioQSOPlayGain = "audio.qso_play_gain" // QSO-recording playback level %
keyAudioPTTMethod = "audio.ptt_method" // "none" (VOX) | "rts" | "dtr"
keyAudioPTTPort = "audio.ptt_port" // COM port for serial PTT
keyAudioPTTData = "audio.ptt_data" // keyer audio arrives on the rig DATA/USB input
keyAudioFormat = "audio.qso_format" // "wav" | "mp3"
keyAudioFromGain = "audio.from_gain" // From Radio (RX) mix level, percent
keyAudioMicGain = "audio.mic_gain" // mic mix level, percent
@@ -8529,6 +8530,10 @@ type AudioSettings struct {
PrerollSeconds int `json:"preroll_seconds"` // rolling pre-roll (default 8)
PTTMethod string `json:"ptt_method"` // "none" (VOX) | "rts" | "dtr"
PTTPort string `json:"ptt_port"` // COM port for serial PTT
// PTTData: the keyer's audio reaches the radio on its DATA/USB input, not
// the microphone socket. CAT keying only — it changes which transmit
// command is sent (a Kenwood TS-590 takes TX1 instead of TX).
PTTData bool `json:"ptt_data"`
Format string `json:"format"` // "wav" | "mp3"
FromGain int `json:"from_gain"` // From Radio (RX) mix level %, default 100
MicGain int `json:"mic_gain"` // mic mix level %, default 100
@@ -8590,7 +8595,7 @@ func (a *App) GetAudioSettings() (AudioSettings, error) {
}
m, err := a.settings.GetMany(a.ctx,
keyAudioFromRadio, keyAudioToRadio, keyAudioRecDevice, keyAudioListenDevice,
keyAudioQSORecord, keyAudioQSODir, keyAudioPreroll, keyAudioPTTMethod, keyAudioPTTPort, keyAudioFormat,
keyAudioQSORecord, keyAudioQSODir, keyAudioPreroll, keyAudioPTTMethod, keyAudioPTTPort, keyAudioPTTData, keyAudioFormat,
keyAudioFromGain, keyAudioMicGain, keyAudioTXGain, keyAudioQSOPlayGain)
if err != nil {
return out, err
@@ -8602,6 +8607,7 @@ func (a *App) GetAudioSettings() (AudioSettings, error) {
out.PTTMethod = v
}
out.PTTPort = m[keyAudioPTTPort]
out.PTTData = m[keyAudioPTTData] == "1"
out.FromRadio = m[keyAudioFromRadio]
out.ToRadio = m[keyAudioToRadio]
out.RecordingDevice = m[keyAudioRecDevice]
@@ -8672,6 +8678,7 @@ func (a *App) SaveAudioSettings(s AudioSettings) error {
keyAudioPreroll: strconv.Itoa(s.PrerollSeconds),
keyAudioPTTMethod: pttMethod,
keyAudioPTTPort: strings.TrimSpace(s.PTTPort),
keyAudioPTTData: boolStr(s.PTTData),
keyAudioFormat: format,
keyAudioFromGain: strconv.Itoa(s.FromGain),
keyAudioMicGain: strconv.Itoa(s.MicGain),
@@ -10599,7 +10606,7 @@ func (a *App) pttKey(cfg AudioSettings) error {
if a.cat == nil {
return fmt.Errorf("CAT not initialized")
}
if err := a.cat.SetPTT(true); err != nil {
if err := a.cat.SetPTTSource(true, cfg.PTTData); err != nil {
applog.Printf("ptt: CAT SetPTT failed: %v", err)
return err
}
+12
View File
@@ -1,4 +1,16 @@
[
{
"version": "0.27.9",
"date": "",
"en": [
"FT decodes warn when the decoding application announces a band the radio is not on — the signature of a lost CAT link, where it repeats the last frequency it knew and every decode after that carries a stale band. Nothing downstream could tell, so NEW BAND was being judged against a band the operator had left. OpsLog says it rather than deciding: a second receiver on another band is a real setup, and it costs that one only a line to read past.",
"Voice keyer with CAT keying: an option saying the keyers audio arrives on the radios DATA / USB input rather than the microphone socket. A Kenwood TS-590 has two transmit commands — TX opens the front mic, TX1 the rear ACC2/USB — so a keyer playing through the rigs own sound card was transmitting dead air while the radio listened to a microphone nobody was speaking into. Shown on the Kenwood backend only — no other radio family draws the distinction — and the Test PTT button exercises the same path."
],
"fr": [
"Les FT decodes signalent quand le logiciel de décodage annonce une bande sur laquelle la radio nest pas — la signature dune liaison CAT perdue, où il répète la dernière fréquence connue et où tous les décodages suivants portent une bande périmée. Rien en aval ne pouvait sen apercevoir : NOUVELLE BANDE était donc jugé sur une bande quittée. OpsLog le dit sans décider à votre place : un second récepteur sur une autre bande est une configuration légitime, et il ne lui en coûte quune ligne à ignorer.",
"Voice keyer avec PTT CAT : une option indiquant que laudio du keyer arrive sur lentrée DATA / USB de la radio et non sur la prise micro. Un Kenwood TS-590 a deux commandes d’émission — TX ouvre le micro de face avant, TX1 lACC2/USB — si bien quun keyer jouant par la carte son du poste émettait dans le vide pendant que la radio écoutait un micro devant lequel personne ne parlait. Affichée sur le backend Kenwood uniquement — aucune autre famille de postes ne fait cette distinction — et le bouton Test PTT emprunte le même chemin."
]
},
{
"version": "0.27.8",
"date": "",
+3
View File
@@ -6328,6 +6328,9 @@ export default function App() {
txState={txState}
txStates={txStates}
spotStatus={spotStatus as any}
// Only while CAT is actually connected: an empty band means "nothing to
// compare with", never "the rig is on no band".
rigBand={catState.connected ? (catState.band || '') : ''}
myCall={station.callsign}
// A click ANSWERS the station: it hands the decode back to WSJT-X/MSHV as
// a Reply, which is the same thing as double-clicking the line in their
+32 -2
View File
@@ -13,7 +13,7 @@
// come from the same resolver the cluster uses, so a call means the same thing in
// both panels rather than being judged twice by two rules.
import { useEffect, useMemo, useState } from 'react';
import { Radio, Search, X, Signal, ArrowUpRight, Timer, Trash2, Ban, Columns2, Bot } from 'lucide-react';
import { AlertTriangle, Radio, Search, X, Signal, ArrowUpRight, Timer, Trash2, Ban, Columns2, Bot } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
import { chaseAllows } from '@/lib/spotDisplay';
@@ -95,6 +95,9 @@ interface Props {
// receiver reported last, which is a coin toss — each pane needs its own.
txStates?: Record<string, TxMsg>;
spotStatus: Record<string, StatusEntry>;
// The band the RIG is on, when CAT is connected. Only ever compared with what
// the decoder announces — see the drift warning.
rigBand?: string;
onCall: (d: Decode) => void;
myCall?: string;
// Drop every decode and transmit message held for this panel. The list is a
@@ -539,7 +542,7 @@ function buildPeriods(filtered: Decode[], txMsgs: TxMsg[]) {
}));
}
export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, onCall, myCall, onClear, onHalt, autoCallOn, onToggleAutoCall }: Props) {
export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, rigBand, onCall, myCall, onClear, onHalt, autoCallOn, onToggleAutoCall }: Props) {
const { t } = useI18n();
// Column widths, dragged in the header and shared by every row. Persisted
// through writeUiPref (not raw localStorage) so the layout travels with data/
@@ -597,6 +600,21 @@ export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, o
// The mode currently on the air, for the slot clock. The newest decode knows
// best; between overs the transmit state still does.
// A decoder that has lost its CAT link keeps announcing the last dial
// frequency it knew, and every decode after that carries a stale band. Nothing
// downstream can tell: the entity verdicts, the band filter and the FT map all
// believe what the decoder said, and an operator ends up reading NEW BAND for a
// band they are not on. (Seen for real: MSHV lost CAT, kept saying 80 m, and
// Korea showed as a new band because on 80 m it would have been.)
//
// Said, not decided. Using the rig's band instead would be wrong for anyone
// decoding a second receiver on another band, and a warning costs that setup
// nothing but a line it can read past.
const decoderBand = decodes.length ? (decodes[decodes.length - 1].band ?? '') : '';
const bandDrift = !!rigBand && !!decoderBand
&& rigBand.toLowerCase() !== decoderBand.toLowerCase();
const driftInstance = decodes.length ? (decodes[decodes.length - 1].instance ?? '') : '';
const liveMode = decodes.length ? decodes[decodes.length - 1].mode : txState?.mode;
const liveTr = trSeconds(liveMode, decodes.length ? decodes[decodes.length - 1].tr_period : undefined);
@@ -733,6 +751,18 @@ export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, o
what the transmit state reports, so it is right the moment anything
is heard and keeps running when the band goes quiet. */}
<PeriodClock trSec={liveTr} mode={liveMode} />
{bandDrift && (
<span
title={t('dec.bandDriftTip')}
className="inline-flex items-center gap-1 rounded-full border border-warning px-2 py-0.5 text-[11px] font-semibold text-warning">
<AlertTriangle className="size-3.5" />
{t('dec.bandDrift', {
app: driftInstance || t('dec.bandDriftApp'),
dec: decoderBand.toUpperCase(),
rig: (rigBand ?? '').toUpperCase(),
})}
</span>
)}
<span className="w-px h-5 bg-border/60 mx-1" />
<button type="button" className={chip(cqOnly, 'success')} onClick={() => setCqOnly(!cqOnly)}>
+17 -2
View File
@@ -1702,13 +1702,13 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
type AudioSettings = {
from_radio: string; to_radio: string; recording_device: string; listening_device: string;
qso_record: boolean; qso_dir: string; preroll_seconds: number;
ptt_method: 'none' | 'cat' | 'rts' | 'dtr'; ptt_port: string; format: 'wav' | 'mp3';
ptt_method: 'none' | 'cat' | 'rts' | 'dtr'; ptt_port: string; ptt_data?: boolean; format: 'wav' | 'mp3';
from_gain: number; mic_gain: number; tx_gain: number; qso_play_gain: number;
};
type AudioDev = { id: string; name: string; default: boolean };
const [audioCfg, setAudioCfg] = useState<AudioSettings>({
from_radio: '', to_radio: '', recording_device: '', listening_device: '',
qso_record: false, qso_dir: '', preroll_seconds: 8, ptt_method: 'none', ptt_port: '', format: 'wav',
qso_record: false, qso_dir: '', preroll_seconds: 8, ptt_method: 'none', ptt_port: '', ptt_data: false, format: 'wav',
from_gain: 100, mic_gain: 100, tx_gain: 100, qso_play_gain: 100,
});
const [audioInputs, setAudioInputs] = useState<AudioDev[]>([]);
@@ -7251,6 +7251,21 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
</Button>
)}
</div>
{/* Kenwood only, because only a Kenwood acts on it: TX1 is that
family's second transmit command. Every other backend keys the
one way it knows, so showing the box there would be a switch
that changes nothing the same dead furniture as ANT2 on a
radio with one socket. */}
{audioCfg.ptt_method === 'cat' && catCfg.backend === 'kenwood' && (
<>
<span />
<label className="flex items-start gap-2 text-sm cursor-pointer" title={t('aud.pttDataHint')}>
<Checkbox className="mt-0.5" checked={!!audioCfg.ptt_data}
onCheckedChange={(c) => setAudioField({ ptt_data: !!c })} />
<span>{t('aud.pttData')}</span>
</label>
</>
)}
{(audioCfg.ptt_method === 'rts' || audioCfg.ptt_method === 'dtr') && (
<>
<Label className="text-sm">{t('aud.pttPort')}</Label>
+4 -4
View File
@@ -162,7 +162,7 @@ const en: Dict = {
'mx.tipThisCall': 'already worked with this callsign',
'mx.tipThisCallConf': 'already confirmed with this callsign',
// FTx decodes panel (Tools -> FT decodes)
'ftmap.tab': 'FT Map', 'ftmap.noGrid': 'Set your station grid in Preferences to place the map.', 'dec.tab': 'FT decodes', 'dec.title': 'Decodes', 'dec.cqOnly': 'CQ only',
'ftmap.tab': 'FT Map', 'ftmap.noGrid': 'Set your station grid in Preferences to place the map.', 'dec.tab': 'FT decodes', 'dec.title': 'Decodes', 'dec.bandDrift': '{app} says {dec}, the rig is on {rig}', 'dec.bandDriftApp': 'the decoder', 'dec.bandDriftTip': 'The decoding application is announcing a band the radio is not on — it has most likely lost its CAT link and is repeating the last frequency it knew. Every decode below carries that band, so the NEW / NEW BAND verdicts are judged against it.', 'dec.cqOnly': 'CQ only',
'dec.allBands': 'All bands', 'dec.allModes': 'All modes', 'toast.qsoLogged': 'QSO logged', 'dec.contsHint': 'Continents: click to keep one or several', 'dec.allConts': 'All continents',
'dec.minSnrTitle': 'Hide anything weaker than this SNR', 'dec.searchPh': 'Call, grid or message',
'dec.clearFilters': 'Clear', 'dec.live': 'live', 'dec.tx': 'TX',
@@ -535,7 +535,7 @@ const en: Dict = {
'aud.preroll': 'Pre-roll (seconds)', 'aud.format': 'File format', 'aud.wav': 'WAV (lossless, larger)', 'aud.mp3': 'MP3 (compressed, small)',
'aud.fromLevel': 'From Radio level', 'aud.txLevel': 'Voice keyer level', 'aud.txLevelHint': 'Level of the recorded messages sent to the radio. Raise it if your voice keyer is much quieter than your microphone; Play previews at this same level. If the radio transmits almost nothing, its modulation source is still the front microphone: on an FTDX10 set MENU → SSB MOD SOURCE to REAR (the USB input).', 'aud.micLevel': 'Mic level', 'aud.qsoPlayLevel': 'QSO playback level', 'aud.levelHint': 'If your voice is louder than the station, lower Mic level.',
'aud.autoSend': 'Auto-send the recording to the station by e-mail when I log a QSO',
'aud.dvkTitle': 'Voice keyer messages (F1F12)', 'aud.deleteMsg': 'Delete this message', 'aud.pttMethod': 'PTT method', 'aud.pttNone': 'None (VOX)', 'aud.pttCat': 'CAT (the radio link)', 'aud.pttRts': 'Serial RTS', 'aud.pttDtr': 'Serial DTR',
'aud.dvkTitle': 'Voice keyer messages (F1F12)', 'aud.deleteMsg': 'Delete this message', 'aud.pttData': 'Kenwood: the keyers audio arrives on the DATA / USB input', 'aud.pttDataHint': 'Sends TX1 (ACC2/USB) instead of TX (front microphone) — the TS-590 familys second transmit command. Without it the radio transmits while listening to a microphone nobody is speaking into. Kenwood only: no other backend has the distinction.', 'aud.pttMethod': 'PTT method', 'aud.pttNone': 'None (VOX)', 'aud.pttCat': 'CAT (the radio link)', 'aud.pttRts': 'Serial RTS', 'aud.pttDtr': 'Serial DTR',
'aud.testPtt': 'Test PTT', 'aud.pttPort': 'PTT COM port', 'aud.pickPort': 'Pick a COM port', 'aud.selectPort': '— select —', 'aud.refresh': 'Refresh',
'aud.msgPlaceholder': 'Message {n} label (CQ, report, 73…)', 'aud.holdRec': '● Hold to rec', 'aud.recordingNow': '● Recording…', 'aud.play': '▶ Play', 'aud.stop': '■ Stop',
'aud.errPttTest': 'PTT test: ', 'aud.errRecord': 'Record: ', 'aud.errSave': 'Save: ', 'aud.errPlay': 'Play: ',
@@ -682,7 +682,7 @@ const fr: Dict = {
'mx.tipThisCall': 'déjà contacté avec cet indicatif',
'mx.tipThisCallConf': 'déjà confirmé avec cet indicatif',
// Panneau des decodes FTx (Outils -> Decodes FT)
'ftmap.tab': 'FT Map', 'ftmap.noGrid': 'Renseigne ton locator dans les Préférences pour placer la carte.', 'dec.tab': 'Decodes FT', 'dec.title': 'Decodes', 'dec.cqOnly': 'CQ seulement',
'ftmap.tab': 'FT Map', 'ftmap.noGrid': 'Renseigne ton locator dans les Préférences pour placer la carte.', 'dec.tab': 'Decodes FT', 'dec.title': 'Decodes', 'dec.bandDrift': '{app} annonce {dec}, le poste est sur {rig}', 'dec.bandDriftApp': 'le décodeur', 'dec.bandDriftTip': 'Le logiciel de décodage annonce une bande sur laquelle la radio nest pas — il a très probablement perdu sa liaison CAT et répète la dernière fréquence connue. Tous les décodages ci-dessous portent cette bande, et les verdicts NOUVEAU / NOUVELLE BANDE sont jugés dessus.', 'dec.cqOnly': 'CQ seulement',
'dec.allBands': 'Toutes bandes', 'dec.allModes': 'Tous modes', 'toast.qsoLogged': 'QSO enregistré', 'dec.contsHint': 'Continents : clique pour en garder un ou plusieurs', 'dec.allConts': 'Tous continents',
'dec.minSnrTitle': 'Masquer tout ce qui est plus faible que ce rapport', 'dec.searchPh': 'Indicatif, locator ou message',
'dec.clearFilters': 'Effacer', 'dec.live': 'en direct', 'dec.tx': 'TX',
@@ -1037,7 +1037,7 @@ const fr: Dict = {
'aud.preroll': 'Pré-enregistrement (secondes)', 'aud.format': 'Format de fichier', 'aud.wav': 'WAV (sans perte, plus volumineux)', 'aud.mp3': 'MP3 (compressé, léger)',
'aud.fromLevel': 'Niveau depuis la radio', 'aud.txLevel': 'Niveau du voice keyer', 'aud.txLevelHint': "Niveau des messages enregistrés envoyés à la radio. Augmentez-le si votre voice keyer est bien plus faible que votre micro ; Lire fait entendre ce même niveau. Si la radio n'émet presque rien, c'est que sa source de modulation est restée le micro de façade : sur un FTDX10, réglez MENU → SSB MOD SOURCE sur REAR (l'entrée USB).", 'aud.micLevel': 'Niveau micro', 'aud.qsoPlayLevel': 'Niveau de relecture QSO', 'aud.levelHint': 'Si votre voix est plus forte que la station, baissez le niveau micro.',
'aud.autoSend': "Envoyer automatiquement l'enregistrement à la station par e-mail lorsque j'enregistre un QSO",
'aud.dvkTitle': 'Messages du manipulateur vocal (F1F12)', 'aud.deleteMsg': 'Supprimer ce message', 'aud.pttMethod': 'Méthode PTT', 'aud.pttNone': 'Aucune (VOX)', 'aud.pttCat': 'CAT (la liaison radio)', 'aud.pttRts': 'RTS série', 'aud.pttDtr': 'DTR série',
'aud.dvkTitle': 'Messages du manipulateur vocal (F1F12)', 'aud.deleteMsg': 'Supprimer ce message', 'aud.pttData': 'Kenwood : laudio du keyer arrive sur lentrée DATA / USB', 'aud.pttDataHint': 'Envoie TX1 (ACC2/USB) au lieu de TX (micro de face avant) — la seconde commande d’émission de la famille TS-590. Sans cela la radio émet en écoutant un micro devant lequel personne ne parle. Kenwood uniquement : aucun autre backend na cette distinction.', 'aud.pttMethod': 'Méthode PTT', 'aud.pttNone': 'Aucune (VOX)', 'aud.pttCat': 'CAT (la liaison radio)', 'aud.pttRts': 'RTS série', 'aud.pttDtr': 'DTR série',
'aud.testPtt': 'Tester le PTT', 'aud.pttPort': 'Port COM du PTT', 'aud.pickPort': 'Choisir un port COM', 'aud.selectPort': '— choisir —', 'aud.refresh': 'Actualiser',
'aud.msgPlaceholder': 'Libellé du message {n} (CQ, report, 73…)', 'aud.holdRec': '● Maintenir pour enreg.', 'aud.recordingNow': '● Enregistrement…', 'aud.play': '▶ Lire', 'aud.stop': '■ Arrêter',
'aud.errPttTest': 'Test PTT : ', 'aud.errRecord': 'Enregistrement : ', 'aud.errSave': 'Sauvegarde : ', 'aud.errPlay': 'Lecture : ',
+1 -1
View File
@@ -1,6 +1,6 @@
// Single source of truth for the app version shown in the UI (header + About).
// Bump this on a release (the release script updates it alongside telemetry.go).
export const APP_VERSION = '0.27.8';
export const APP_VERSION = '0.27.9';
// Author / credits, shown in Help -> About.
export const APP_AUTHOR = 'F4BPO';
+2
View File
@@ -1924,6 +1924,7 @@ export namespace main {
preroll_seconds: number;
ptt_method: string;
ptt_port: string;
ptt_data: boolean;
format: string;
from_gain: number;
mic_gain: number;
@@ -1945,6 +1946,7 @@ export namespace main {
this.preroll_seconds = source["preroll_seconds"];
this.ptt_method = source["ptt_method"];
this.ptt_port = source["ptt_port"];
this.ptt_data = source["ptt_data"];
this.format = source["format"];
this.from_gain = source["from_gain"];
this.mic_gain = source["mic_gain"];
+27
View File
@@ -256,6 +256,33 @@ func (m *Manager) SetPTT(on bool) error {
return m.exec(func(b Backend) error { return b.SetPTT(on) })
}
// dataPTTSetter is implemented by a backend that can key the DATA input rather
// than the microphone. A Kenwood TS-590 has two transmit commands and takes its
// audio from a different socket for each: TX (or TX0) opens the front mic, TX1
// the rear ACC2/USB. Send the wrong one and the radio transmits in silence,
// because the audio arriving on USB is simply not the input it is listening to.
type dataPTTSetter interface {
SetPTTData(on bool) error
}
// SetPTTSource keys the transmitter, saying WHERE the audio is coming from.
//
// data=true means "the audio reaches the radio on its data/USB input" — what a
// voice keyer playing through the rig's own sound card needs. A backend that
// draws no distinction (every rig where one PTT is all there is) falls back to
// the ordinary key, so nothing changes for it.
func (m *Manager) SetPTTSource(on, data bool) error {
if !data {
return m.SetPTT(on)
}
return m.exec(func(b Backend) error {
if d, ok := b.(dataPTTSetter); ok {
return d.SetPTTData(on)
}
return b.SetPTT(on)
})
}
// splitSetter is implemented by the backends that can arm split AND place the
// transmit frequency. Both together: arming without setting the dial transmits
// on whatever the transmit VFO happened to hold, which is worse than refusing.
+22
View File
@@ -632,6 +632,28 @@ func (k *Kenwood) SetPTT(on bool) error {
return k.write("RX;")
}
// SetPTTData keys the transmitter on the DATA input: TX1 on a TS-590, which is
// ACC2/USB rather than the front microphone. The radio's own manual is explicit
// that the parameter chooses the input — "0: SEND (normal transmission using
// the MIC input), 1: DATA SEND (ACC2/USB input)" — so a voice keyer playing
// into the rig's USB codec has to say TX1 or it transmits dead air while the
// radio listens to a microphone nobody is speaking into.
//
// Unkeying is the same RX either way; there is no data-flavoured stop.
func (k *Kenwood) SetPTTData(on bool) error {
k.mu.Lock()
defer k.mu.Unlock()
if k.port == nil {
return fmt.Errorf("kenwood: not connected")
}
k.tx = on
if on {
k.txAt = time.Now()
return k.write("TX1;")
}
return k.write("RX;")
}
func (k *Kenwood) write(cmd string) error {
if k.port == nil {
return fmt.Errorf("kenwood: not connected")
+1 -1
View File
@@ -21,7 +21,7 @@ import (
const (
// appVersion is stamped on every heartbeat (and could feed the About box).
appVersion = "0.27.8"
appVersion = "0.27.9"
// posthogHost is the PostHog ingestion endpoint. EU cloud by default; change
// to https://us.i.posthog.com for a US project.