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
+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.
// 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 {
if (!cur) return false;
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}
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. */}
<Meter label="Po" value={st.power_meter} accent="#ef4444" scale={`${Math.round(st.power_meter * (((st.model ?? '').includes('7760')) ? 2.5 : 1))} W`} />
{(() => {
if ((st.model ?? '').includes('7760')) {
const { w, defl } = icomWatts(st.power_meter);
return <Meter label="Po" value={defl} accent="#ef4444" scale={`${w} 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'} />
</div>
@@ -793,7 +808,11 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r
<Card icon={Mic} title={t('icmp.transmit')} accent="#ef4444">
<Row label={t('icmp.power')}>
<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>
{isPhone && (
<Row label={t('icmp.mic')}>