chore: release v0.21.3

This commit is contained in:
2026-07-26 16:57:19 +02:00
parent 4fd70f6a9d
commit 91b5af1c7b
46 changed files with 2491 additions and 355 deletions
+44 -23
View File
@@ -39,10 +39,10 @@ const (
CmdReadID = 0x19 // sub 0x00 = rig's own CI-V address (identifies model)
CmdPower = 0x18 // power on/off (sub 0x01 = on, 0x00 = off; on needs an FE wake preamble)
CmdAnt = 0x12 // antenna selector (sub 0x00 = ANT1, 0x01 = ANT2; read = no sub)
CmdAtt = 0x11 // attenuator (1 BCD byte of dB; 0x00 = off)
CmdLevel = 0x14 // analogue levels (sub + 2 BCD bytes, 0000-0255)
CmdMeter = 0x15 // meters (sub + 2 BCD bytes, 0000-0255): S-meter/Po/SWR
CmdAnt = 0x12 // antenna selector (sub 0x00 = ANT1, 0x01 = ANT2; read = no sub)
CmdAtt = 0x11 // attenuator (1 BCD byte of dB; 0x00 = off)
CmdLevel = 0x14 // analogue levels (sub + 2 BCD bytes, 0000-0255)
CmdMeter = 0x15 // meters (sub + 2 BCD bytes, 0000-0255): S-meter/Po/SWR
CmdSwitch = 0x16 // on/off + multi-state DSP settings (sub + 1 byte)
CmdATU = 0x1C // sub 0x01 = antenna tuner (0x00 off, 0x01 through, 0x02 tune)
CmdScope = 0x27 // spectrum-scope waveform stream (sub 0x00 = data, 0x11 = on/off)
@@ -91,26 +91,26 @@ const (
SubATU = 0x01 // antenna tuner (data 0x02 = start tune)
// CmdScope sub-commands.
SubScopeData = 0x00 // waveform data frame (divided across several frames)
SubScopeOnOff = 0x10 // turn the scope display itself on/off (00/01)
SubScopeOn = 0x11 // enable/disable waveform data output over CI-V (00/01)
SubScopeData = 0x00 // waveform data frame (divided across several frames)
SubScopeOnOff = 0x10 // turn the scope display itself on/off (00/01)
SubScopeOn = 0x11 // enable/disable waveform data output over CI-V (00/01)
SubScopeMode = 0x14 // center/fixed mode (0=center, 1=fixed)
SubScopeSpan = 0x15 // span in center mode (±span/2 as 5 LE-BCD)
SubScopeEdge = 0x16 // fixed-mode ACTIVE edge set 1-4 (vfo + set#)
SubScopeFixEdge = 0x1e // fixed-mode edge FREQUENCIES: [range][set#][low 5-BCD][high 5-BCD]
// CmdSwitch sub-commands.
SubSwPreamp = 0x02 // 0=off, 1=P.AMP1, 2=P.AMP2
SubSwAGC = 0x12 // 1=FAST, 2=MID, 3=SLOW
SubSwNB = 0x22 // noise blanker on/off
SubSwNR = 0x40 // noise reduction on/off
SubSwANF = 0x41 // auto-notch on/off
SubSwComp = 0x44 // speech compressor on/off
SubSwMon = 0x45 // monitor on/off
SubSwVOX = 0x46 // VOX on/off
SubSwPreamp = 0x02 // 0=off, 1=P.AMP1, 2=P.AMP2
SubSwAGC = 0x12 // 1=FAST, 2=MID, 3=SLOW
SubSwNB = 0x22 // noise blanker on/off
SubSwNR = 0x40 // noise reduction on/off
SubSwANF = 0x41 // auto-notch on/off
SubSwComp = 0x44 // speech compressor on/off
SubSwMon = 0x45 // monitor on/off
SubSwVOX = 0x46 // VOX on/off
SubSwBreakIn = 0x47 // CW break-in: 0=OFF, 1=SEMI, 2=FULL (needed so 0x17 CW keys TX)
SubSwMN = 0x48 // manual notch on/off
SubSwAPF = 0x32 // audio peak filter on/off (CW only)
SubSwMN = 0x48 // manual notch on/off
SubSwAPF = 0x32 // audio peak filter on/off (CW only)
)
// CW break-in modes (CmdSwitch 0x47).
@@ -307,22 +307,43 @@ func ModeToADIF(m byte, data bool) string {
// ModelName maps a rig's default CI-V address (from CmdReadID) to a readable
// model. Unknown addresses fall back to a hex label.
//
// The name is not cosmetic: the UI derives model-dependent behaviour from it —
// notably the attenuator steps, which are 6/12/18 dB on the big rigs and a single
// 20 dB on the small ones. An address missing here therefore shows the WRONG
// attenuator buttons, and the rig NAKs them.
//
// Two entries here used to be 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 absent — so a 7800 came up as
// "Icom (0x6A)" with a 20 dB attenuator it does not have.
//
// Addresses cross-checked against the TR4W CI-V table and an independent
// published list; both agree on every value below.
func ModelName(addr byte) string {
switch addr {
case 0x6A:
return "IC-7800"
case 0x74:
return "IC-7700"
case 0x7A:
return "IC-7600"
case 0x7C:
return "IC-9100"
case 0x80:
return "IC-7410"
case 0x88:
return "IC-7100"
case 0x8E:
return "IC-7851" // shared with the IC-7850
case 0x94:
return "IC-7300"
case 0x98:
return "IC-7610"
case 0x7C:
return "IC-9100"
case 0xA2:
return "IC-9700"
case 0xA4:
return "IC-705"
case 0x88:
return "IC-7700"
case 0x80:
return "IC-7800"
}
return fmt.Sprintf("Icom (0x%02X)", addr)
}
+34 -2
View File
@@ -55,8 +55,8 @@ func TestScanSingleFreqResponse(t *testing.T) {
}
func TestScanSkipsEchoAndKeepsPartial(t *testing.T) {
echo := Frame(0x98, AddrController, CmdReadFreq) // our outgoing (echoed back)
resp := Frame(AddrController, 0x98, CmdReadMode, ModeCW, 0x01) // a real response
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)
@@ -130,3 +130,35 @@ func TestModelName(t *testing.T) {
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",
} {
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)
}
}