feat(yaesu): eight-digit rigs — the width comes from the radio

An FTDX3000 rejects every FA command: the FTDX10 family writes a
frequency in nine digits and everything before it — FTDX3000, FTDX5000,
FTDX1200, FT-2000, FT-950, FT-450 — writes eight, answering the longer
form with "?;". The rig would not follow and nothing said why.

The width is LEARNED from the rig's own replies rather than tabulated: it
announces the format in every answer to FA;, so it comes from the radio
in front of the operator instead of from a model list that is always one
release behind — and a Yaesu this backend has never heard of is right on
the first read. Nine until the first reply lands, which is what the
modern rigs use and what this backend was written against.

The five older ID codes are named too, so the console says FTDX3000
rather than a bare number.
This commit is contained in:
2026-09-06 21:11:19 +02:00
parent 23f324e606
commit 3745d23339
3 changed files with 101 additions and 4 deletions
+59 -2
View File
@@ -59,6 +59,13 @@ var yaesuModels = map[string]string{
"0650": "FT-891",
"0670": "FT-DX3000",
"0460": "FT-450D",
// The eight-digit family. Named for the console; the frequency format is
// learned from the rig either way (see learnFreqWidth).
"0251": "FT-2000",
"0310": "FT-950",
"0583": "FTDX1200",
"0462": "FTDX3000",
"0101": "FTDX5000",
}
// yaesuModeToADIF maps the MD digit to an ADIF mode. The DATA and RTTY variants
@@ -100,6 +107,9 @@ type Yaesu struct {
// rxVFOCmd is "FR" when the rig reports its receive VFO that way, else empty
// and VS is used — see ReadState.
rxVFOCmd string
// freqDigits is how many digits this rig writes a frequency in, LEARNED from
// its own replies. See setFreqWidth.
freqDigits int
curFreq int64
curRXFreq int64
@@ -275,6 +285,7 @@ func (y *Yaesu) ReadState() (RigState, error) {
if err != nil {
return RigState{}, err // the rig stopped answering — let the Manager reconnect
}
y.learnFreqWidth(faRaw, "FA")
freqA, ok := parseYaesuFreq(faRaw, "FA")
if !ok {
return RigState{}, fmt.Errorf("yaesu: unparsable FA reply %q", faRaw)
@@ -355,7 +366,7 @@ func (y *Yaesu) SetFrequency(hz int64) error {
if y.curVFO == "B" {
cmd = "FB"
}
return y.write(fmt.Sprintf("%s%09d;", cmd, hz))
return y.write(fmt.Sprintf("%s%0*d;", cmd, y.freqWidth(), hz))
}
func (y *Yaesu) SetMode(mode string) error {
@@ -481,7 +492,53 @@ func cmdPrefix(cmd string) string {
return c
}
// parseYaesuFreq reads "FA014074000;" into Hz.
// EIGHT DIGITS OR NINE — the rig says which, and it is not a matter of taste.
//
// The FTDX10, FT-991A, FT-891 and FT-710 write a frequency in nine digits;
// everything before them — FTDX3000, FTDX5000, FTDX1200, FT-2000, FT-950,
// FT-450 — writes eight, and answers a nine-digit SET with "?;". An operator
// with an FTDX3000 saw exactly that: every FA command rejected, a radio that
// would not follow, and nothing to say why.
//
// The width is LEARNED rather than tabulated: the rig announces it in every
// reply to "FA;", so the answer comes from the radio in front of the operator
// instead of from a list of models that will always be one release behind. Nine
// until the first reply lands, which is what the modern rigs use and what this
// backend was written against.
const yaesuFreqDigitsDefault = 9
func (y *Yaesu) freqWidth() int {
if y.freqDigits >= 8 && y.freqDigits <= 11 {
return y.freqDigits
}
return yaesuFreqDigitsDefault
}
// learnFreqWidth takes the width from a frequency reply. Only a reply that
// parses as a frequency teaches anything — a "?;" or a stray frame says nothing
// about the format, and a width learned from one would be worse than the
// default.
func (y *Yaesu) learnFreqWidth(reply, prefix string) {
r := strings.TrimSpace(reply)
if !strings.HasPrefix(r, prefix) {
return
}
digits := strings.TrimSuffix(strings.TrimPrefix(r, prefix), ";")
if len(digits) < 8 || len(digits) > 11 {
return
}
for _, c := range digits {
if c < '0' || c > '9' {
return
}
}
if y.freqDigits != len(digits) {
debugLog.Printf("yaesu: this rig writes frequencies in %d digits — commands will match", len(digits))
y.freqDigits = len(digits)
}
}
// parseYaesuFreq reads "FA014074000;" (or "FA14074000;") into Hz.
func parseYaesuFreq(reply, prefix string) (int64, bool) {
r := strings.TrimSpace(reply)
if !strings.HasPrefix(r, prefix) {