No single Kenwood mode digit fits every rig for a data mode: a K3/K4 wants DATA (MD6), a TS-590SG/TS-990S data mode is a USB modifier set on the rig, and MD6 on a plain Kenwood is FSK/RTTY. So the operator now chooses in Settings → CAT: USB (default), DATA (MD6), or leave the rig's mode unchanged. Only soundcard/data modes (FT8, PSK, JT…) follow this; RTTY/FSK stays its own native mode, and voice/CW are untouched (isKenwoodDataMode + test).
149 lines
5.2 KiB
Go
149 lines
5.2 KiB
Go
package cat
|
|
|
|
import "testing"
|
|
|
|
// The IF status frame, read by POSITION.
|
|
//
|
|
// One frame carries frequency, TX state, mode, VFO and split, which is why it is
|
|
// the poll. Every field is a fixed offset, so a length check comes first: a
|
|
// truncated read would otherwise index into the wrong characters and yield a
|
|
// plausible-looking wrong frequency — the worst kind, because it reaches the log.
|
|
//
|
|
// The layout is the one internal/catemu already EMITS to satisfy an ACOM
|
|
// amplifier, so these two halves of the repository agree by construction.
|
|
func TestParseKenwoodIF(t *testing.T) {
|
|
// IF | freq(11) | step(4) | RIT(±5) | 3 | mem(2) | rx/tx | mode | VFO | scan |
|
|
// split | tone | tone#(2) | shift | ; → 38 characters
|
|
const rx20mUSB = "IF00014250000" + "0000" + "+00000" + "000" + "00" + "0" + "2" + "0" + "0" + "0" + "0" + "00" + "0" + ";"
|
|
if len(rx20mUSB) != 38 {
|
|
t.Fatalf("the test's own frame is %d chars, not 38 — fix the fixture first", len(rx20mUSB))
|
|
}
|
|
|
|
f, ok := parseKenwoodIF(rx20mUSB)
|
|
if !ok {
|
|
t.Fatalf("a valid frame was rejected: %q", rx20mUSB)
|
|
}
|
|
if f.FreqHz != 14250000 {
|
|
t.Errorf("frequency = %d, want 14250000", f.FreqHz)
|
|
}
|
|
if f.Mode != '2' {
|
|
t.Errorf("mode = %q, want '2' (USB)", f.Mode)
|
|
}
|
|
if f.VFO != "A" || f.Split || f.TX {
|
|
t.Errorf("got VFO=%s split=%v tx=%v — want A, simplex, receiving", f.VFO, f.Split, f.TX)
|
|
}
|
|
|
|
// Same frame transmitting, on VFO B, split, in CW.
|
|
const txSplitB = "IF00007030000" + "0000" + "+00000" + "000" + "00" + "1" + "3" + "1" + "0" + "1" + "0" + "00" + "0" + ";"
|
|
f2, ok := parseKenwoodIF(txSplitB)
|
|
if !ok {
|
|
t.Fatalf("valid frame rejected: %q", txSplitB)
|
|
}
|
|
if !f2.TX || f2.Mode != '3' || f2.VFO != "B" || !f2.Split {
|
|
t.Errorf("got tx=%v mode=%q vfo=%s split=%v — want transmitting, CW, B, split",
|
|
f2.TX, f2.Mode, f2.VFO, f2.Split)
|
|
}
|
|
|
|
// Rejections: anything that would make position-reading unsafe.
|
|
for _, bad := range []string{
|
|
"",
|
|
"IF;", // query echoed with no payload
|
|
"IF0001425000;", // short — the trap this guards against
|
|
"FA00014250000;", // another command's reply
|
|
"IF0001425000x0000+00000000000203000000;", // non-numeric frequency
|
|
} {
|
|
if _, ok := parseKenwoodIF(bad); ok {
|
|
t.Errorf("accepted a frame it should have refused: %q", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
// FA/FB carry ELEVEN digits on Kenwood where Yaesu uses nine — the single most
|
|
// likely place to copy the Yaesu backend and be wrong by a factor of a hundred.
|
|
func TestParseKenwoodFreq(t *testing.T) {
|
|
cases := []struct {
|
|
reply, prefix string
|
|
want int64
|
|
ok bool
|
|
}{
|
|
{"FA00014250000;", "FA", 14250000, true},
|
|
{"FB00007030000;", "FB", 7030000, true},
|
|
{"FA00000474000;", "FA", 474000, true}, // 630 m — leading zeros must not truncate
|
|
{"FA10368000000;", "FA", 10368000000, true}, // 3 cm
|
|
{"FA00014250000;", "FB", 0, false}, // wrong VFO is never silently accepted
|
|
{"FA;", "FA", 0, false},
|
|
{"FAxxxxxxxxxxx;", "FA", 0, false},
|
|
{"", "FA", 0, false},
|
|
}
|
|
for _, c := range cases {
|
|
got, ok := parseKenwoodFreq(c.reply, c.prefix)
|
|
if got != c.want || ok != c.ok {
|
|
t.Errorf("parseKenwoodFreq(%q,%q) = %d,%v — want %d,%v", c.reply, c.prefix, got, ok, c.want, c.ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The sideband follows the frequency by worldwide convention. A backend that
|
|
// puts USB on 40 m makes every SSB QSO in the log wrong, which is why this is
|
|
// pinned rather than left to the rig.
|
|
func TestKenwoodModeDigit(t *testing.T) {
|
|
cases := []struct {
|
|
mode string
|
|
hz int64
|
|
want byte
|
|
}{
|
|
{"SSB", 7150000, '1'}, // LSB below 10 MHz
|
|
{"SSB", 14250000, '2'}, // USB above
|
|
{"LSB", 14250000, '1'}, // an explicit choice wins over the convention
|
|
{"USB", 7150000, '2'},
|
|
{"CW", 7030000, '3'},
|
|
{"RTTY", 14080000, '6'},
|
|
{"AM", 7150000, '5'},
|
|
{"FM", 145000000, '4'},
|
|
{"FT8", 7074000, '2'}, // data is ALWAYS USB, even below 10 MHz (K3 "DATA REV" otherwise)
|
|
{"FT8", 14074000, '2'}, // …and above
|
|
{"", 14074000, 0}, // nothing to set
|
|
}
|
|
for _, c := range cases {
|
|
if got := kenwoodModeDigit(c.mode, c.hz); got != c.want {
|
|
t.Errorf("kenwoodModeDigit(%q, %d) = %q, want %q", c.mode, c.hz, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The DATA-mode preference only applies to soundcard/data modes, never to voice,
|
|
// CW or native RTTY — getting this wrong would hijack an SSB or CW mode change.
|
|
func TestIsKenwoodDataMode(t *testing.T) {
|
|
data := []string{"FT8", "FT4", "PSK31", "JT65", "DATA", "DIGITAL", "OLIVIA"}
|
|
notData := []string{"", "LSB", "USB", "SSB", "CW", "CW-R", "FM", "AM", "RTTY", "FSK", "RTTY-R"}
|
|
for _, m := range data {
|
|
if !isKenwoodDataMode(m) {
|
|
t.Errorf("isKenwoodDataMode(%q) = false, want true", m)
|
|
}
|
|
}
|
|
for _, m := range notData {
|
|
if isKenwoodDataMode(m) {
|
|
t.Errorf("isKenwoodDataMode(%q) = true, want false", m)
|
|
}
|
|
}
|
|
}
|
|
|
|
// What the log records for each mode digit the rig reports.
|
|
func TestKenwoodModeToADIF(t *testing.T) {
|
|
cases := []struct {
|
|
d byte
|
|
want string
|
|
}{
|
|
{'1', "LSB"}, {'2', "USB"},
|
|
{'3', "CW"}, {'7', "CW"}, // CW-R is still CW in the log
|
|
{'4', "FM"}, {'5', "AM"},
|
|
{'6', "RTTY"}, {'9', "RTTY"}, // FSK-R likewise
|
|
{'0', ""}, // unknown → say nothing rather than guess
|
|
}
|
|
for _, c := range cases {
|
|
if got := kenwoodModeToADIF(c.d, "FT8"); got != c.want {
|
|
t.Errorf("kenwoodModeToADIF(%q) = %q, want %q", c.d, got, c.want)
|
|
}
|
|
}
|
|
}
|