feat: Yaesu control panel (meters, bands, DSP, TX), and drop a stale Flex hint

A console pane for the native Yaesu backend, in the same shape as the Icom and
Flex ones: S/PO/SWR meters, band and mode rows, AF/RF/squelch, AGC, the IPO/AMP1/
AMP2 front-end selector, ATT, NB, DNR + level, narrow filter, power in watts, mic
gain, VOX, split and ATU tune.

Three decisions worth keeping:

Panel reads are STAGGERED and live in their own file, away from ReadState. Meters
poll every cycle; settings only change when someone turns a knob, so they refresh
every 8th cycle and right after any set. Polling all of it every cycle would put
twenty queries a second on the serial link the frequency display shares.

Band buttons use the rig's own band memory (BS) rather than a frequency we pick,
so 20 m lands where the operator last was on 20 m — what the radio's own band
keys do.

A set is followed by a read-back, so the panel shows what the RIG ended up with,
not what we asked for; the two differ whenever a value is out of range or the
mode forbids the control. And a control the model lacks keeps its previous value
instead of dropping to zero, which reads as a setting that reset itself.

The S9 point of the S-meter scale is a hypothesis (the manual does not state it)
and is commented as such — one number to correct if reports come out an S unit
off, rather than a fudge spread through the RST helper.

Also removes the FlexRadio settings blurb, which explained the backend to
someone who had already chosen it.
This commit is contained in:
2026-07-29 11:17:08 +02:00
parent e2aba828a9
commit 842d4708a7
12 changed files with 1072 additions and 15 deletions
+6 -9
View File
@@ -156,6 +156,7 @@ interface Props {
onMainPaneChanged?: (side: 'left' | 'right', value: string) => void; // live Main-view layout update
flexAvailable?: boolean; // CAT backend is FlexRadio → offer it as a Main pane
icomAvailable?: boolean; // CAT backend is Icom → offer the Icom console as a Main pane
yaesuAvailable?: boolean; // CAT backend is Yaesu → offer the Yaesu console as a Main pane
}
// Pretty little card showing what OpsLog will stamp on each QSO based on
@@ -817,7 +818,7 @@ function RelayAutoPanel() {
// cluster grid or the worked-before grid. Per-profile (stored via SetUIPref,
// which is profile-prefixed). Self-contained so it owns its async-loaded state.
const MAIN_PANE_VALUES = ['map1', 'map2', 'cluster', 'worked', 'recent', 'netcontrol'];
function MainViewPanes({ onChanged, flexAvailable, icomAvailable }: { onChanged?: (side: 'left' | 'right', value: string) => void; flexAvailable?: boolean; icomAvailable?: boolean }) {
function MainViewPanes({ onChanged, flexAvailable, icomAvailable, yaesuAvailable }: { onChanged?: (side: 'left' | 'right', value: string) => void; flexAvailable?: boolean; icomAvailable?: boolean; yaesuAvailable?: boolean }) {
const { t } = useI18n();
const [left, setLeft] = useState('map1');
const [right, setRight] = useState('map2');
@@ -826,10 +827,11 @@ function MainViewPanes({ onChanged, flexAvailable, icomAvailable }: { onChanged?
...MAIN_PANE_VALUES,
...(flexAvailable ? ['flex'] : []),
...(icomAvailable ? ['icom'] : []),
...(yaesuAvailable ? ['yaesu'] : []),
].map((value) => ({ value, label: t(`settings.pane.${value}`) }))
.sort((a, b) => a.label.localeCompare(b.label));
useEffect(() => {
const valid = (v: string) => v === 'flex' || v === 'icom' || MAIN_PANE_VALUES.includes(v);
const valid = (v: string) => v === 'flex' || v === 'icom' || v === 'yaesu' || MAIN_PANE_VALUES.includes(v);
Promise.all([GetUIPref('mainPaneLeft').catch(() => ''), GetUIPref('mainPaneRight').catch(() => '')])
.then(([l, r]) => { if (valid(l)) setLeft(l); if (valid(r)) setRight(r); });
}, []);
@@ -1024,7 +1026,7 @@ const ICOM_MODELS: { name: string; addr: number }[] = [
{ name: 'IC-9700', addr: 0xA2 },
];
export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChanged, flexAvailable, icomAvailable }: Props) {
export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChanged, flexAvailable, icomAvailable, yaesuAvailable }: Props) {
const { t } = useI18n();
const [selected, setSelected] = useState<SectionId>((initialSection as SectionId) || 'station');
const [loading, setLoading] = useState(true);
@@ -2664,11 +2666,6 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</p>
</>
)}
{catCfg.backend === 'flex' && (
<p className="text-xs text-muted-foreground">
{t('cat.flexHint')}
</p>
)}
</div>
</>
);
@@ -5133,7 +5130,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
</label>
<TelemetryToggle />
<MainViewPanes onChanged={onMainPaneChanged} flexAvailable={flexAvailable} icomAvailable={icomAvailable} />
<MainViewPanes onChanged={onMainPaneChanged} flexAvailable={flexAvailable} icomAvailable={icomAvailable} yaesuAvailable={yaesuAvailable} />
<div className="border-t border-border/60 pt-4 space-y-2">
<h4 className="text-sm font-semibold text-foreground">{t('gen.pwEnc')}</h4>