diff --git a/internal/cat/icomnet.go b/internal/cat/icomnet.go index 818171b..8eb011c 100644 --- a/internal/cat/icomnet.go +++ b/internal/cat/icomnet.go @@ -136,6 +136,11 @@ type icomNet struct { // but link fine" (stay connected) from "link dead" (reconnect). See Alive(). lastRx atomic.Int64 + // dead is set when the rig explicitly tears the session down (control 0x05): + // Alive() then returns false immediately so ReadState fails on the next poll and + // the manager reconnects cleanly, instead of waiting out the 6 s lastRx timeout. + dead atomic.Bool + // audio is the optional RX audio stream (UDP 50003). nil when audio is off. // Torn down alongside the CI-V/control streams in Close. audio *icomAudio @@ -178,6 +183,9 @@ func (n *icomNet) markRx() { n.lastRx.Store(time.Now().UnixNano()) } // the radio — is gone. Independent of CI-V replies, so a powered-off rig still // reads as Alive and the session isn't torn down. Satisfies aliveTransport. func (n *icomNet) Alive() bool { + if n.dead.Load() { + return false // rig sent an explicit disconnect — reconnect now, don't wait + } last := n.lastRx.Load() if last == 0 { return true // just connected, nothing received yet — give it a chance @@ -292,6 +300,7 @@ func (n *icomNet) ctrlPump() { n.ctrlResend(icnLE.Uint16(buf[6:])) } case 0x05: // rig-initiated disconnect — it dropped US + n.dead.Store(true) // make Alive() fail now → prompt clean reconnect debugLog.Printf("icom net: rig sent DISCONNECT on control stream — session dropped by the rig") default: // Anything else on the control stream is (almost always) the rig's @@ -367,6 +376,7 @@ func (n *icomNet) civPump() { n.resend(icnLE.Uint16(buf[6:])) } case typ == 0x05: // rig-initiated disconnect — it dropped US + n.dead.Store(true) // make Alive() fail now → prompt clean reconnect debugLog.Printf("icom net: rig sent DISCONNECT on CI-V stream — session dropped by the rig") case typ == 0x00 && k > 0x15 && buf[0x10] == 0xc1: // CI-V data n.trackRxSeq(icnLE.Uint16(buf[6:])) // note gaps for retransmit diff --git a/internal/cat/omnirig.go b/internal/cat/omnirig.go index 2046934..3a457f9 100644 --- a/internal/cat/omnirig.go +++ b/internal/cat/omnirig.go @@ -32,6 +32,7 @@ type OmniRig struct { omnirig *ole.IDispatch rig *ole.IDispatch lastSig string // last logged Split/VFO signature — only log on change + rigType string // OmniRig's RigType string (the .ini title), e.g. "IC-7610" // lastSetFreq is the frequency most recently COMMANDED via SetFrequency. // SetMode uses it to pick USB vs LSB for "SSB" instead of reading OmniRig's @@ -80,11 +81,20 @@ func (o *OmniRig) Connect() error { o.rig = rigVar.ToIDispatch() if rt, err := oleutil.GetProperty(o.rig, "RigType"); err == nil { - debugLog.Printf("OmniRig connected to Rig%d type=%q", o.RigNum, rt.ToString()) + o.rigType = rt.ToString() + debugLog.Printf("OmniRig connected to Rig%d type=%q", o.RigNum, o.rigType) } return nil } +// isIC7610 reports whether the connected rig is an IC-7610. OmniRig's generic +// Freq property reads the wrong VFO on the 7610 (its Main/Sub model confuses the +// stock ini), so we read VFO A explicitly for it instead — matching what Log4OM +// shows. +func (o *OmniRig) isIC7610() bool { + return strings.Contains(strings.ToUpper(o.rigType), "7610") +} + func (o *OmniRig) Disconnect() { if o.rig != nil { o.rig.Release() @@ -199,6 +209,12 @@ func (o *OmniRig) ReadState() (RigState, error) { s.Split = false s.RxFreqHz = 0 s.FreqHz = freqMain + // IC-7610 quirk: OmniRig's generic Freq reports VFO B (its Main/Sub model + // confuses the stock ini), so OpsLog showed the wrong VFO. Read VFO A + // explicitly for the 7610 — what the operator actually wants to see. + if o.isIC7610() && freqA != 0 { + s.FreqHz = freqA + } if s.FreqHz == 0 { if s.Vfo == "B" || s.Vfo == "BB" { s.FreqHz = freqB