fix(icom): power in watts on the 7760, and Listening restarts clean

The power meter applies Icom's own meter calibration (raw 143 = half
deflection, 213 = full scale) anchored against the real radio: a measured
100 W sits at half deflection of the 7760's 250 W face — the linear guess
showed it as 140. The RF power slider says watts on a 200 W rig instead of a
percentage the operator has to convert.

And the Listening button, pressed while a network Icom is connected, restarts
the monitor render-only: it used to open a USB capture from the From-radio
device as well, and that second producer interleaved with the network pushes
chopped the audio to pieces.
This commit is contained in:
2026-08-29 16:02:15 +02:00
parent 6d4a110949
commit b246c4d10a
3 changed files with 41 additions and 8 deletions
+10
View File
@@ -10667,6 +10667,16 @@ func (a *App) AudioStartMonitor() error {
if strings.TrimSpace(cfg.FromRadio) == "" { if strings.TrimSpace(cfg.FromRadio) == "" {
return fmt.Errorf(`no "From radio" capture device set — pick the rig's USB Audio CODEC in Settings → Audio`) return fmt.Errorf(`no "From radio" capture device set — pick the rig's USB Audio CODEC in Settings → Audio`)
} }
// When the rig's audio arrives over the NETWORK (Icom 50003), the monitor
// must be render-only: this button used to start a USB capture from the
// "From radio" device as well, and that second producer — often another
// radio's DAX — interleaved its chunks with the network stream's pushes.
// The result was audio chopped to pieces the moment the operator toggled
// Listening off and on while connected to a network Icom.
if cs, err := a.GetCATSettings(); err == nil && cs.Enabled && cs.Backend == "icom-net" && cs.IcomNetAudio {
applog.Printf("audio: RX monitor start (network sink only → listen=%q)", cfg.ListeningDevice)
return a.audioMgr.StartMonitorSink(cfg.ListeningDevice)
}
applog.Printf("audio: RX monitor start (from=%q → listen=%q)", cfg.FromRadio, cfg.ListeningDevice) applog.Printf("audio: RX monitor start (from=%q → listen=%q)", cfg.FromRadio, cfg.ListeningDevice)
a.audioMgr.SetMonitorGain(cfg.FromGain) a.audioMgr.SetMonitorGain(cfg.FromGain)
return a.audioMgr.StartMonitor(cfg.FromRadio, cfg.ListeningDevice) return a.audioMgr.StartMonitor(cfg.FromRadio, cfg.ListeningDevice)
+6 -2
View File
@@ -11,7 +11,9 @@
"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-7760s 250 W scale.", "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-7760s 250 W scale.",
"Icom network audio: the right codec is requested (16-bit mono LPCM), settled by experiment on a real IC-7760.", "Icom network audio: the right codec is requested (16-bit mono LPCM), settled by experiment on a real IC-7760.",
"CAT settings: reopening the panel now shows the radio that is actually selected — it always showed the first one, with the fields (MY_RIG included) silently editing the wrong entry." "CAT settings: reopening the panel now shows the radio that is actually selected — it always showed the first one, with the fields (MY_RIG included) silently editing the wrong entry.",
"IC-7760: the power meter is calibrated against the radio (100 W reads 100 W on the 250 W face) and the RF power setting reads in watts, not a percentage.",
"Icom network audio: toggling Listening off and on no longer chops the sound — the monitor restarts as network-fed instead of also opening a USB capture."
], ],
"fr": [ "fr": [
"Console Elecraft : le S-mètre est calibré sur un vrai K3 — S9 et les +dB correspondent désormais à laffichage 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 à laffichage de la radio (il lisait environ deux points S trop bas).",
@@ -22,7 +24,9 @@
"Icom : revenir dun 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 lIC-7760.", "Icom : revenir dun 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 lIC-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 lIC-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 lIC-7760.",
"Audio réseau Icom : le bon codec est demandé (LPCM mono 16 bits), déterminé par lexpérience sur un vrai IC-7760.", "Audio réseau Icom : le bon codec est demandé (LPCM mono 16 bits), déterminé par lexpérience sur un vrai IC-7760.",
"Réglages CAT : rouvrir le panneau montre désormais la radio réellement sélectionnée — il montrait toujours la première, et les champs (MY_RIG compris) modifiaient silencieusement la mauvaise entrée." "Réglages CAT : rouvrir le panneau montre désormais la radio réellement sélectionnée — il montrait toujours la première, et les champs (MY_RIG compris) modifiaient silencieusement la mauvaise entrée.",
"IC-7760 : le wattmètre est calibré sur la radio (100 W affiche 100 W sur l’échelle 250 W) et le réglage RF power se lit en watts, plus en pourcentage.",
"Audio réseau Icom : couper puis relancer Listening ne hache plus le son — le moniteur redémarre alimenté par le réseau au lieu douvrir en plus une capture USB."
] ]
}, },
{ {
+25 -6
View File
@@ -129,6 +129,19 @@ function fmtVFO(hz?: number): string {
} }
// modeMatches marks a mode button active, folding the rig's USB/LSB into SSB. // modeMatches marks a mode button active, folding the rig's USB/LSB into SSB.
// icomWatts turns the backend's 0-100 meter percentage back into watts on the
// IC-7760's own meter face. The backend value is linear in the RAW meter byte
// (0-255 → 0-100), but Icom's calibration is not: raw 143 is half deflection
// and raw 213 is full scale. On a real 7760 a measured 100 W sits at half
// deflection of the 250 W face — the linear ×2.5 first tried showed 140 W for
// it. Below half scale watts run 0→100, above it 100→250.
function icomWatts(pct: number): { w: number; defl: number } {
const raw = Math.max(0, pct * 2.55);
const defl = raw <= 143 ? (raw / 143) * 50 : Math.min(100, 50 + ((raw - 143) / 70) * 50);
const w = raw <= 143 ? (raw / 143) * 100 : Math.min(250, 100 + ((raw - 143) / 70) * 150);
return { w: Math.round(w), defl };
}
function modeMatches(btn: string, cur?: string): boolean { function modeMatches(btn: string, cur?: string): boolean {
if (!cur) return false; if (!cur) return false;
if (btn === 'SSB') return cur === 'SSB' || cur === 'USB' || cur === 'LSB'; if (btn === 'SSB') return cur === 'SSB' || cur === 'USB' || cur === 'LSB';
@@ -743,11 +756,13 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r
title={onReportRST ? t('rst.clickToFill') : undefined} title={onReportRST ? t('rst.clickToFill') : undefined}
onClick={onReportRST ? () => onReportRST(sMeterRST(sp.s, sp.over, st.mode)) : 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 if ((st.model ?? '').includes('7760')) {
makes 200), so its percentage is worth 2.5 W a point — same face as const { w, defl } = icomWatts(st.power_meter);
the radio's. */} return <Meter label="Po" value={defl} accent="#ef4444" scale={`${w} W`} />;
<Meter label="Po" value={st.power_meter} accent="#ef4444" scale={`${Math.round(st.power_meter * (((st.model ?? '').includes('7760')) ? 2.5 : 1))} W`} /> }
return <Meter label="Po" value={st.power_meter} accent="#ef4444" scale={`${st.power_meter} W`} />;
})()}
<Meter label="SWR" value={st.swr_meter} accent="#f59e0b" scale={st.swr_meter > 0 ? `${(1 + st.swr_meter / 33.3).toFixed(1)}` : '1.0'} /> <Meter label="SWR" value={st.swr_meter} accent="#f59e0b" scale={st.swr_meter > 0 ? `${(1 + st.swr_meter / 33.3).toFixed(1)}` : '1.0'} />
</div> </div>
@@ -793,7 +808,11 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r
<Card icon={Mic} title={t('icmp.transmit')} accent="#ef4444"> <Card icon={Mic} title={t('icmp.transmit')} accent="#ef4444">
<Row label={t('icmp.power')}> <Row label={t('icmp.power')}>
<Slider value={st.rf_power} accent="#ef4444" onChange={(v) => set({ rf_power: v }, () => IcomSetRFPower(v))} /> <Slider value={st.rf_power} accent="#ef4444" onChange={(v) => set({ rf_power: v }, () => IcomSetRFPower(v))} />
<span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.rf_power}</span> {/* PC is a percentage of the rig's rated power; on a 200 W rig the
operator thinks in watts, so say it in watts there. */}
<span className="w-12 text-right text-xs font-mono tabular-nums text-muted-foreground">
{(st.model ?? '').includes('7760') ? `${st.rf_power * 2} W` : st.rf_power}
</span>
</Row> </Row>
{isPhone && ( {isPhone && (
<Row label={t('icmp.mic')}> <Row label={t('icmp.mic')}>