feat: Flexradio/Icom, click on signal to fill the RST tx
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
} from '../../wailsjs/go/main/App';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { sMeterRST } from '@/lib/rst';
|
||||
|
||||
type FlexState = {
|
||||
available: boolean; model?: string;
|
||||
@@ -140,15 +141,17 @@ function LevelRow({ label, on, onToggle, value, onLevel, disabled, accent, slide
|
||||
// `display` overrides the numeric readout; `segColor` colours segments by their
|
||||
// 0..1 position (zones); the top ~18% light red by default (overload/peak).
|
||||
const METER_SEGMENTS = 26;
|
||||
function MeterBar({ label, value, unit, lo, hi, accent = '#16a34a', extra, display, segColor }: {
|
||||
function MeterBar({ label, value, unit, lo, hi, accent = '#16a34a', extra, display, segColor, onClick, title }: {
|
||||
label: string; value: number; unit?: string; lo: number; hi: number; accent?: string; extra?: string; display?: string;
|
||||
segColor?: (frac: number) => string;
|
||||
segColor?: (frac: number) => string; onClick?: () => void; title?: string;
|
||||
}) {
|
||||
const span = hi - lo;
|
||||
const pct = span > 0 ? Math.max(0, Math.min(100, ((value - lo) / span) * 100)) : 0;
|
||||
const lit = Math.round((pct / 100) * METER_SEGMENTS);
|
||||
return (
|
||||
<div className="rounded-lg border border-border/70 px-2.5 py-2 bg-gradient-to-b from-card to-muted/40 shadow-sm min-w-0">
|
||||
<div onClick={onClick} title={title}
|
||||
className={cn('rounded-lg border border-border/70 px-2.5 py-2 bg-gradient-to-b from-card to-muted/40 shadow-sm min-w-0',
|
||||
onClick && 'cursor-pointer hover:border-primary/60 hover:from-muted/40')}>
|
||||
<div className="flex items-baseline justify-between gap-1 mb-1.5">
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground truncate">{label}</span>
|
||||
<span className="text-sm font-mono font-bold tabular-nums whitespace-nowrap text-foreground/90">
|
||||
@@ -190,7 +193,7 @@ function Card({ icon: Icon, title, accent, children }: { icon: any; title: strin
|
||||
|
||||
// onCWSpeed (optional): notified when the operator changes CW speed here, so the
|
||||
// host can keep the WinKeyer (which actually sends the macros) in sync.
|
||||
export function FlexPanel({ onCWSpeed }: { onCWSpeed?: (wpm: number) => void } = {}) {
|
||||
export function FlexPanel({ onCWSpeed, onReportRST }: { onCWSpeed?: (wpm: number) => void; onReportRST?: (rst: string) => void } = {}) {
|
||||
const { t } = useI18n();
|
||||
const [st, setSt] = useState<FlexState>(ZERO);
|
||||
const hold = useRef<Record<string, number>>({});
|
||||
@@ -547,17 +550,19 @@ export function FlexPanel({ onCWSpeed }: { onCWSpeed?: (wpm: number) => void } =
|
||||
: /volt/i.test(m.unit || '') ? '#2563eb' : '#16a34a';
|
||||
// S-meter: dBm → S-units (S9 = -73 dBm on HF, 6 dB per unit).
|
||||
const sUnit = (dbm: number) => {
|
||||
const s = (dbm + 127) / 6; // S0 = -127 dBm
|
||||
if (s >= 9) {
|
||||
const over = Math.round(dbm + 73); // dB over S9
|
||||
return { display: over > 0 ? `S9+${over}` : 'S9', bar: s };
|
||||
const sv = (dbm + 127) / 6; // S0 = -127 dBm
|
||||
if (sv >= 9) {
|
||||
const over = Math.max(0, Math.round(dbm + 73)); // dB over S9
|
||||
return { display: over > 0 ? `S9+${over}` : 'S9', bar: sv, s: 9, over };
|
||||
}
|
||||
return { display: `S${Math.max(0, Math.round(s))}`, bar: Math.max(0, s) };
|
||||
return { display: `S${Math.max(0, Math.round(sv))}`, bar: Math.max(0, sv), s: Math.max(0, sv), over: 0 };
|
||||
};
|
||||
const cur = [
|
||||
sig && (() => { const dbm = peakHold('s', sig.value); const s = sUnit(dbm); return (
|
||||
<MeterBar key="s" label="S-METER" value={s.bar} lo={0} hi={19} accent="#16a34a" display={s.display} extra={`${dbm.toFixed(1)} dBm`}
|
||||
segColor={(fr) => { const sv = fr * 19; return sv < 9 ? '#16a34a' : sv < 12.33 ? '#f59e0b' : '#dc2626'; }} />
|
||||
title={onReportRST ? t('rst.clickToFill') : undefined}
|
||||
onClick={onReportRST ? () => onReportRST(sMeterRST(s.s, s.over, st.mode)) : undefined}
|
||||
segColor={(fr) => { const sval = fr * 19; return sval < 9 ? '#16a34a' : sval < 12.33 ? '#f59e0b' : '#dc2626'; }} />
|
||||
); })(),
|
||||
fwd && (() => { const w = peakHold('p', isDbm(fwd) ? dbmToW(fwd.value) : fwd.value); return (
|
||||
<MeterBar key="p" label="PWR" unit="W" lo={0} hi={120} accent="#dc2626"
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '../../wailsjs/go/main/App';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { sMeterRST } from '@/lib/rst';
|
||||
|
||||
type IcomState = {
|
||||
available: boolean; model?: string; mode?: string;
|
||||
@@ -126,26 +127,34 @@ function Row({ label, children }: { label: string; children: React.ReactNode })
|
||||
}
|
||||
|
||||
// Meter — a thin horizontal bar for a live 0-100 reading (S / Po / SWR).
|
||||
function Meter({ label, value, accent, scale }: { label: string; value: number; accent: string; scale?: string }) {
|
||||
// Optional onClick makes the row a button (used to send the S reading to RST tx).
|
||||
function Meter({ label, value, accent, scale, onClick, title }: { label: string; value: number; accent: string; scale?: string; onClick?: () => void; title?: string }) {
|
||||
const v = Math.max(0, Math.min(100, value));
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
const body = (
|
||||
<>
|
||||
<span className="w-9 shrink-0 text-[11px] font-bold uppercase tracking-wider text-muted-foreground">{label}</span>
|
||||
<div className="flex-1 h-2.5 rounded-full bg-muted/60 overflow-hidden">
|
||||
<div className="h-full rounded-full transition-[width] duration-150" style={{ width: `${v}%`, background: accent }} />
|
||||
</div>
|
||||
<span className="w-10 text-right text-[11px] font-mono tabular-nums text-muted-foreground">{scale ?? v}</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
if (onClick) {
|
||||
return <button type="button" onClick={onClick} title={title} className="flex items-center gap-2 w-full rounded hover:bg-muted/50 -mx-1 px-1 py-0.5">{body}</button>;
|
||||
}
|
||||
return <div className="flex items-center gap-2">{body}</div>;
|
||||
}
|
||||
|
||||
// S-meter raw 0-100 → S-unit label (S9 ≈ 47% on the CI-V 0-255 scale; above = +dB).
|
||||
function sUnit(v: number): 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.
|
||||
function sParts(v: number): { s: number; over: number; label: string } {
|
||||
if (v >= 47) {
|
||||
const over = Math.round((v - 47) / 8.8) * 10;
|
||||
return over > 0 ? `S9+${over}` : 'S9';
|
||||
const over = Math.max(0, Math.round((v - 47) * 60 / 47));
|
||||
return { s: 9, over, label: over > 0 ? `S9+${over}` : 'S9' };
|
||||
}
|
||||
return `S${Math.max(0, Math.min(9, Math.round(v / 5.2)))}`;
|
||||
const s = Math.max(0, Math.min(9, Math.round(v / 5.2)));
|
||||
return { s, over: 0, label: `S${s}` };
|
||||
}
|
||||
|
||||
// ScopePanadapter — enables the rig's spectrum-scope stream and draws the
|
||||
@@ -315,7 +324,7 @@ function ScopePanadapter() {
|
||||
// Unlike the Flex (which pushes state), the Icom is polled: meters/TX state are
|
||||
// read every cache cycle; DSP set-controls are optimistic and reconcile on the
|
||||
// next poll. Front-panel knob changes for DSP show after ↻ Refresh.
|
||||
export function IcomPanel() {
|
||||
export function IcomPanel({ onReportRST }: { onReportRST?: (rst: string) => void } = {}) {
|
||||
const { t } = useI18n();
|
||||
const [st, setSt] = useState<IcomState>(ZERO);
|
||||
const [busy, setBusy] = useState(false);
|
||||
@@ -397,9 +406,11 @@ export function IcomPanel() {
|
||||
<Meter label="Po" value={st.power_meter} accent="#ef4444" scale={`${st.power_meter}%`} />
|
||||
<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="S" value={st.s_meter} accent="#22c55e" scale={sUnit(st.s_meter)} />
|
||||
)}
|
||||
) : (() => { const sp = sParts(st.s_meter); return (
|
||||
<Meter label="S" value={st.s_meter} accent="#22c55e" scale={sp.label}
|
||||
title={onReportRST ? t('rst.clickToFill') : undefined}
|
||||
onClick={onReportRST ? () => onReportRST(sMeterRST(sp.s, sp.over, st.mode)) : undefined} />
|
||||
); })()}
|
||||
</Card>
|
||||
|
||||
{/* Transmit controls. */}
|
||||
|
||||
Reference in New Issue
Block a user