Adds the IC-7300MKII at CI-V 0xB6. Doing so exposed a drift between the two hand-kept copies of the model table: the settings offered the IC-7700 at 0x88 and the IC-7800 at 0x80, which are the IC-7100's and the IC-7410's factory addresses. Picking either set an address the rig never answers on — the symptom is a radio that simply stays silent — and the backend then named it as the other model. Corrected to 0x74 and 0x6A, and the four models the backend already knew (IC-7100, IC-7410, IC-7600, IC-7851) are now offered too instead of forcing a manual address. A test reads the model list out of the .tsx and asserts civ.ModelName agrees, so the next model added on one side alone fails the build rather than someone's radio.
166 lines
5.0 KiB
Go
166 lines
5.0 KiB
Go
package civ
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
)
|
|
|
|
func TestFreqBCDRoundTrip(t *testing.T) {
|
|
cases := []int64{0, 1, 7074000, 14250000, 28074000, 50313000, 144174000, 1296000000}
|
|
for _, hz := range cases {
|
|
b := FreqToBCD(hz)
|
|
if len(b) != 5 {
|
|
t.Fatalf("FreqToBCD(%d) len=%d, want 5", hz, len(b))
|
|
}
|
|
if got := BCDToFreq(b); got != hz {
|
|
t.Errorf("round trip %d → % X → %d", hz, b, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFreqBCDKnownEncoding(t *testing.T) {
|
|
// 14.250.000 Hz → little-endian BCD 00 00 25 14 00.
|
|
want := []byte{0x00, 0x00, 0x25, 0x14, 0x00}
|
|
if got := FreqToBCD(14250000); !bytes.Equal(got, want) {
|
|
t.Errorf("FreqToBCD(14250000) = % X, want % X", got, want)
|
|
}
|
|
}
|
|
|
|
func TestFrame(t *testing.T) {
|
|
// Read-frequency request to a 7610 (0x98) from the controller (0xE0).
|
|
got := Frame(0x98, AddrController, CmdReadFreq)
|
|
want := []byte{0xFE, 0xFE, 0x98, 0xE0, 0x03, 0xFD}
|
|
if !bytes.Equal(got, want) {
|
|
t.Errorf("Frame = % X, want % X", got, want)
|
|
}
|
|
}
|
|
|
|
func TestScanSingleFreqResponse(t *testing.T) {
|
|
// Rig (0x98) → controller (0xE0): freq read response for 14.250 MHz.
|
|
in := Frame(AddrController, 0x98, CmdReadFreq, 0x00, 0x00, 0x25, 0x14, 0x00)
|
|
frames, consumed := Scan(in)
|
|
if consumed != len(in) {
|
|
t.Fatalf("consumed=%d, want %d", consumed, len(in))
|
|
}
|
|
if len(frames) != 1 {
|
|
t.Fatalf("got %d frames, want 1", len(frames))
|
|
}
|
|
f := frames[0]
|
|
if f.From != 0x98 || f.To != AddrController || f.Cmd != CmdReadFreq {
|
|
t.Errorf("addrs/cmd wrong: %+v", f)
|
|
}
|
|
if hz := BCDToFreq(f.Data); hz != 14250000 {
|
|
t.Errorf("decoded freq %d, want 14250000", hz)
|
|
}
|
|
}
|
|
|
|
func TestScanSkipsEchoAndKeepsPartial(t *testing.T) {
|
|
echo := Frame(0x98, AddrController, CmdReadFreq) // our outgoing (echoed back)
|
|
resp := Frame(AddrController, 0x98, CmdReadMode, ModeCW, 0x01) // a real response
|
|
buf := append(append([]byte{}, echo...), resp...)
|
|
buf = append(buf, 0xFE, 0xFE, 0x98) // a partial third frame (no FD yet)
|
|
|
|
frames, consumed := Scan(buf)
|
|
if len(frames) != 2 {
|
|
t.Fatalf("got %d frames, want 2", len(frames))
|
|
}
|
|
// The partial frame must be left unconsumed so the next read can finish it.
|
|
if consumed != len(echo)+len(resp) {
|
|
t.Errorf("consumed=%d, want %d (partial frame retained)", consumed, len(echo)+len(resp))
|
|
}
|
|
if frames[1].Cmd != CmdReadMode || len(frames[1].Data) < 1 || frames[1].Data[0] != ModeCW {
|
|
t.Errorf("second frame wrong: %+v", frames[1])
|
|
}
|
|
}
|
|
|
|
func TestModeToADIF(t *testing.T) {
|
|
cases := []struct {
|
|
m byte
|
|
data bool
|
|
want string
|
|
}{
|
|
{ModeUSB, false, "SSB"},
|
|
{ModeLSB, false, "SSB"},
|
|
{ModeUSB, true, "DATA"},
|
|
{ModeCW, false, "CW"},
|
|
{ModeCWR, false, "CW"},
|
|
{ModeRTTY, false, "RTTY"},
|
|
{ModeAM, false, "AM"},
|
|
{ModeFM, false, "FM"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := ModeToADIF(c.m, c.data); got != c.want {
|
|
t.Errorf("ModeToADIF(0x%02X, %v) = %q, want %q", c.m, c.data, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLevelBCDRoundTrip(t *testing.T) {
|
|
for _, v := range []int{0, 1, 50, 99, 100, 128, 200, 255} {
|
|
b := LevelToBCD(v)
|
|
if len(b) != 2 {
|
|
t.Fatalf("LevelToBCD(%d) len=%d", v, len(b))
|
|
}
|
|
if got := BCDToLevel(b); got != v {
|
|
t.Errorf("level round trip %d → % X → %d", v, b, got)
|
|
}
|
|
}
|
|
// Known encodings from the Icom CI-V reference.
|
|
if got := LevelToBCD(128); !bytes.Equal(got, []byte{0x01, 0x28}) {
|
|
t.Errorf("LevelToBCD(128) = % X, want 01 28", got)
|
|
}
|
|
if got := LevelToBCD(255); !bytes.Equal(got, []byte{0x02, 0x55}) {
|
|
t.Errorf("LevelToBCD(255) = % X, want 02 55", got)
|
|
}
|
|
}
|
|
|
|
func TestByteBCDRoundTrip(t *testing.T) {
|
|
for _, v := range []int{0, 6, 12, 18, 21} {
|
|
if got := BCDToByte(ByteToBCD(v)); got != v {
|
|
t.Errorf("byte BCD round trip %d → %d", v, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestModelName(t *testing.T) {
|
|
if got := ModelName(0x98); got != "IC-7610" {
|
|
t.Errorf("ModelName(0x98) = %q, want IC-7610", got)
|
|
}
|
|
if got := ModelName(0x12); got != "Icom (0x12)" {
|
|
t.Errorf("ModelName(0x12) = %q, want fallback", got)
|
|
}
|
|
}
|
|
|
|
// CI-V addresses are hardware constants: a wrong one means the console shows the
|
|
// wrong model, and with it the wrong attenuator steps (6/12/18 dB on the big
|
|
// rigs, a single 20 dB on the small ones) — buttons the rig then NAKs.
|
|
//
|
|
// Two entries here were previously wrong in a way that pointed at each other:
|
|
// 0x80 was labelled IC-7800 (it is the IC-7410) and 0x88 IC-7700 (it is the
|
|
// IC-7100), while the real IC-7800 (0x6A) and IC-7700 (0x74) were missing — so an
|
|
// IC-7800 came up as "Icom (0x6A)" with a 20 dB attenuator it does not have.
|
|
func TestModelNameAddresses(t *testing.T) {
|
|
for addr, want := range map[byte]string{
|
|
0x6A: "IC-7800",
|
|
0x74: "IC-7700",
|
|
0x7A: "IC-7600",
|
|
0x7C: "IC-9100",
|
|
0x80: "IC-7410",
|
|
0x88: "IC-7100",
|
|
0x8E: "IC-7851",
|
|
0x94: "IC-7300",
|
|
0x98: "IC-7610",
|
|
0xA2: "IC-9700",
|
|
0xA4: "IC-705",
|
|
0xB6: "IC-7300MKII",
|
|
} {
|
|
if got := ModelName(addr); got != want {
|
|
t.Errorf("ModelName(0x%02X) = %q, want %q", addr, got, want)
|
|
}
|
|
}
|
|
// An unknown address must stay identifiable rather than masquerade as a model.
|
|
if got := ModelName(0x42); got != "Icom (0x42)" {
|
|
t.Errorf("ModelName(0x42) = %q, want the hex fallback", got)
|
|
}
|
|
}
|