From ffbbff80d630cdad9bc9af5fb20ef3384785e616 Mon Sep 17 00:00:00 2001 From: rouggy Date: Sat, 29 Aug 2026 15:48:26 +0200 Subject: [PATCH] =?UTF-8?q?feat(icom):=20a=20DATA=20button,=20native=20PSK?= =?UTF-8?q?,=20real=20watts=20=E2=80=94=20and=20the=20audio=20codec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The console gains a DATA mode button (USB-D, what FT8 and friends ride on); the PSK button now maps to the rigs' native PSK mode (0x12) instead of erroring as an unknown mode — rigs without one NAK it, exactly as RS-BA1's button does there. The power meter shows real watts on the IC-7760, whose meter face runs to 250 W where a 100 W rig's percentage was the watts. The clickable S-meter gets a pointer cursor so it looks like what it is. Network audio: rxcodec 0x04 — the experiments on the real 7760 settle the codec table (0x10 = 16-bit stereo, 0x02 = 8-bit mono, both heard as garble), and 0x04 is the 16-bit mono the playback path actually plays. --- changelog.json | 8 ++++++-- frontend/src/components/IcomPanel.tsx | 16 ++++++++++++---- internal/cat/civ/civ.go | 4 ++++ internal/cat/icomnet.go | 12 ++++++------ internal/cat/icomserial.go | 6 ++++++ 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/changelog.json b/changelog.json index 66beaaf..0500fbc 100644 --- a/changelog.json +++ b/changelog.json @@ -8,7 +8,9 @@ "Icom: the IC-7760 joins the model list (CI-V address B2h).", "Icom console: the IC-7760 gets its real attenuator steps — 6/12/18 dB.", "Icom network audio: the RX stream is decoded correctly — mono is requested and the payload offset was confirmed on a real IC-7760, curing the garbled, clicking audio.", - "Icom: switching back from a data mode (USB-D1) to plain USB now sticks — if the rig ignores the data-flag command, it is repeated with the modern one-frame form (0x26). Seen on the IC-7760." + "Icom: switching back from a data mode (USB-D1) to plain USB now sticks — if the rig ignores the data-flag command, it is repeated with the modern one-frame form (0x26). Seen on the IC-7760.", + "Icom console: a DATA mode button (USB-D, what FT8 wants), the PSK button drives the rigs that have a native PSK mode, and the power meter reads in real watts on the IC-7760’s 250 W scale.", + "Icom network audio: the right codec is requested (16-bit mono LPCM), settled by experiment on a real IC-7760." ], "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).", @@ -16,7 +18,9 @@ "Icom : l’IC-7760 rejoint la liste des modèles (adresse CI-V B2h).", "Console Icom : l’IC-7760 reçoit ses vrais crans d’atténuateur — 6/12/18 dB.", "Audio réseau Icom : le flux RX est décodé correctement — le mono est demandé et l’offset du payload a été confirmé sur un vrai IC-7760, ce qui guérit le son inaudible et les clics.", - "Icom : revenir d’un mode data (USB-D1) au USB simple tient désormais — si la radio ignore la commande du drapeau data, elle est répétée sous la forme moderne en une trame (0x26). Constaté sur l’IC-7760." + "Icom : revenir d’un mode data (USB-D1) au USB simple tient désormais — si la radio ignore la commande du drapeau data, elle est répétée sous la forme moderne en une trame (0x26). Constaté sur l’IC-7760.", + "Console Icom : un bouton de mode DATA (USB-D, celui de FT8), le bouton PSK pilote les radios qui ont un vrai mode PSK, et le wattmètre lit en watts réels sur l’échelle 250 W de l’IC-7760.", + "Audio réseau Icom : le bon codec est demandé (LPCM mono 16 bits), déterminé par l’expérience sur un vrai IC-7760." ] }, { diff --git a/frontend/src/components/IcomPanel.tsx b/frontend/src/components/IcomPanel.tsx index 934d66c..583fadc 100644 --- a/frontend/src/components/IcomPanel.tsx +++ b/frontend/src/components/IcomPanel.tsx @@ -79,7 +79,7 @@ function bandsFor(model?: string): Band[] { // Mode buttons for the console (like RS-BA1's row). SetCATMode picks USB/LSB for // SSB by frequency and the rig's data variant for digital modes. -const MODES = ['SSB', 'CW', 'RTTY', 'PSK', 'AM', 'FM']; +const MODES = ['SSB', 'CW', 'RTTY', 'PSK', 'AM', 'FM', 'DATA']; // Attenuator steps are MODEL-dependent even though the CI-V command (0x11) is the // same: the value byte is the dB. The IC-7610 (and 7700/7800/7851) have a 6/12/18 @@ -132,6 +132,10 @@ function fmtVFO(hz?: number): string { function modeMatches(btn: string, cur?: string): boolean { if (!cur) return false; if (btn === 'SSB') return cur === 'SSB' || cur === 'USB' || cur === 'LSB'; + // The backend surfaces USB-D as the operator's digital default (FT8…), or as + // plain DATA — either way it is the DATA button that should light. + if (btn === 'DATA') return ['DATA', 'FT8', 'FT4', 'JS8', 'JT65', 'JT9', 'MFSK', 'OLIVIA'].includes(cur); + if (btn === 'PSK') return cur === 'PSK' || cur === 'PSK31'; return btn === cur; } @@ -246,7 +250,7 @@ function Meter({ label, value, accent, scale, onClick, title }: { label: string; ); if (onClick) { - return ; + return ; } return
{body}
; } @@ -718,7 +722,7 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r {/* Mode selector row (RS-BA1's SSB/CW/RTTY/PSK/AM/FM). */} -
+
{MODES.map((m) => { const on = modeMatches(m, curMode); return ( @@ -739,7 +743,11 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r title={onReportRST ? t('rst.clickToFill') : undefined} onClick={onReportRST ? () => onReportRST(sMeterRST(sp.s, sp.over, st.mode)) : undefined} /> ); })()} - + {/* The meter reads 0-100% of the rig's own scale. On a 100 W rig the + percentage IS the watts; the IC-7760's meter runs to 250 W (the rig + makes 200), so its percentage is worth 2.5 W a point — same face as + the radio's. */} + 0 ? `${(1 + st.swr_meter / 33.3).toFixed(1)}` : '1.0'} />
diff --git a/internal/cat/civ/civ.go b/internal/cat/civ/civ.go index 2f37504..64779b8 100644 --- a/internal/cat/civ/civ.go +++ b/internal/cat/civ/civ.go @@ -131,6 +131,8 @@ const ( ModeFM = 0x05 ModeCWR = 0x07 ModeRTTYR = 0x08 + ModePSK = 0x12 // native PSK (IC-7610/7760/7851 class; older rigs NAK it) + ModePSKR = 0x13 ) // Frame builds a complete CI-V frame (preamble … end) for payload, which is the @@ -306,6 +308,8 @@ func ModeToADIF(m byte, data bool) string { return "CW" case ModeRTTY, ModeRTTYR: return "RTTY" + case ModePSK, ModePSKR: + return "PSK31" case ModeAM: return "AM" case ModeFM: diff --git a/internal/cat/icomnet.go b/internal/cat/icomnet.go index 3d1b552..1397cf5 100644 --- a/internal/cat/icomnet.go +++ b/internal/cat/icomnet.go @@ -849,12 +849,12 @@ func icnConnInfo(seq, innerSeq, tokReq uint16, sentid, rcvdid, token uint32, use copy(b[0x60:0x70], icnPasscode(user)) b[0x70] = rxEnable // rxenable: 1 opens the 50003 RX audio stream, 0 = CI-V only b[0x71] = 0x00 // txenable (Phase 5) - // rxcodec 0x02 = LPCM, ONE channel, 16-bit. 0x10 (two channels) was asked - // for at first, and a real IC-7760 duly sent interleaved stereo — which the - // 16 kHz mono playback path rendered as garble at double speed. Mono is what - // the monitor plays and half the bandwidth; the second channel of an RX - // stream carries nothing a logger wants. - b[0x72] = 0x02 // rxcodec + // rxcodec 0x04 = LPCM, ONE channel, 16-BIT — settled by experiment on a + // real IC-7760, one wrong guess at a time: 0x10 produced 1280-byte payloads + // of interleaved 16-bit stereo, 0x02 produced 320-byte payloads of 8-bit + // mono (samples hugging 0x80). 0x04 sits between them in the codec table + // (as in wfview): 16-bit mono, the format the 16 kHz playback path plays. + b[0x72] = 0x04 // rxcodec b[0x73] = 0x04 // txcodec icnBE.PutUint32(b[0x74:], 16000) icnBE.PutUint32(b[0x78:], 8000) diff --git a/internal/cat/icomserial.go b/internal/cat/icomserial.go index fe2eae4..77699b1 100644 --- a/internal/cat/icomserial.go +++ b/internal/cat/icomserial.go @@ -1494,6 +1494,12 @@ func (b *IcomSerial) modeCode(mode string) (code byte, data bool, err error) { return civ.ModeFM, false, nil case "RTTY", "FSK": return civ.ModeRTTY, false, nil + case "PSK": + // The console button, not the ADIF mode: the rigs that HAVE a native PSK + // mode (7610/7760/7851 class) get it; a 7300 NAKs 0x12 and the button + // stays dead there, exactly as RS-BA1's does. Soundcard PSK31 stays on + // USB-D below, where every rig can do it. + return civ.ModePSK, false, nil case "FT8", "FT4", "PSK31", "MFSK", "JS8", "JT65", "JT9", "OLIVIA", "DATA", "DIGITALVOICE": // Digital data modes ride on USB with the data flag set (FT8 etc.). return civ.ModeUSB, true, nil