feat(icom): PTT and offset split at the operator's hand
The MOX and SPLIT controls lived at the bottom of the Transmit card, below the fold on most layouts — a console whose PTT needs scrolling is not a console. A strip under the mode row now carries the PTT and Split as OFF/+1k/+5k/+10k, DXpedition style, on a new SetIcomSplitOffset that takes the chosen TX offset (0 keeps the old CW/SSB convention). The mode-alignment from the previous fix rides along on every engage.
This commit is contained in:
@@ -14533,6 +14533,15 @@ func (a *App) IcomSetSplit(on bool) error {
|
|||||||
return a.cat.IcomDo(func(ic cat.IcomController) error { return ic.SetIcomSplit(on) })
|
return a.cat.IcomDo(func(ic cat.IcomController) error { return ic.SetIcomSplit(on) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IcomSetSplitOffset turns split on with a chosen TX offset (Hz) — the
|
||||||
|
// console's +1/+5/+10 kHz buttons.
|
||||||
|
func (a *App) IcomSetSplitOffset(hz int64) error {
|
||||||
|
if a.cat == nil {
|
||||||
|
return fmt.Errorf("cat not initialized")
|
||||||
|
}
|
||||||
|
return a.cat.IcomDo(func(ic cat.IcomController) error { return ic.SetIcomSplitOffset(true, hz) })
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) IcomSetAntenna(n int) error {
|
func (a *App) IcomSetAntenna(n int) error {
|
||||||
if a.cat == nil {
|
if a.cat == nil {
|
||||||
return fmt.Errorf("cat not initialized")
|
return fmt.Errorf("cat not initialized")
|
||||||
|
|||||||
+4
-2
@@ -28,7 +28,8 @@
|
|||||||
"Icom network audio: “Talk to radio” now works over the LAN too — live microphone straight to the rig, PTT included. With RX audio and a headset, OpsLog is a complete remote station: hear, talk, key CW and log over one network link.",
|
"Icom network audio: “Talk to radio” now works over the LAN too — live microphone straight to the rig, PTT included. With RX audio and a headset, OpsLog is a complete remote station: hear, talk, key CW and log over one network link.",
|
||||||
"Icom console: the SUB display shows the sub receiver’s frequency at all times (it was blank until split), and engaging split copies the mode to the TX VFO so it matches the main.",
|
"Icom console: the SUB display shows the sub receiver’s frequency at all times (it was blank until split), and engaging split copies the mode to the TX VFO so it matches the main.",
|
||||||
"Icom network audio: the stream now opens regardless of the speaker choice — speakers-off (or a failed output device) was silently disabling the whole stream: no audio session, no recordings, no voice keyer, despite the RX-audio option being ticked.",
|
"Icom network audio: the stream now opens regardless of the speaker choice — speakers-off (or a failed output device) was silently disabling the whole stream: no audio session, no recordings, no voice keyer, despite the RX-audio option being ticked.",
|
||||||
"Icom network: a radio in standby keeps its session and the console’s ON button — the quiet-CI-V recovery was tearing the session down every 15 s, so a radio that was off when OpsLog started could never be powered on."
|
"Icom network: a radio in standby keeps its session and the console’s ON button — the quiet-CI-V recovery was tearing the session down every 15 s, so a radio that was off when OpsLog started could never be powered on.",
|
||||||
|
"Icom console: PTT and Split move up under the dial — one PTT button, and Split as OFF/+1k/+5k/+10k, DXpedition style. Engaging split also aligns the TX VFO’s mode with the main."
|
||||||
],
|
],
|
||||||
"fr": [
|
"fr": [
|
||||||
"Console Elecraft : le S-mètre est calibré sur un vrai K3 — S9 et les +dB correspondent désormais à l’affichage de la radio (il lisait environ deux points S trop bas).",
|
"Console Elecraft : le S-mètre est calibré sur un vrai K3 — S9 et les +dB correspondent désormais à l’affichage de la radio (il lisait environ deux points S trop bas).",
|
||||||
@@ -56,7 +57,8 @@
|
|||||||
"Audio réseau Icom : « Talk to radio » fonctionne aussi par le LAN — micro en direct vers la radio, PTT compris. Avec le RX audio et un casque, OpsLog devient une station remote complète : écouter, parler, manipuler la CW et loguer sur un seul lien réseau.",
|
"Audio réseau Icom : « Talk to radio » fonctionne aussi par le LAN — micro en direct vers la radio, PTT compris. Avec le RX audio et un casque, OpsLog devient une station remote complète : écouter, parler, manipuler la CW et loguer sur un seul lien réseau.",
|
||||||
"Console Icom : l’affichage SUB montre la fréquence du sub receiver en permanence (il restait vide hors split), et activer le split copie le mode sur le VFO TX pour qu’il suive le main.",
|
"Console Icom : l’affichage SUB montre la fréquence du sub receiver en permanence (il restait vide hors split), et activer le split copie le mode sur le VFO TX pour qu’il suive le main.",
|
||||||
"Audio réseau Icom : le flux s’ouvre désormais indépendamment du choix d’écoute — enceintes coupées (ou périphérique de sortie en échec) désactivait silencieusement tout le flux : pas de session audio, ni enregistrements, ni voice keyer, malgré la case RX audio cochée.",
|
"Audio réseau Icom : le flux s’ouvre désormais indépendamment du choix d’écoute — enceintes coupées (ou périphérique de sortie en échec) désactivait silencieusement tout le flux : pas de session audio, ni enregistrements, ni voice keyer, malgré la case RX audio cochée.",
|
||||||
"Réseau Icom : une radio en veille garde sa session et le bouton ON de la console — la récupération du CI-V muet détruisait la session toutes les 15 s, donc une radio éteinte au lancement d’OpsLog ne pouvait jamais être allumée."
|
"Réseau Icom : une radio en veille garde sa session et le bouton ON de la console — la récupération du CI-V muet détruisait la session toutes les 15 s, donc une radio éteinte au lancement d’OpsLog ne pouvait jamais être allumée.",
|
||||||
|
"Console Icom : le PTT et le Split remontent sous le cadran — un bouton PTT, et le Split en OFF/+1k/+5k/+10k, façon DXpedition. Activer le split aligne aussi le mode du VFO TX sur le main."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
IcomSetAFGain, IcomSetRFGain, IcomSetNB, IcomSetNBLevel, IcomSetNR, IcomSetNRLevel,
|
IcomSetAFGain, IcomSetRFGain, IcomSetNB, IcomSetNBLevel, IcomSetNR, IcomSetNRLevel,
|
||||||
IcomSetANF, IcomSetAPF, IcomSetAGC, IcomSetPreamp, IcomSetAtt, IcomSetFilter,
|
IcomSetANF, IcomSetAPF, IcomSetAGC, IcomSetPreamp, IcomSetAtt, IcomSetFilter,
|
||||||
AudioMonitorActive, AudioStartMonitor, AudioStopMonitor,
|
AudioMonitorActive, AudioStartMonitor, AudioStopMonitor,
|
||||||
IcomSetRFPower, IcomSetMicGain, IcomSetSplit, IcomTune, IcomSetPTT,
|
IcomSetRFPower, IcomSetMicGain, IcomSetSplit, IcomSetSplitOffset, IcomTune, IcomSetPTT,
|
||||||
IcomSetScope, IcomScopeData, IcomSetScopeMode, IcomSetScopeEdges, GetCATState, SetCATFrequency, SetCATMode,
|
IcomSetScope, IcomScopeData, IcomSetScopeMode, IcomSetScopeEdges, GetCATState, SetCATFrequency, SetCATMode,
|
||||||
IcomSetRIT, IcomSetRITOn, IcomSetXITOn,
|
IcomSetRIT, IcomSetRITOn, IcomSetXITOn,
|
||||||
IcomSetAntenna, IcomSetPBTInner, IcomSetPBTOuter, IcomSetManualNotch, IcomSetNotchPos,
|
IcomSetAntenna, IcomSetPBTInner, IcomSetPBTOuter, IcomSetManualNotch, IcomSetNotchPos,
|
||||||
@@ -786,6 +786,29 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{/* PTT + split, at the operator's hand under the dial — the Transmit
|
||||||
|
card lower down kept these below the fold, and a console whose PTT
|
||||||
|
needs scrolling is not a console. */}
|
||||||
|
<div className="flex items-center gap-1.5 border-t border-border/60 px-2 py-1.5">
|
||||||
|
<button type="button" onClick={toggleMox}
|
||||||
|
className={cn('flex-1 px-3 py-1.5 rounded-md text-xs font-black tracking-wider border transition-colors',
|
||||||
|
tx ? 'bg-destructive border-destructive text-destructive-foreground animate-pulse' : 'bg-card text-foreground border-border hover:bg-muted')}>
|
||||||
|
{tx ? 'TX ON' : 'PTT'}
|
||||||
|
</button>
|
||||||
|
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground pl-1">Split</span>
|
||||||
|
<button type="button" onClick={() => set({ split: false }, () => IcomSetSplit(false))}
|
||||||
|
className={cn('px-2 py-1.5 rounded-md text-[11px] font-bold border transition-colors',
|
||||||
|
!st.split ? 'bg-primary text-primary-foreground border-primary' : 'bg-card text-muted-foreground border-border hover:bg-muted')}>
|
||||||
|
OFF
|
||||||
|
</button>
|
||||||
|
{[1, 5, 10].map((k) => (
|
||||||
|
<button key={k} type="button" onClick={() => set({ split: true }, () => IcomSetSplitOffset(k * 1000))}
|
||||||
|
className={cn('px-2 py-1.5 rounded-md text-[11px] font-bold border transition-colors',
|
||||||
|
st.split ? 'bg-card text-foreground border-border hover:bg-muted' : 'bg-card text-muted-foreground border-border hover:bg-muted')}>
|
||||||
|
+{k}k
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Live meters — always visible: S (RX, click → RST), Po in watts, SWR. */}
|
{/* Live meters — always visible: S (RX, click → RST), Po in watts, SWR. */}
|
||||||
|
|||||||
Vendored
+2
@@ -704,6 +704,8 @@ export function IcomSetScopeMode(arg1:boolean):Promise<void>;
|
|||||||
|
|
||||||
export function IcomSetSplit(arg1:boolean):Promise<void>;
|
export function IcomSetSplit(arg1:boolean):Promise<void>;
|
||||||
|
|
||||||
|
export function IcomSetSplitOffset(arg1:number):Promise<void>;
|
||||||
|
|
||||||
export function IcomSetSquelch(arg1:number):Promise<void>;
|
export function IcomSetSquelch(arg1:number):Promise<void>;
|
||||||
|
|
||||||
export function IcomSetVOX(arg1:boolean):Promise<void>;
|
export function IcomSetVOX(arg1:boolean):Promise<void>;
|
||||||
|
|||||||
@@ -1346,6 +1346,10 @@ export function IcomSetSplit(arg1) {
|
|||||||
return window['go']['main']['App']['IcomSetSplit'](arg1);
|
return window['go']['main']['App']['IcomSetSplit'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function IcomSetSplitOffset(arg1) {
|
||||||
|
return window['go']['main']['App']['IcomSetSplitOffset'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function IcomSetSquelch(arg1) {
|
export function IcomSetSquelch(arg1) {
|
||||||
return window['go']['main']['App']['IcomSetSquelch'](arg1);
|
return window['go']['main']['App']['IcomSetSquelch'](arg1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -640,6 +640,7 @@ type IcomController interface {
|
|||||||
SetRFPower(int) error
|
SetRFPower(int) error
|
||||||
SetMicGain(int) error
|
SetMicGain(int) error
|
||||||
SetIcomSplit(bool) error
|
SetIcomSplit(bool) error
|
||||||
|
SetIcomSplitOffset(bool, int64) error
|
||||||
TuneATU() error
|
TuneATU() error
|
||||||
SetScope(bool) error // enable/disable the spectrum-scope waveform stream
|
SetScope(bool) error // enable/disable the spectrum-scope waveform stream
|
||||||
SetScopeMode(bool) error // true = fixed span, false = center-on-VFO
|
SetScopeMode(bool) error // true = fixed span, false = center-on-VFO
|
||||||
|
|||||||
@@ -1860,10 +1860,16 @@ func (b *IcomSerial) SetMicGain(p int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *IcomSerial) SetIcomSplit(on bool) error {
|
func (b *IcomSerial) SetIcomSplit(on bool) error {
|
||||||
|
return b.SetIcomSplitOffset(on, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIcomSplitOffset enables split with a CHOSEN TX offset in Hz (0 = the
|
||||||
|
// usual convention: +1 kHz on CW, +5 kHz otherwise). The console offers
|
||||||
|
// +1/+5/+10 directly, DXpedition style.
|
||||||
|
func (b *IcomSerial) SetIcomSplitOffset(on bool, offsetHz int64) error {
|
||||||
if on {
|
if on {
|
||||||
// Enable split with the usual "work him up" TX offset: +1 kHz on CW,
|
// Set the unselected (TX) VFO to RX+offset first, then turn split on.
|
||||||
// +5 kHz otherwise (SSB). Set the unselected (TX) VFO to RX+offset first,
|
// 0x25 0x01 + BCD sets the unselected VFO's frequency.
|
||||||
// then turn split on. 0x25 0x01 + BCD sets the unselected VFO's frequency.
|
|
||||||
rx := b.curFreq
|
rx := b.curFreq
|
||||||
if rx <= 0 {
|
if rx <= 0 {
|
||||||
if hz, err := b.readFreq(); err == nil {
|
if hz, err := b.readFreq(); err == nil {
|
||||||
@@ -1871,9 +1877,12 @@ func (b *IcomSerial) SetIcomSplit(on bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rx > 0 {
|
if rx > 0 {
|
||||||
offset := int64(5000)
|
offset := offsetHz
|
||||||
if b.curModeByte == civ.ModeCW || b.curModeByte == civ.ModeCWR {
|
if offset <= 0 {
|
||||||
offset = 1000
|
offset = 5000
|
||||||
|
if b.curModeByte == civ.ModeCW || b.curModeByte == civ.ModeCWR {
|
||||||
|
offset = 1000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ = b.exec(append([]byte{civ.CmdVfoFreq, civ.SubVfoUnselected}, civ.FreqToBCD(rx+offset)...)...)
|
_ = b.exec(append([]byte{civ.CmdVfoFreq, civ.SubVfoUnselected}, civ.FreqToBCD(rx+offset)...)...)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user