Xiegu speaks CI-V with a reduced command set: frames, BCD encoding, addressing and the opcodes for frequency, mode, PTT and split are Icom's, so this reuses internal/cat/civ wholesale instead of re-deriving a codec. It is a SEPARATE backend rather than the Icom one at address 0x70, because what the two rigs do NOT share is the deciding part. The Icom backend reads the spectrum scope, the DSP block, data mode via 1A 06 and the model id via 19 — none of which a Xiegu implements. Pointed at a G90 it would poll every cycle for answers that never come, and spend its silence tolerance on commands the radio was never going to support. Two consequences of the rig's smaller mode table are handled explicitly rather than left to fail: there is no data mode, so a digital QSO is set to plain sideband (what the operator does on the radio anyway) instead of being refused; and the split TX frequency is NOT reported, because reading the unselected VFO needs 0x25, which the Xiegu table does not list — a split flag carrying a wrong TX frequency is worse than the flag alone, since the frequency is what gets logged. The published command table has rows that slipped during typesetting (0x07 and 0x0F share a block). Where it contradicts itself the Icom meaning is used, the rest of the table matching Icom exactly, and every unexpected reply is logged raw so a first on-air run settles it. NOT yet verified on a radio.
84 lines
2.7 KiB
Go
84 lines
2.7 KiB
Go
package cat
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"hamlog/internal/cat/civ"
|
|
)
|
|
|
|
// The Xiegu mode table is SHORTER than Icom's: LSB, USB, AM, CW, CWR and
|
|
// nothing else. The interesting cases are the ones with no equivalent — a
|
|
// digital mode must land on plain sideband rather than be refused, because
|
|
// refusing it would leave the rig on whatever it was and silently log the wrong
|
|
// mode.
|
|
func TestXieguModeByte(t *testing.T) {
|
|
cases := []struct {
|
|
mode string
|
|
hz int64
|
|
want byte
|
|
ok bool
|
|
}{
|
|
{"SSB", 7150000, 0x00, true}, // LSB below 10 MHz
|
|
{"SSB", 14250000, 0x01, true}, // USB above
|
|
{"LSB", 14250000, 0x00, true}, // explicit beats the convention
|
|
{"USB", 7150000, 0x01, true},
|
|
{"AM", 7150000, 0x02, true},
|
|
{"CW", 7030000, 0x03, true},
|
|
{"CWR", 7030000, 0x07, true},
|
|
{"FT8", 7074000, 0x00, true}, // no data mode on this rig → LSB
|
|
{"FT8", 14074000, 0x01, true}, // → USB
|
|
{"RTTY", 14080000, 0x01, true},
|
|
{"FM", 145000000, 0x01, true}, // no FM either; sideband is the honest fallback
|
|
{"", 14074000, 0x00, false},
|
|
}
|
|
for _, c := range cases {
|
|
got, ok := xieguModeByte(c.mode, c.hz)
|
|
if got != c.want || ok != c.ok {
|
|
t.Errorf("xieguModeByte(%q, %d) = 0x%02X,%v — want 0x%02X,%v", c.mode, c.hz, got, ok, c.want, c.ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The Xiegu mode bytes must decode through the shared Icom table, since that is
|
|
// the whole reason this backend reuses internal/cat/civ instead of its own
|
|
// codec. If the two ever disagree, the radio would be set to one mode and read
|
|
// back as another.
|
|
func TestXieguModesRoundTripThroughCIV(t *testing.T) {
|
|
cases := []struct {
|
|
mode string
|
|
adif string
|
|
}{
|
|
// ADIF has no LSB/USB distinction — both sidebands ARE the SSB mode, and
|
|
// that is what gets logged. The sideband still matters to the radio, which
|
|
// is why xieguModeByte picks it from the frequency.
|
|
{"LSB", "SSB"},
|
|
{"USB", "SSB"},
|
|
{"AM", "AM"},
|
|
{"CW", "CW"},
|
|
}
|
|
for _, c := range cases {
|
|
b, ok := xieguModeByte(c.mode, 14200000)
|
|
if !ok {
|
|
t.Fatalf("xieguModeByte(%q) refused", c.mode)
|
|
}
|
|
if got := civ.ModeToADIF(b, false); got != c.adif {
|
|
t.Errorf("%s → 0x%02X → %q, want %q", c.mode, b, got, c.adif)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Frequency encoding is shared with Icom, and it is the one field where a byte
|
|
// out of place mistunes the radio silently. Pinned here at the boundaries a
|
|
// Xiegu actually covers (it is an HF rig, so the 5-byte BCD upper bytes are 0).
|
|
func TestXieguFrequencyEncoding(t *testing.T) {
|
|
for _, hz := range []int64{1840000, 7074000, 14074000, 28500000, 50313000} {
|
|
b := civ.FreqToBCD(hz)
|
|
if len(b) != 5 {
|
|
t.Fatalf("FreqToBCD(%d) produced %d bytes, want 5", hz, len(b))
|
|
}
|
|
if got := civ.BCDToFreq(b); got != hz {
|
|
t.Errorf("round trip %d → % X → %d", hz, b, got)
|
|
}
|
|
}
|
|
}
|