diff --git a/changelog.json b/changelog.json index edc8bb6..f6ae2f3 100644 --- a/changelog.json +++ b/changelog.json @@ -33,7 +33,8 @@ "Icom console: the spectrum scope is removed. Every model streams its waveform differently — and on the IC-7760 enabling it killed the whole CI-V link, audio included. The radio’s own scope does it better.", "Icom: any leftover waveform stream is switched off at connect — the scope flag lives in the radio and survived sessions, and its flood is what was killing the IC-7760’s CI-V link.", "Icom console: an ATU chip beside SPLIT engages or DISENGAGES the internal tuner — TUNE started a cycle but could never take the tuner back out of line.", - "Icom console: the TX meters get needle inertia — power holds its peak instead of flickering to 0 W between syllables, SWR shows the real peak then returns to the live value — and the wattage readout no longer wraps at three digits." + "Icom console: the TX meters get needle inertia — power holds its peak instead of flickering to 0 W between syllables, SWR shows the real peak then returns to the live value — and the wattage readout no longer wraps at three digits.", + "Icom console: the meters use the same LED bars as the Flex and Elecraft consoles, and the mode badge says USB or LSB instead of an ambiguous SSB." ], "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).", @@ -66,7 +67,8 @@ "Console Icom : le scope spectral est retiré. Chaque modèle streame sa forme d’onde différemment — et sur l’IC-7760 son activation tuait tout le lien CI-V, audio compris. Le scope de la radio fait ça mieux.", "Icom : tout flux waveform résiduel est coupé à la connexion — le drapeau scope vit dans la radio et survivait aux sessions, et son flot est ce qui tuait le lien CI-V de l’IC-7760.", "Console Icom : une puce ATU à côté de SPLIT engage ou DÉSENGAGE le tuner interne — TUNE lançait un cycle mais ne pouvait jamais remettre le tuner hors ligne.", - "Console Icom : les mètres TX gagnent une inertie d’aiguille — la puissance tient sa crête au lieu de retomber à 0 W entre les syllabes, le ROS montre la vraie crête puis revient à la valeur vive — et l’affichage en watts ne passe plus à la ligne à trois chiffres." + "Console Icom : les mètres TX gagnent une inertie d’aiguille — la puissance tient sa crête au lieu de retomber à 0 W entre les syllabes, le ROS montre la vraie crête puis revient à la valeur vive — et l’affichage en watts ne passe plus à la ligne à trois chiffres.", + "Console Icom : les mètres utilisent les mêmes barres LED que les consoles Flex et Elecraft, et le badge de mode dit USB ou LSB au lieu d’un SSB ambigu." ] }, { diff --git a/frontend/src/components/IcomPanel.tsx b/frontend/src/components/IcomPanel.tsx index d824616..797dc6c 100644 --- a/frontend/src/components/IcomPanel.tsx +++ b/frontend/src/components/IcomPanel.tsx @@ -284,6 +284,13 @@ function Meter({ label, value, accent, scale, onClick, title }: { label: string; // sParts turns the raw 0-100 S-meter into S-unit + dB-over-S9 (S9 ≈ 47% on the // CI-V 0-255 scale, +60 dB near full scale). Used for both the display label and // the RST-tx value on click. +// Green to S9, amber through +20, red above — the Elecraft console's scale. +function sSegColor(frac: number) { + if (frac > 0.78) return '#dc2626'; + if (frac > 0.55) return '#f59e0b'; + return '#16a34a'; +} + function sParts(v: number): { s: number; over: number; label: string } { if (v >= 47) { const over = Math.max(0, Math.round((v - 47) * 60 / 47)); @@ -420,7 +427,9 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r // in split the CAT state's TX freq is the authority, otherwise the panel's // own sub_hz read. const subHz: number = split ? (cat?.freq_hz || 0) : (st.sub_hz || 0); - const curMode: string = cat?.mode || st.mode || ''; + // The panel's own mode first: it carries the sideband (USB/LSB) where the + // CAT state folds both into ADIF's SSB. + const curMode: string = st.mode || cat?.mode || ''; // Mode-dependent controls: VOX / speech-comp / mic are voice-only (hidden on // CW and data); APF (audio peak filter) is CW-only. Fold USB/LSB into phone. const um = curMode.toUpperCase(); @@ -512,21 +521,26 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r - {/* Live meters — always visible: S (RX, click → RST), Po in watts, SWR. */} -
+ {/* Live meters — the SAME LED MeterBar every other console uses (Flex, + Elecraft, the amp cards): one instrument look across the app, per the + operator's "les consoles doivent se ressembler". S is clickable → RST. */} +
{(() => { const sp = sParts(st.s_meter); return ( - onReportRST(sMeterRST(sp.s, sp.over, st.mode)) : undefined} /> + onClick={onReportRST ? () => { if (!st.transmitting) onReportRST(sMeterRST(sp.s, sp.over, st.mode)); } : undefined} /> ); })()} {(() => { if ((st.model ?? '').includes('7760')) { const { w, defl } = icomWatts(st.power_meter); - return ; + return ; } - return ; + return ; })()} - 0 ? `${(1 + st.swr_meter / 33.3).toFixed(1)}` : '1.0'} /> + 0 ? 1 + st.swr_meter / 33.3 : 0} lo={1} hi={4} accent="#f59e0b" + display={st.swr_meter > 0 ? (1 + st.swr_meter / 33.3).toFixed(1) : '—'} />
diff --git a/internal/cat/icomserial.go b/internal/cat/icomserial.go index b08f71d..e2500cb 100644 --- a/internal/cat/icomserial.go +++ b/internal/cat/icomserial.go @@ -422,6 +422,19 @@ func (b *IcomSerial) ReadState() (RigState, error) { } b.dspMu.Lock() b.dsp.Mode = s.Mode + // The console says which SIDEBAND, not the ADIF family: "SSB" on 40 m + // leaves the operator guessing whether the rig is where convention puts + // it. The log keeps SSB; the panel shows what the radio is actually doing. + switch b.curModeByte { + case civ.ModeUSB: + if s.Mode == "SSB" { + b.dsp.Mode = "USB" + } + case civ.ModeLSB: + if s.Mode == "SSB" { + b.dsp.Mode = "LSB" + } + } b.dspMu.Unlock() }