feat(elecraft): the rest of the main controls on the K3/K4 console

So the tester can exercise everything in one session instead of one
control per build: RF and mic gain, squelch, preamp, attenuator, NB, NR,
AGC, filter width, antenna, RIT/XIT with a clear, and the keyer speed.

The analogue scales differ between an Elecraft and a Kenwood — RF gain
000-250, mic 000-060, squelch 000-029 against 0-255 — so each is mapped
through its own full scale and shown as a percentage. Using one rig's
range on the other silently halves or doubles every setting.

Antenna only appears when the radio answers AN: a K3 without the internal
ATU has one socket, and a switch that goes nowhere is worse than none.
This commit is contained in:
2026-08-24 14:29:38 +02:00
parent 60dad353ce
commit dd77e7f467
8 changed files with 502 additions and 7 deletions
+110 -3
View File
@@ -2,6 +2,9 @@ import { useEffect, useRef, useState } from 'react';
import { Radio, Power, Activity, AudioLines, SlidersHorizontal } from 'lucide-react';
import {
GetKenwoodState, RefreshKenwood, SetKenwoodPower, SetKenwoodAFGain, SetKenwoodTX, TuneKenwoodATU,
SetKenwoodRFGain, SetKenwoodMicGain, SetKenwoodSquelch, SetKenwoodPreamp, SetKenwoodAtt,
SetKenwoodNB, SetKenwoodNR, SetKenwoodAGC, SetKenwoodFilter, SetKenwoodAntenna,
SetKenwoodRIT, SetKenwoodXIT, ClearKenwoodRIT, SetKenwoodKeySpeed,
GetCATState,
} from '../../wailsjs/go/main/App';
import { cn } from '@/lib/utils';
@@ -14,16 +17,26 @@ type KenwoodState = {
transmitting: boolean; split: boolean; split_tx_hz?: number;
s_meter: number; s_meter_raw: number;
power_meter: number; swr: number; swr_raw: number;
rf_power: number; af_gain: number;
rf_power: number; af_gain: number; rf_gain: number; mic_gain: number; squelch: number;
preamp: boolean; att: boolean; nb: boolean; nr: boolean; agc?: string;
filter_hz: number; antenna: number; rit: boolean; xit: boolean; key_speed: number;
meters_provisional: boolean;
};
const ZERO: KenwoodState = {
available: false, elecraft: false, transmitting: false, split: false,
s_meter: 0, s_meter_raw: 0, power_meter: 0, swr: 0, swr_raw: 0,
rf_power: 0, af_gain: 0, meters_provisional: true,
rf_power: 0, af_gain: 0, rf_gain: 0, mic_gain: 0, squelch: 0,
preamp: false, att: false, nb: false, nr: false,
filter_hz: 0, antenna: 0, rit: false, xit: false, key_speed: 0,
meters_provisional: true,
};
// Filter widths worth a button. CW work happens at 200-500 Hz, SSB at 2.4-2.8
// kHz; the rest of the range is reachable from the radio's own knob, and a
// panel offering thirty widths is slower to use than the knob it replaces.
const FILTERS = [200, 400, 700, 1000, 1800, 2400, 2800];
// Raw S-meter → S units. The K3 answers 0-21 across S0…S9+60; S9 is taken at
// raw 9 and each step above it as 6 dB. PROVISIONAL, like the rest of the
// scaling: the raw value is on screen and in the log, so a real radio settles
@@ -47,6 +60,18 @@ function sSegColor(frac: number) {
return '#16a34a';
}
// One button shape for every on/off control, so the panel reads as one
// instrument instead of a collection of differently-styled switches.
function Toggle({ label, on, off, onClick }: { label: string; on: boolean; off: boolean; onClick: () => void }) {
return (
<button type="button" disabled={off} onClick={onClick}
className={cn('px-2 py-1 rounded-md text-[11px] font-bold tracking-wide border transition-all disabled:opacity-30',
on ? 'bg-primary text-primary-foreground border-primary' : 'bg-card text-muted-foreground border-border hover:border-primary/60')}>
{label}
</button>
);
}
export function ElecraftPanel({ onReportRST }: { onReportRST?: (rst: string) => void }) {
const { t } = useI18n();
const [st, setSt] = useState<KenwoodState>(ZERO);
@@ -54,7 +79,7 @@ export function ElecraftPanel({ onReportRST }: { onReportRST?: (rst: string) =>
const [err, setErr] = useState('');
// Optimistic overlay: a slider must follow the finger, not the poll. Dropped
// once the radio has had time to answer with the value it actually took.
const [local, setLocal] = useState<{ rf_power?: number; af_gain?: number }>({});
const [local, setLocal] = useState<{ rf_power?: number; af_gain?: number; rf_gain?: number; mic_gain?: number; squelch?: number; key_speed?: number }>({});
const localAtRef = useRef(0);
useEffect(() => {
@@ -80,6 +105,8 @@ export function ElecraftPanel({ onReportRST }: { onReportRST?: (rst: string) =>
const view = { ...st, ...local };
const off = !st.available;
const setErrMsg = (e: any) => setErr(String(e?.message ?? e));
const put = (patch: typeof local, run: () => Promise<any>) => {
setLocal((p) => ({ ...p, ...patch }));
localAtRef.current = Date.now();
@@ -169,6 +196,86 @@ export function ElecraftPanel({ onReportRST }: { onReportRST?: (rst: string) =>
className="flex-1 accent-primary" />
<span className="w-12 text-right font-mono tabular-nums">{view.af_gain ?? 0}</span>
</label>
<label className="flex items-center gap-2 text-xs">
<span className="w-16 shrink-0 text-muted-foreground">{t('k3.rfGain')}</span>
<input type="range" min={0} max={100} step={1} disabled={off}
value={view.rf_gain ?? 0}
onChange={(e) => put({ rf_gain: Number(e.target.value) }, () => SetKenwoodRFGain(Number(e.target.value)))}
className="flex-1 accent-primary" />
<span className="w-12 text-right font-mono tabular-nums">{view.rf_gain ?? 0}</span>
</label>
<label className="flex items-center gap-2 text-xs">
<span className="w-16 shrink-0 text-muted-foreground">{t('k3.micGain')}</span>
<input type="range" min={0} max={100} step={1} disabled={off}
value={view.mic_gain ?? 0}
onChange={(e) => put({ mic_gain: Number(e.target.value) }, () => SetKenwoodMicGain(Number(e.target.value)))}
className="flex-1 accent-primary" />
<span className="w-12 text-right font-mono tabular-nums">{view.mic_gain ?? 0}</span>
</label>
<label className="flex items-center gap-2 text-xs">
<span className="w-16 shrink-0 text-muted-foreground">{t('k3.squelch')}</span>
<input type="range" min={0} max={100} step={1} disabled={off}
value={view.squelch ?? 0}
onChange={(e) => put({ squelch: Number(e.target.value) }, () => SetKenwoodSquelch(Number(e.target.value)))}
className="flex-1 accent-primary" />
<span className="w-12 text-right font-mono tabular-nums">{view.squelch ?? 0}</span>
</label>
{/* Receive chain. Toggles, because that is what they are on the radio:
one press each, and the state comes back from the rig rather than
from what the button was asked to do. */}
<div className="flex flex-wrap items-center gap-1.5 pt-1">
<Toggle label="PRE" on={view.preamp} off={off} onClick={() => SetKenwoodPreamp(!view.preamp).catch(setErrMsg)} />
<Toggle label="ATT" on={view.att} off={off} onClick={() => SetKenwoodAtt(!view.att).catch(setErrMsg)} />
<Toggle label="NB" on={view.nb} off={off} onClick={() => SetKenwoodNB(!view.nb).catch(setErrMsg)} />
<Toggle label="NR" on={view.nr} off={off} onClick={() => SetKenwoodNR(!view.nr).catch(setErrMsg)} />
<span className="w-2" />
{['OFF', 'SLOW', 'FAST'].map((a) => (
<Toggle key={a} label={a} on={(view.agc || '').toUpperCase() === a} off={off}
onClick={() => SetKenwoodAGC(a).catch(setErrMsg)} />
))}
</div>
{/* RIT / XIT. Clear zeroes both offsets at once — the radio's own RC,
and what an operator means by "clear it". */}
<div className="flex flex-wrap items-center gap-1.5">
<Toggle label="RIT" on={view.rit} off={off} onClick={() => SetKenwoodRIT(!view.rit).catch(setErrMsg)} />
<Toggle label="XIT" on={view.xit} off={off} onClick={() => SetKenwoodXIT(!view.xit).catch(setErrMsg)} />
<Toggle label={t('k3.clear')} on={false} off={off} onClick={() => ClearKenwoodRIT().catch(setErrMsg)} />
{/* Antenna only when the radio answered AN — a K3 without the internal
ATU has one socket and no switch to offer. */}
{view.antenna > 0 && (<>
<span className="w-2" />
<span className="text-[10px] uppercase tracking-wider text-muted-foreground">{t('k3.antenna')}</span>
{[1, 2].map((n) => (
<Toggle key={n} label={'ANT' + n} on={view.antenna === n} off={off}
onClick={() => SetKenwoodAntenna(n).catch(setErrMsg)} />
))}
</>)}
</div>
{/* Filter width */}
<div className="flex flex-wrap items-center gap-1.5">
<span className="text-[10px] uppercase tracking-wider text-muted-foreground">{t('k3.filter')}</span>
{FILTERS.map((hz) => (
<Toggle key={hz} label={hz >= 1000 ? (hz / 1000).toFixed(1) + 'k' : String(hz)}
on={view.filter_hz === hz} off={off}
onClick={() => SetKenwoodFilter(hz).catch(setErrMsg)} />
))}
{view.filter_hz > 0 && (
<span className="text-[10px] font-mono text-muted-foreground">{view.filter_hz} Hz</span>
)}
</div>
{/* CW keyer speed — the radio's own keyer, the one the K3 sends with. */}
<label className="flex items-center gap-2 text-xs">
<span className="w-16 shrink-0 text-muted-foreground">{t('k3.keySpeed')}</span>
<input type="range" min={8} max={50} step={1} disabled={off}
value={view.key_speed || 20}
onChange={(e) => put({ key_speed: Number(e.target.value) }, () => SetKenwoodKeySpeed(Number(e.target.value)))}
className="flex-1 accent-primary" />
<span className="w-12 text-right font-mono tabular-nums">{view.key_speed || 0} wpm</span>
</label>
</div>
</section>
);
+2 -2
View File
@@ -426,7 +426,7 @@ const en: Dict = {
'tgp.online': 'online', 'tgp.offline': 'offline', 'tgp.close': 'Close', 'tgp.connecting': 'Connecting…', 'tgp.swr': 'SWR', 'tgp.power': 'Fwd power', 'tgp.tune': 'Tune', 'tgp.tuning': 'Tuning…', 'tgp.tuneHint': 'Start an automatic tuning cycle on the active channel — key the rig into a carrier so the tuner can measure SWR', 'tgp.bypass': 'Bypass', 'tgp.bypassHint': 'Toggle global bypass — route the antenna straight through, tuner out of line', 'tgp.operate': 'Operate', 'tgp.standby': 'Standby', 'tgp.operateHint': 'Toggle Operate / Standby',
'tgp.title': 'Tuner Genius', 'tgp.chActive': 'Channel {letter} — active', 'tgp.chSelect': 'Make channel {letter} active', 'tgp.chActiveTag': 'active', 'tgp.ant': 'Ant', 'tgp.bypassed': 'Bypassed', 'tgp.inLine': 'In line',
'flxp.ritHint': 'RIT — shifts your RECEIVE frequency only. Wheel, ± or arrow keys to adjust (Ctrl = 100 Hz). The offset is kept when you switch it off.', 'flxp.xitHint': 'XIT — shifts your TRANSMIT frequency only. Wheel, ± or arrow keys to adjust (Ctrl = 100 Hz). The offset is kept when you switch it off.',
'flxp.smartsdrRemote': 'SmartSDR remote control', 'flxp.offline': 'OFFLINE', 'flxp.waiting': 'Waiting for the FlexRadio… (set CAT to FlexRadio and connect)', 'flxp.transmit': 'Transmit', 'flxp.rfPower': 'RF Power', 'flxp.tunePwr': 'Tune Pwr', 'flxp.rstChaseHint': 'Chase the pile-up: when the CW skimmer marks a report ({m}) on the panadapter, move the TRANSMIT slice there — that is where the DX was listening a second ago. The receive slice never moves. Right-click to change the marker text.', 'flxp.rstChaseMarkerHint': 'The text the skimmer writes for a report — whatever SDC is set to send (599, 5NN…). Several can be given, separated by commas; add the old-report marker to chase those too.', 'flxp.rstChaseOffset': 'off', 'k3.console': 'Elecraft Console', 'k3.waiting': 'Waiting for the radio… (set CAT to Elecraft or Kenwood and connect)', 'k3.power': 'Power', 'k3.volume': 'Volume', 'k3.refreshHint': 'Re-read the settings from the radio — for when a knob was turned on the front panel.', 'k3.sMeterHint': 'Click to use this reading as the report sent. Raw value from the rig: {raw}.', 'k3.tuneHint': 'Start an ATU tuning cycle (K3: a tap of the ATU TUNE button). The exact command sent is written to the log.', 'k3.provisional': 'Meter scaling is provisional: it has not yet been confirmed against a real K3, and the raw readings are written to the log so it can be.', 'flxp.splitHint': 'Split: RX/TX on separate slices. ON creates a TX slice +1 kHz (CW) / +5 kHz (SSB) up, like SmartSDR.', 'flxp.sliceHint': 'Click to make this the active slice — frequency, mode, DSP and spot-clicks all follow it.', 'flxp.txSlice': 'This slice transmits', 'flxp.setTxSlice': 'Move TX to this slice (transmit here)', 'flxp.voxDly': 'VOX Dly', 'flxp.speed': 'Speed', 'flxp.pitch': 'Pitch', 'flxp.delay': 'Delay',
'flxp.smartsdrRemote': 'SmartSDR remote control', 'flxp.offline': 'OFFLINE', 'flxp.waiting': 'Waiting for the FlexRadio… (set CAT to FlexRadio and connect)', 'flxp.transmit': 'Transmit', 'flxp.rfPower': 'RF Power', 'flxp.tunePwr': 'Tune Pwr', 'flxp.rstChaseHint': 'Chase the pile-up: when the CW skimmer marks a report ({m}) on the panadapter, move the TRANSMIT slice there — that is where the DX was listening a second ago. The receive slice never moves. Right-click to change the marker text.', 'flxp.rstChaseMarkerHint': 'The text the skimmer writes for a report — whatever SDC is set to send (599, 5NN…). Several can be given, separated by commas; add the old-report marker to chase those too.', 'flxp.rstChaseOffset': 'off', 'k3.console': 'Elecraft Console', 'k3.waiting': 'Waiting for the radio… (set CAT to Elecraft or Kenwood and connect)', 'k3.rfGain': 'RF gain', 'k3.micGain': 'Mic', 'k3.squelch': 'Squelch', 'k3.filter': 'Filter', 'k3.antenna': 'Antenna', 'k3.clear': 'CLEAR', 'k3.keySpeed': 'Keyer', 'k3.power': 'Power', 'k3.volume': 'Volume', 'k3.refreshHint': 'Re-read the settings from the radio — for when a knob was turned on the front panel.', 'k3.sMeterHint': 'Click to use this reading as the report sent. Raw value from the rig: {raw}.', 'k3.tuneHint': 'Start an ATU tuning cycle (K3: a tap of the ATU TUNE button). The exact command sent is written to the log.', 'k3.provisional': 'Meter scaling is provisional: it has not yet been confirmed against a real K3, and the raw readings are written to the log so it can be.', 'flxp.splitHint': 'Split: RX/TX on separate slices. ON creates a TX slice +1 kHz (CW) / +5 kHz (SSB) up, like SmartSDR.', 'flxp.sliceHint': 'Click to make this the active slice — frequency, mode, DSP and spot-clicks all follow it.', 'flxp.txSlice': 'This slice transmits', 'flxp.setTxSlice': 'Move TX to this slice (transmit here)', 'flxp.voxDly': 'VOX Dly', 'flxp.speed': 'Speed', 'flxp.pitch': 'Pitch', 'flxp.delay': 'Delay',
'flxp.receiveActive': 'Receive (active slice)', 'flxp.muted': 'Muted — click to unmute', 'flxp.mute': 'Mute RX audio', 'flxp.filter': 'Filter', 'flxp.amplifier': 'Amplifier', 'flxp.atuTune': 'TUNE', 'flxp.atuTuneHint': 'Start a tuning cycle on the built-in ATU. The radio keys a carrier itself to measure the match.', 'flxp.atuBypass': 'BYPASS', 'flxp.atuBypassHint': 'Take the ATU out of line (straight through).', 'flxp.atuMem': 'MEM', 'flxp.atuMemHint': 'Reuse the stored tuning solution for this frequency instead of tuning again.', 'flxp.atuIdle': 'not tuned', 'flxp.atuTuning': 'tuning…', 'flxp.atuOk': 'tuned', 'flxp.atuFail': 'TUNE FAILED', 'flxp.atuBypassed': 'bypassed', 'flxp.atuAborted': 'aborted', 'flxp.ampInLine': 'Amplifier is in line (transmitting through PA).', 'flxp.ampBypassed': 'Amplifier bypassed (standby).', 'flxp.pgConnected': 'PowerGenius connected', 'flxp.pgOffline': 'PowerGenius offline', 'flxp.fan': 'Fan', 'flxp.fanStandard': 'Standard', 'flxp.fanContest': 'Contest', 'flxp.fanBroadcast': 'Broadcast', 'flxp.fault': 'FAULT', 'flxp.meters': 'Meters', 'flxp.voltage': 'VOLTAGE', 'flxp.paTemp': 'PA TEMP', 'flxp.txFilter': 'TX filter', 'flxp.micProfile': 'Mic profile', 'flxp.noMeters': "No meters yet — waiting for the radio's UDP stream…", 'flxp.amplifierHdr': 'AMPLIFIER', 'flxp.outputPower': 'OUTPUT POWER', 'flxp.speOffline': 'SPE offline', 'flxp.acomOffline': 'ACOM offline', 'flxp.ampPick': 'Choose which amplifier this card shows', 'flxp.dspV4Hint': 'SmartSDR v4 DSP (8000/Aurora series)', 'flxp.daxHint': 'DAX as the transmit audio source (SmartSDR transmit-bar DAX button) — for WSJT-X & co', 'flxp.rnnHint': 'RNN — AI noise reduction (on/off)', 'flxp.anftHint': 'ANFT — FFT-based automatic notch filter (on/off)', 'flxp.dspNoise': 'Noise', 'flxp.dspMore': 'Show/hide advanced DSP (WNB, v4 NR/notch)',
'icmp.spectrum': 'Spectrum', 'icmp.scopeFixed': 'Fixed — double-click / wheel to tune', 'icmp.scopeCenter': 'Center — follows VFO', 'icmp.scopeOff': 'Scope off', 'icmp.scopePanDown': 'Shift scope 50 kHz', 'icmp.scopePanUp': 'Shift scope +50 kHz', 'icmp.scopeCenterVfo': 'Center scope on the current frequency (±50 kHz)', 'icmp.notConnected': "Icom not connected. Enable the Icom CI-V backend in Settings → CAT and connect the radio's USB port.", 'icmp.refresh': 'Refresh', 'icmp.meters': 'Meters', 'icmp.transmit': 'Transmit', 'icmp.power': 'Power', 'icmp.mic': 'Mic', 'icmp.receive': 'Receive', 'icmp.preamp': 'Preamp', 'icmp.filter': 'Filter', 'icmp.noiseNotch': 'Noise / Notch', 'icmp.autoNotch': 'Auto notch filter', 'icmp.apf': 'Audio peak filter (CW)', 'icmp.clarifiers': 'RIT / ΔTX', 'icmp.ritHint': 'Wheel or ± to shift · Ctrl+←/→ shifts RIT when active', 'icmp.bandsAntenna': 'Bands & Antenna', 'icmp.bandCurrent': 'The rig is on {b} m', 'icmp.antenna': 'Antenna', 'icmp.passband': 'Passband / Notch', 'icmp.pbtCenter': 'Center PBT', 'icmp.manualNotch': 'Manual notch — MN on, then set position', 'icmp.squelch': 'Squelch', 'icmp.powerOnHint': 'Power the radio ON (boots ~15 s)', 'icmp.powerOffHint': 'Power the radio OFF', 'icmp.powerOffConfirm': 'Switch the radio OFF?',
'rst.clickToFill': 'Click to set RST tx from the signal',
@@ -898,7 +898,7 @@ const fr: Dict = {
'tgp.online': 'en ligne', 'tgp.offline': 'hors ligne', 'tgp.close': 'Fermer', 'tgp.connecting': 'Connexion…', 'tgp.swr': 'ROS', 'tgp.power': 'Puiss. directe', 'tgp.tune': 'Accord', 'tgp.tuning': 'Accord…', 'tgp.tuneHint': "Lancer un cycle d'accord automatique sur le canal actif — passe la radio en porteuse pour que le coupleur mesure le ROS", 'tgp.bypass': 'Bypass', 'tgp.bypassHint': "Basculer le bypass global — antenne en direct, coupleur hors ligne", 'tgp.operate': 'Operate', 'tgp.standby': 'Standby', 'tgp.operateHint': 'Basculer Operate / Standby',
'tgp.title': 'Tuner Genius', 'tgp.chActive': 'Canal {letter} — actif', 'tgp.chSelect': 'Activer le canal {letter}', 'tgp.chActiveTag': 'actif', 'tgp.ant': 'Ant', 'tgp.bypassed': 'Bypass', 'tgp.inLine': 'En ligne',
'flxp.ritHint': "RIT — décale uniquement ta fréquence de RÉCEPTION. Molette, ± ou flèches pour ajuster (Ctrl = 100 Hz). Le décalage est conservé quand tu l'éteins.", 'flxp.xitHint': "XIT — décale uniquement ta fréquence d'ÉMISSION. Molette, ± ou flèches pour ajuster (Ctrl = 100 Hz). Le décalage est conservé quand tu l'éteins.",
'flxp.smartsdrRemote': 'Contrôle à distance SmartSDR', 'flxp.offline': 'HORS LIGNE', 'flxp.waiting': 'En attente du FlexRadio… (règle le CAT sur FlexRadio et connecte)', 'flxp.transmit': 'Émission', 'flxp.rfPower': 'Puissance RF', 'flxp.tunePwr': 'Puiss. TUNE', 'flxp.rstChaseHint': "Chasser le pile-up : quand le skimmer CW marque un report ({m}) sur le panadapter, déplacer la slice d'ÉMISSION dessus — c'est là que le DX écoutait il y a une seconde. La slice de réception ne bouge jamais. Clic droit pour changer le texte du marqueur.", 'flxp.rstChaseMarkerHint': "Le texte que le skimmer écrit pour un report — ce que SDC est réglé à envoyer (599, 5NN…). On peut en mettre plusieurs, séparés par des virgules ; ajoute le marqueur des reports anciens pour les chasser aussi.", 'flxp.rstChaseOffset': 'off', 'k3.console': 'Console Elecraft', 'k3.waiting': 'En attente de la radio… (règle le CAT sur Elecraft ou Kenwood et connecte)', 'k3.power': 'Puissance', 'k3.volume': 'Volume', 'k3.refreshHint': "Relire les réglages depuis la radio — quand un bouton a été tourné en façade.", 'k3.sMeterHint': 'Cliquer pour utiliser cette lecture comme report envoyé. Valeur brute de la radio : {raw}.', 'k3.tuneHint': "Lancer un cycle d'accord de l'ATU (K3 : appui sur la touche ATU TUNE). La commande exacte envoyée est écrite dans le journal.", 'k3.provisional': "L'échelle des mesures est provisoire : elle n'a pas encore été confirmée sur un vrai K3, et les valeurs brutes sont écrites dans le journal pour qu'elle puisse l'être.", 'flxp.splitHint': 'Split : RX/TX sur des slices séparées. ON crée une slice TX +1 kHz (CW) / +5 kHz (SSB) au-dessus, comme SmartSDR.', 'flxp.sliceHint': 'Cliquer pour rendre cette slice active — fréquence, mode, DSP et clics de spot la suivent tous.', 'flxp.txSlice': 'Cette slice émet', 'flxp.setTxSlice': 'Déplacer le TX sur cette slice (émettre ici)', 'flxp.voxDly': 'Délai VOX', 'flxp.speed': 'Vitesse', 'flxp.pitch': 'Tonalité', 'flxp.delay': 'Délai',
'flxp.smartsdrRemote': 'Contrôle à distance SmartSDR', 'flxp.offline': 'HORS LIGNE', 'flxp.waiting': 'En attente du FlexRadio… (règle le CAT sur FlexRadio et connecte)', 'flxp.transmit': 'Émission', 'flxp.rfPower': 'Puissance RF', 'flxp.tunePwr': 'Puiss. TUNE', 'flxp.rstChaseHint': "Chasser le pile-up : quand le skimmer CW marque un report ({m}) sur le panadapter, déplacer la slice d'ÉMISSION dessus — c'est là que le DX écoutait il y a une seconde. La slice de réception ne bouge jamais. Clic droit pour changer le texte du marqueur.", 'flxp.rstChaseMarkerHint': "Le texte que le skimmer écrit pour un report — ce que SDC est réglé à envoyer (599, 5NN…). On peut en mettre plusieurs, séparés par des virgules ; ajoute le marqueur des reports anciens pour les chasser aussi.", 'flxp.rstChaseOffset': 'off', 'k3.console': 'Console Elecraft', 'k3.waiting': 'En attente de la radio… (règle le CAT sur Elecraft ou Kenwood et connecte)', 'k3.rfGain': 'Gain HF', 'k3.micGain': 'Micro', 'k3.squelch': 'Squelch', 'k3.filter': 'Filtre', 'k3.antenna': 'Antenne', 'k3.clear': 'EFFACER', 'k3.keySpeed': 'Manip', 'k3.power': 'Puissance', 'k3.volume': 'Volume', 'k3.refreshHint': "Relire les réglages depuis la radio — quand un bouton a été tourné en façade.", 'k3.sMeterHint': 'Cliquer pour utiliser cette lecture comme report envoyé. Valeur brute de la radio : {raw}.', 'k3.tuneHint': "Lancer un cycle d'accord de l'ATU (K3 : appui sur la touche ATU TUNE). La commande exacte envoyée est écrite dans le journal.", 'k3.provisional': "L'échelle des mesures est provisoire : elle n'a pas encore été confirmée sur un vrai K3, et les valeurs brutes sont écrites dans le journal pour qu'elle puisse l'être.", 'flxp.splitHint': 'Split : RX/TX sur des slices séparées. ON crée une slice TX +1 kHz (CW) / +5 kHz (SSB) au-dessus, comme SmartSDR.', 'flxp.sliceHint': 'Cliquer pour rendre cette slice active — fréquence, mode, DSP et clics de spot la suivent tous.', 'flxp.txSlice': 'Cette slice émet', 'flxp.setTxSlice': 'Déplacer le TX sur cette slice (émettre ici)', 'flxp.voxDly': 'Délai VOX', 'flxp.speed': 'Vitesse', 'flxp.pitch': 'Tonalité', 'flxp.delay': 'Délai',
'flxp.receiveActive': 'Réception (slice active)', 'flxp.muted': 'Coupé — clic pour rétablir', 'flxp.mute': "Couper l'audio RX", 'flxp.filter': 'Filtre', 'flxp.amplifier': 'Amplificateur', 'flxp.atuTune': 'ACCORD', 'flxp.atuTuneHint': "Lance un cycle d'accord sur le coupleur intégré. La radio émet elle-même une porteuse pour mesurer l'adaptation.", 'flxp.atuBypass': 'BYPASS', 'flxp.atuBypassHint': 'Sort le coupleur de la ligne (passage direct).', 'flxp.atuMem': 'MEM', 'flxp.atuMemHint': "Réutilise l'accord mémorisé pour cette fréquence au lieu de refaire un cycle.", 'flxp.atuIdle': 'non accordé', 'flxp.atuTuning': 'accord en cours…', 'flxp.atuOk': 'accordé', 'flxp.atuFail': 'ÉCHEC ACCORD', 'flxp.atuBypassed': 'contourné', 'flxp.atuAborted': 'interrompu', 'flxp.ampInLine': 'Amplificateur en ligne (émission via le PA).', 'flxp.ampBypassed': 'Amplificateur en bypass (standby).', 'flxp.pgConnected': 'PowerGenius connecté', 'flxp.pgOffline': 'PowerGenius hors ligne', 'flxp.fan': 'Ventilo', 'flxp.fanStandard': 'Standard', 'flxp.fanContest': 'Contest', 'flxp.fanBroadcast': 'Diffusion', 'flxp.fault': 'DÉFAUT', 'flxp.meters': 'Mesures', 'flxp.voltage': 'TENSION', 'flxp.paTemp': 'TEMP PA', 'flxp.txFilter': 'Filtre TX', 'flxp.micProfile': 'Profil micro', 'flxp.noMeters': 'Aucune mesure — en attente du flux UDP de la radio…', 'flxp.amplifierHdr': 'AMPLIFICATEUR', 'flxp.outputPower': 'PUISSANCE DE SORTIE', 'flxp.speOffline': 'SPE hors ligne', 'flxp.acomOffline': 'ACOM hors ligne', 'flxp.ampPick': 'Choisir quel amplificateur cette carte affiche', 'flxp.dspV4Hint': 'DSP SmartSDR v4 (séries 8000/Aurora)', 'flxp.daxHint': "DAX comme source audio d'émission (bouton DAX du bandeau transmit de SmartSDR) — pour WSJT-X & co", 'flxp.rnnHint': 'RNN — réduction de bruit par IA (on/off)', 'flxp.anftHint': 'ANFT — filtre notch automatique FFT (on/off)', 'flxp.dspNoise': 'Bruit', 'flxp.dspMore': 'Afficher/masquer le DSP avancé (WNB, NR/notch v4)',
'icmp.spectrum': 'Spectre', 'icmp.scopeFixed': 'Fixe — double-clic / molette pour accorder', 'icmp.scopeCenter': 'Centré — suit le VFO', 'icmp.scopeOff': 'Scope éteint', 'icmp.scopePanDown': 'Décaler le scope 50 kHz', 'icmp.scopePanUp': 'Décaler le scope +50 kHz', 'icmp.scopeCenterVfo': 'Centrer le scope sur la fréquence actuelle (±50 kHz)', 'icmp.notConnected': 'Icom non connecté. Active le backend CI-V Icom dans Réglages → CAT et connecte le port USB de la radio.', 'icmp.refresh': 'Rafraîchir', 'icmp.meters': 'Mesures', 'icmp.transmit': 'Émission', 'icmp.power': 'Puissance', 'icmp.mic': 'Micro', 'icmp.receive': 'Réception', 'icmp.preamp': 'Préampli', 'icmp.filter': 'Filtre', 'icmp.noiseNotch': 'Bruit / Notch', 'icmp.autoNotch': 'Filtre notch auto', 'icmp.apf': 'Filtre de pic audio (CW)', 'icmp.clarifiers': 'RIT / ΔTX', 'icmp.ritHint': 'Molette ou ± pour décaler · Ctrl+←/→ décale le RIT si actif', 'icmp.bandsAntenna': 'Bandes & Antenne', 'icmp.bandCurrent': 'Le poste est sur {b} m', 'icmp.antenna': 'Antenne', 'icmp.passband': 'Passe-bande / Notch', 'icmp.pbtCenter': 'Centrer PBT', 'icmp.manualNotch': 'Notch manuel — active MN, puis règle la position', 'icmp.squelch': 'Squelch', 'icmp.powerOnHint': 'Allumer la radio (démarre en ~15 s)', 'icmp.powerOffHint': 'Éteindre la radio', 'icmp.powerOffConfirm': 'Éteindre la radio ?',
'rst.clickToFill': 'Clic pour remplir le RST tx depuis le signal',