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
+24
View File
@@ -845,3 +845,27 @@ func BandFromHz(hz int64) string {
}
return ""
}
// YaesuState returns the panel snapshot, or (zero, false) when the active
// backend isn't a Yaesu — same shape as IcomState.
func (m *Manager) YaesuState() (YaesuTXState, bool) {
m.mu.RLock()
b := m.backend
m.mu.RUnlock()
if yc, ok := b.(YaesuController); ok {
return yc.YaesuState(), true
}
return YaesuTXState{}, false
}
// YaesuDo dispatches a Yaesu control onto the CAT goroutine, so a panel click
// and the poll loop never share the serial port at the same instant.
func (m *Manager) YaesuDo(fn func(YaesuController) error) error {
return m.exec(func(b Backend) error {
yc, ok := b.(YaesuController)
if !ok {
return fmt.Errorf("active CAT backend is not a Yaesu")
}
return fn(yc)
})
}