feat: CAT Icom working as well as split (IC7610)

This commit is contained in:
2026-07-04 18:43:11 +02:00
parent fda21e5a6f
commit abbdde9367
2 changed files with 70 additions and 2 deletions
+4
View File
@@ -32,6 +32,8 @@ const (
CmdReadMode = 0x04
CmdSetFreq = 0x05
CmdSetMode = 0x06
CmdSplit = 0x0F // read/set split & duplex (0x00 off, 0x01 split on)
CmdVfoFreq = 0x25 // read a specific VFO's freq (sub 0x00 selected, 0x01 unselected)
CmdPTT = 0x1C // sub 0x00 = PTT
CmdExtra = 0x1A // sub 0x06 = data mode on modern Icoms
CmdReadID = 0x19 // sub 0x00 = rig's own CI-V address (identifies model)
@@ -42,6 +44,8 @@ const (
SubDataMode = 0x06
SubPTT = 0x00
SubVfoSelected = 0x00 // CmdVfoFreq: the active/RX VFO
SubVfoUnselected = 0x01 // CmdVfoFreq: the other VFO (TX in split)
// CmdLevel sub-commands.
SubLevelAF = 0x01 // AF (volume)
+64
View File
@@ -28,6 +28,7 @@ type IcomSerial struct {
curFreq int64 // last frequency read (for sideband choice)
curModeByte byte // last raw Icom mode byte (for filter re-send)
readFails int // consecutive ReadState freq-read failures (transient tolerance)
lastSetFreq int64 // last frequency commanded (spot click: freq then mode)
lastSetFreqAt time.Time
@@ -77,6 +78,12 @@ func (b *IcomSerial) Connect() error {
// Short read timeout so recv() polls in a tight loop without blocking the
// CAT goroutine when the rig is silent.
_ = port.SetReadTimeout(60 * time.Millisecond)
// Deassert DTR/RTS. Icom USB rigs (IC-7610, IC-7300…) let "USB SEND" and
// "USB Keying (CW)" be mapped to the RTS or DTR line: if the port opens with
// those asserted, the rig keys into TRANSMIT. PTT here is CI-V only, so both
// hardware lines must stay low.
_ = port.SetDTR(false)
_ = port.SetRTS(false)
b.port = port
b.rx = b.rx[:0]
b.model = civ.ModelName(b.rigAddr)
@@ -110,8 +117,25 @@ func (b *IcomSerial) ReadState() (RigState, error) {
hz, err := b.readFreq()
if err != nil {
// The rig briefly stops answering CI-V while it switches band/VFO. Treat a
// few consecutive misses as transient — keep the connection and report the
// last known state — so a band change doesn't trigger a full disconnect +
// 5 s reconnect (which showed the new frequency ~10 s late). Only after
// several failures do we declare the rig lost so the Manager reconnects.
b.readFails++
if b.readFails <= 6 && b.curFreq > 0 {
s.FreqHz = b.curFreq
if b.curModeByte != 0 {
s.Mode = civ.ModeToADIF(b.curModeByte, false)
if s.Mode == "DATA" {
s.Mode = b.digital
}
}
return s, nil
}
return RigState{}, err
}
b.readFails = 0
s.FreqHz = hz
b.curFreq = hz
@@ -126,6 +150,16 @@ func (b *IcomSerial) ReadState() (RigState, error) {
b.dsp.Mode = s.Mode
b.dspMu.Unlock()
}
// Split: the selected VFO (read above) is RX; the unselected VFO is TX. ADIF
// convention → FreqHz = TX, RxFreqHz = RX.
if on, ok := b.readSplit(); ok && on {
if txHz, ok2 := b.readTXFreq(); ok2 && txHz > 0 && txHz != s.FreqHz {
s.Split = true
s.RxFreqHz = s.FreqHz // selected VFO = RX
s.FreqHz = txHz // unselected VFO = TX
}
}
return s, nil
}
@@ -232,6 +266,36 @@ func (b *IcomSerial) readFreq() (int64, error) {
return civ.BCDToFreq(f.Data), nil
}
// readSplit reads the rig's split state (CI-V 0x0F). 0x01 = split on; 0x10/0x11
// are repeater duplex (not split) and 0x00 is off.
func (b *IcomSerial) readSplit() (on bool, ok bool) {
if err := b.write(civ.CmdSplit); err != nil {
return false, false
}
f, err := b.recv(icomReadTimeout, func(d civ.Decoded) bool {
return d.Cmd == civ.CmdSplit && len(d.Data) >= 1
})
if err != nil {
return false, false
}
return f.Data[0] == 0x01, true
}
// readTXFreq reads the UNSELECTED VFO's frequency (CI-V 0x25/01) — the TX VFO
// when the rig is in split. Supported on IC-7610/7300/7851/705/9700 and similar.
func (b *IcomSerial) readTXFreq() (int64, bool) {
if err := b.write(civ.CmdVfoFreq, civ.SubVfoUnselected); err != nil {
return 0, false
}
f, err := b.recv(icomReadTimeout, func(d civ.Decoded) bool {
return d.Cmd == civ.CmdVfoFreq && len(d.Data) >= 6 && d.Data[0] == civ.SubVfoUnselected
})
if err != nil {
return 0, false
}
return civ.BCDToFreq(f.Data[1:]), true
}
func (b *IcomSerial) readMode() (byte, bool) {
if err := b.write(civ.CmdReadMode); err != nil {
return 0, false