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, '1'}, // data rides on the sideband for the band {"FT8", 14074000, '2'}, {"", 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) } } } // 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) } } }