fix(tci): the drive commands need the TRX index, and the console holds its own clicks
Two faults, reported from a real SunSDR. DRIVE AND TUNE DRIVE DID NOTHING, and neither did MUTE. Those commands carry the transceiver index — 'drive:0,15;', not 'drive:15;' — and sent without it the radio ignores them silently: no error, no answer, the power unchanged. The rule was in the radio's own reports all along, which is where it should have been read from: it announces 'drive:0,85' and 'mute:0,false' at connect, while 'mic_level:100' and 'volume:-12' come with no index at all. Sending the shape the radio speaks in is the whole rule, and it is now written down next to the two exceptions. AGC LOOKED STUCK ON SLOW. The panel showed only what the radio reported back, on the principle that the radio is the truth — but ExpertSDR3 does not echo every setting it accepts, so a working button sat unlit. Changes are shown at once and held for a moment now; whatever the radio announces afterwards still wins, so a clamped or refused setting stays honest without every working one looking broken. Also from the same report, and fair: the consoles did not resemble each other. The Icom panel's RIT control is now a shared component both use — chip, signed offset, ± keys, wheel, and TYPING a value straight in, which is the thing a row of ±10/±100 buttons cannot do. Ctrl+←/→ shifts the RIT here as it does there. And LONG is gone from the AGC row: the protocol takes it, but it is a hang time nobody reaches for between overs.
This commit is contained in:
@@ -14,6 +14,8 @@ import {
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { sMeterRST } from '@/lib/rst';
|
||||
import { MeterBar } from '@/components/MeterBar';
|
||||
import { ShiftRow } from '@/components/ShiftRow';
|
||||
|
||||
type IcomState = {
|
||||
available: boolean; model?: string; mode?: string;
|
||||
@@ -248,39 +250,6 @@ function Meter({ label, value, accent, scale, onClick, title }: { label: string;
|
||||
return <div className="flex items-center gap-2">{body}</div>;
|
||||
}
|
||||
|
||||
// ShiftRow — a RIT / ΔTX offset control: on/off chip + a wheel-adjustable signed
|
||||
// offset (±10 Hz per notch or per ± button) + a clear (0) button.
|
||||
function ShiftRow({ label, on, hz, accent, onToggle, onDelta, onClear }: {
|
||||
label: string; on: boolean; hz: number; accent: string;
|
||||
onToggle: () => void; onDelta: (d: number) => void; onClear: () => void;
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const cb = useRef(onDelta); cb.current = onDelta;
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
const onWheel = (e: WheelEvent) => { e.preventDefault(); cb.current(e.deltaY < 0 ? 10 : -10); };
|
||||
el.addEventListener('wheel', onWheel, { passive: false });
|
||||
return () => el.removeEventListener('wheel', onWheel);
|
||||
}, []);
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Chip on={on} onClick={onToggle} label={label} />
|
||||
<div ref={ref} title="Wheel / ± to shift"
|
||||
className={cn('flex-1 flex items-center justify-between rounded-md border px-1 py-0.5 select-none cursor-ns-resize',
|
||||
on ? 'border-border bg-muted/40' : 'border-border/60 bg-muted/20 opacity-60')}>
|
||||
<button type="button" onClick={() => onDelta(-10)} className="px-2 text-sm font-bold text-muted-foreground hover:text-foreground">−</button>
|
||||
<span className="text-sm font-mono font-bold tabular-nums" style={{ color: on ? accent : undefined }}>
|
||||
{hz > 0 ? '+' : hz < 0 ? '−' : ''}{Math.abs(hz)} Hz
|
||||
</span>
|
||||
<button type="button" onClick={() => onDelta(10)} className="px-2 text-sm font-bold text-muted-foreground hover:text-foreground">+</button>
|
||||
</div>
|
||||
<button type="button" onClick={onClear}
|
||||
className="w-8 shrink-0 py-1 rounded-md text-[11px] font-bold border border-border bg-card text-muted-foreground hover:bg-muted">0</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -797,10 +766,10 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r
|
||||
<Card icon={SlidersHorizontal} title={t('icmp.clarifiers')} accent="#8b5cf6">
|
||||
<ShiftRow label="RIT" accent="#8b5cf6" on={st.rit_on} hz={st.rit_hz}
|
||||
onToggle={() => set({ rit_on: !st.rit_on }, () => IcomSetRITOn(!st.rit_on))}
|
||||
onDelta={(d) => setRit(st.rit_hz + d)} onClear={() => setRit(0)} />
|
||||
onSet={setRit} />
|
||||
<ShiftRow label="ΔTX" accent="#f59e0b" on={st.xit_on} hz={st.rit_hz}
|
||||
onToggle={() => set({ xit_on: !st.xit_on }, () => IcomSetXITOn(!st.xit_on))}
|
||||
onDelta={(d) => setRit(st.rit_hz + d)} onClear={() => setRit(0)} />
|
||||
onSet={setRit} />
|
||||
<p className="text-[11px] text-muted-foreground">{t('icmp.ritHint')}</p>
|
||||
</Card>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user