feat: Yaesu panel — sideband on re-click, split readout, one-touch up 1/up 5
Three corrections from the operator's second pass. The sideband gesture was wrong: I used double-click, which hides the action. Clicking a button that is ALREADY active now flips its sideband — CW-U → CW-L → CW-U. One button, one finger, nothing to discover. A lit SPLIT chip does not tell an operator anything useful: it says split is on, not where they transmit. The header now shows the TX frequency and the offset in kHz whenever split is active. And the offset that matters is set in one action: up 1 kHz on CW, up 5 kHz on phone. Doing it by hand means swapping VFOs, retuning and swapping back — exactly the fumbling a panel exists to remove. Both are offered rather than picked from the mode, because which one is idiomatic is the operator's call, and the button turns split on at the same time. The offset is measured from the RECEIVE frequency and written to the VFO we are not listening on, so it stays correct when the operator works on VFO B, where the roles are mirrored.
This commit is contained in:
+12
-3
@@ -93,8 +93,9 @@ type Yaesu struct {
|
||||
// off rather than invented.
|
||||
splitCmd string
|
||||
|
||||
curFreq int64
|
||||
curVFO string // "A" or "B"
|
||||
curFreq int64
|
||||
curRXFreq int64
|
||||
curVFO string // "A" or "B"
|
||||
|
||||
// Control-panel state and its slow-beat counter — see yaesu_panel.go.
|
||||
panel YaesuTXState
|
||||
@@ -210,6 +211,12 @@ func (y *Yaesu) ReadState() (RigState, error) {
|
||||
s.Vfo = vfo
|
||||
s.FreqHz, s.RxFreqHz, s.Split = resolveYaesuVFOs(freqA, freqB, vfo, split)
|
||||
y.curFreq = s.FreqHz
|
||||
// The frequency being LISTENED to, which is what a split offset is measured
|
||||
// from — under split that is RxFreqHz, not FreqHz.
|
||||
y.curRXFreq = s.FreqHz
|
||||
if s.Split && s.RxFreqHz > 0 {
|
||||
y.curRXFreq = s.RxFreqHz
|
||||
}
|
||||
|
||||
if r, err := y.ask("MD0;"); err == nil && len(r) >= 4 {
|
||||
// Keep the RAW mode too: ADIF folds CW-U/CW-L and DATA-U/DATA-L together,
|
||||
@@ -224,7 +231,9 @@ func (y *Yaesu) ReadState() (RigState, error) {
|
||||
debugLog.Printf("yaesu: unknown mode reply %q", r)
|
||||
}
|
||||
}
|
||||
y.readPanel(s.Mode, s.Split)
|
||||
// s.FreqHz is the TX frequency by the ADIF convention, so it IS the split
|
||||
// transmit frequency when split is on.
|
||||
y.readPanel(s.Mode, s.Split, s.FreqHz)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,13 @@ type YaesuTXState struct {
|
||||
|
||||
Transmitting bool `json:"transmitting"`
|
||||
Split bool `json:"split"`
|
||||
SMeter int `json:"s_meter"` // 0-100 (raw 0-255)
|
||||
PowerMeter int `json:"power_meter"` // 0-100, TX only
|
||||
SWRMeter int `json:"swr_meter"` // 0-100, TX only
|
||||
// SplitTXHz is where the rig will TRANSMIT while split — the panel showed a
|
||||
// lit SPLIT button and nothing else, which does not tell an operator whether
|
||||
// they are up 1 or up 5.
|
||||
SplitTXHz int64 `json:"split_tx_hz"`
|
||||
SMeter int `json:"s_meter"` // 0-100 (raw 0-255)
|
||||
PowerMeter int `json:"power_meter"` // 0-100, TX only
|
||||
SWRMeter int `json:"swr_meter"` // 0-100, TX only
|
||||
|
||||
RFPower int `json:"rf_power"` // watts
|
||||
MicGain int `json:"mic_gain"` // 0-100
|
||||
@@ -72,6 +76,7 @@ type YaesuController interface {
|
||||
SetYaesuNarrow(bool) error
|
||||
SetYaesuVOX(bool) error
|
||||
SetYaesuSplit(bool) error
|
||||
SetYaesuSplitOffset(int64) error
|
||||
SetYaesuBand(string) error
|
||||
SetYaesuModeRaw(string) error
|
||||
TuneYaesuATU() error
|
||||
@@ -88,9 +93,13 @@ func (y *Yaesu) YaesuState() YaesuTXState {
|
||||
|
||||
// readPanel refreshes the meters, and the settings on the slower beat. Called
|
||||
// from ReadState with the mutex HELD, so it shares the same serialised link.
|
||||
func (y *Yaesu) readPanel(mode string, split bool) {
|
||||
func (y *Yaesu) readPanel(mode string, split bool, txHz int64) {
|
||||
y.panel.Mode = mode
|
||||
y.panel.Split = split
|
||||
y.panel.SplitTXHz = 0
|
||||
if split {
|
||||
y.panel.SplitTXHz = txHz
|
||||
}
|
||||
|
||||
// TX state first: which meters mean anything depends on it, and a power
|
||||
// reading shown while receiving is how a panel lies.
|
||||
@@ -485,3 +494,47 @@ func yaesuRawModeName(d byte) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetYaesuSplitOffset puts the transmit VFO a fixed distance above the receive
|
||||
// one and turns split on, in a single action.
|
||||
//
|
||||
// This is the split an operator actually uses when working a pile-up: listen on
|
||||
// the DX, transmit up 5 kHz on phone or up 1 kHz on CW. Doing it by hand means
|
||||
// swapping VFOs, retuning and swapping back, which is exactly the fumbling a
|
||||
// panel should remove.
|
||||
//
|
||||
// The offset is applied to the RECEIVE frequency and written to the OTHER VFO —
|
||||
// whichever that is. On VFO B the roles are mirrored, so listening on B writes A.
|
||||
func (y *Yaesu) SetYaesuSplitOffset(offsetHz int64) error {
|
||||
y.mu.Lock()
|
||||
defer y.mu.Unlock()
|
||||
if y.port == nil {
|
||||
return fmt.Errorf("yaesu: not connected")
|
||||
}
|
||||
rx := y.curRXFreq
|
||||
if rx <= 0 {
|
||||
return fmt.Errorf("yaesu: no receive frequency read yet")
|
||||
}
|
||||
tx := rx + offsetHz
|
||||
if tx <= 0 || tx > 999_999_999 {
|
||||
return fmt.Errorf("yaesu: split frequency %d out of the CAT range", tx)
|
||||
}
|
||||
// Write the VFO we are NOT listening on.
|
||||
cmd := "FB"
|
||||
if y.curVFO == "B" {
|
||||
cmd = "FA"
|
||||
}
|
||||
if err := y.write(fmt.Sprintf("%s%09d;", cmd, tx)); err != nil {
|
||||
return err
|
||||
}
|
||||
if y.splitCmd == "" {
|
||||
return fmt.Errorf("yaesu: this rig answered neither ST; nor FT; — split cannot be set")
|
||||
}
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
if err := y.write(fmt.Sprintf("%s1;", y.splitCmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
y.panel.Split = true
|
||||
y.panel.SplitTXHz = tx
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user