feat: CAT Icom working as well as split (IC7610)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user