feat: CAT Icom working as well as split (IC7610)
This commit is contained in:
@@ -32,6 +32,8 @@ const (
|
|||||||
CmdReadMode = 0x04
|
CmdReadMode = 0x04
|
||||||
CmdSetFreq = 0x05
|
CmdSetFreq = 0x05
|
||||||
CmdSetMode = 0x06
|
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
|
CmdPTT = 0x1C // sub 0x00 = PTT
|
||||||
CmdExtra = 0x1A // sub 0x06 = data mode on modern Icoms
|
CmdExtra = 0x1A // sub 0x06 = data mode on modern Icoms
|
||||||
CmdReadID = 0x19 // sub 0x00 = rig's own CI-V address (identifies model)
|
CmdReadID = 0x19 // sub 0x00 = rig's own CI-V address (identifies model)
|
||||||
@@ -40,8 +42,10 @@ const (
|
|||||||
CmdLevel = 0x14 // analogue levels (sub + 2 BCD bytes, 0000-0255)
|
CmdLevel = 0x14 // analogue levels (sub + 2 BCD bytes, 0000-0255)
|
||||||
CmdSwitch = 0x16 // on/off + multi-state DSP settings (sub + 1 byte)
|
CmdSwitch = 0x16 // on/off + multi-state DSP settings (sub + 1 byte)
|
||||||
|
|
||||||
SubDataMode = 0x06
|
SubDataMode = 0x06
|
||||||
SubPTT = 0x00
|
SubPTT = 0x00
|
||||||
|
SubVfoSelected = 0x00 // CmdVfoFreq: the active/RX VFO
|
||||||
|
SubVfoUnselected = 0x01 // CmdVfoFreq: the other VFO (TX in split)
|
||||||
|
|
||||||
// CmdLevel sub-commands.
|
// CmdLevel sub-commands.
|
||||||
SubLevelAF = 0x01 // AF (volume)
|
SubLevelAF = 0x01 // AF (volume)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type IcomSerial struct {
|
|||||||
|
|
||||||
curFreq int64 // last frequency read (for sideband choice)
|
curFreq int64 // last frequency read (for sideband choice)
|
||||||
curModeByte byte // last raw Icom mode byte (for filter re-send)
|
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)
|
lastSetFreq int64 // last frequency commanded (spot click: freq then mode)
|
||||||
lastSetFreqAt time.Time
|
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
|
// Short read timeout so recv() polls in a tight loop without blocking the
|
||||||
// CAT goroutine when the rig is silent.
|
// CAT goroutine when the rig is silent.
|
||||||
_ = port.SetReadTimeout(60 * time.Millisecond)
|
_ = 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.port = port
|
||||||
b.rx = b.rx[:0]
|
b.rx = b.rx[:0]
|
||||||
b.model = civ.ModelName(b.rigAddr)
|
b.model = civ.ModelName(b.rigAddr)
|
||||||
@@ -110,8 +117,25 @@ func (b *IcomSerial) ReadState() (RigState, error) {
|
|||||||
|
|
||||||
hz, err := b.readFreq()
|
hz, err := b.readFreq()
|
||||||
if err != nil {
|
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
|
return RigState{}, err
|
||||||
}
|
}
|
||||||
|
b.readFails = 0
|
||||||
s.FreqHz = hz
|
s.FreqHz = hz
|
||||||
b.curFreq = hz
|
b.curFreq = hz
|
||||||
|
|
||||||
@@ -126,6 +150,16 @@ func (b *IcomSerial) ReadState() (RigState, error) {
|
|||||||
b.dsp.Mode = s.Mode
|
b.dsp.Mode = s.Mode
|
||||||
b.dspMu.Unlock()
|
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
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +266,36 @@ func (b *IcomSerial) readFreq() (int64, error) {
|
|||||||
return civ.BCDToFreq(f.Data), nil
|
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) {
|
func (b *IcomSerial) readMode() (byte, bool) {
|
||||||
if err := b.write(civ.CmdReadMode); err != nil {
|
if err := b.write(civ.CmdReadMode); err != nil {
|
||||||
return 0, false
|
return 0, false
|
||||||
|
|||||||
Reference in New Issue
Block a user