revert(icom): drop Wake-on-LAN — the deck does not honour it

Tried on the real IC-7760: the magic packet wakes nothing; Icom's own
mechanism is the network-standby session plus the CI-V power-on, which
OpsLog already does. The MAC store and the WOL sender go, and the changelog
entry with them. The CI-V pump now says once per session that the client
pings are armed — invisible pings read as absent pings.
This commit is contained in:
2026-08-30 11:59:25 +02:00
parent 88c9756edf
commit 31c898ad7f
3 changed files with 20 additions and 92 deletions
+20 -83
View File
@@ -21,7 +21,6 @@ package cat
import (
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"net"
@@ -85,23 +84,6 @@ func NewIcomNet(host, user, pass string, civAddr int, digitalDefault string, aud
tr, err := dialIcomNet(host, user, pass, "OpsLog", b.rigAddr, cancel, audioSink)
if err == nil {
lastNetConnect = time.Now()
} else {
// The deck did not answer — a 7760 switched OFF takes its LAN
// server down entirely. Send Wake-on-LAN to the remembered MAC so
// the NEXT attempt finds a server: the rig wakes into standby, the
// session opens, and the console's ON button does the rest.
icnMACMu.Lock()
load := icnLoadMAC
icnMACMu.Unlock()
if load != nil {
if mac := load(); mac != "" {
if werr := icnWakeOnLAN(mac); werr != nil {
debugLog.Printf("icom net: wake-on-LAN to %s failed: %v", mac, werr)
} else {
debugLog.Printf("icom net: deck unreachable — sent Wake-on-LAN to %s", mac)
}
}
}
}
return tr, err
}
@@ -426,6 +408,7 @@ func (n *icomNet) civPump() {
// quiet for 10 s, say so ONCE, with the last read error — a socket error
// and a rig that stopped talking are different repairs, and the 30 s
// watchdog that follows cannot tell them apart from where it sits.
debugLog.Printf("icom net: client pings armed on the CI-V stream (every 500 ms)")
lastPkt := time.Now()
lastPing := time.Now()
lastData := time.Now() // CI-V payload packets (replies + transceive)
@@ -775,11 +758,6 @@ func dialIcomNet(host, user, pass, compName string, rigAddr byte, cancel <-chan
return nil, err
}
debugLog.Printf("icom net: conninfo sent (rig mac % X) — opening CI-V stream", rigMAC)
icnMACMu.Lock()
if icnSaveMAC != nil {
icnSaveMAC(hex.EncodeToString(rigMAC))
}
icnMACMu.Unlock()
civ, err := net.DialUDP("udp4", &net.UDPAddr{Port: 50002}, vraddr)
if err != nil {
_ = ctrl.Close()
@@ -1045,66 +1023,6 @@ func (n *icomNet) TXAudioSender() (func([]byte) error, error) {
return n.audio.SendTXChunk, nil
}
// The rig's MAC, learned from the login exchange and REMEMBERED (the app
// persists it via these hooks): a 7760 that is switched off takes its LAN
// server down with it, and the only way back in is Wake-on-LAN — which needs
// the MAC when nothing else is answering.
var (
icnMACMu sync.Mutex
icnSaveMAC func(mac string)
icnLoadMAC func() string
)
// SetIcomMACStore installs the persistence hooks for the rig's MAC address.
func SetIcomMACStore(save func(string), load func() string) {
icnMACMu.Lock()
icnSaveMAC, icnLoadMAC = save, load
icnMACMu.Unlock()
}
// icnWakeOnLAN broadcasts the magic packet for mac ("aabbccddeeff" hex) so a
// rig whose LAN server sleeps with it can be brought back without a walk to
// the shack. Errors are returned for the log only — WOL is fire-and-forget.
func icnWakeOnLAN(mac string) error {
raw, err := hex.DecodeString(mac)
if err != nil || len(raw) != 6 {
return fmt.Errorf("bad MAC %q", mac)
}
pkt := make([]byte, 6+16*6)
for i := 0; i < 6; i++ {
pkt[i] = 0xFF
}
for i := 0; i < 16; i++ {
copy(pkt[6+i*6:], raw)
}
conn, err := net.Dial("udp4", "255.255.255.255:9")
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write(pkt)
return err
}
// icnPing builds a CLIENT ping request (wfview's ping_packet: 0x15 bytes,
// type 0x07, reply=0, a monotonic time at 0x11). The rig answers each one —
// and, decisively, treats them as the client's sign of life: wfview sends one
// every 500 ms on every stream, and OpsLog, which only ever REPLIED to the
// rig's pings, watched the IC-7760 stop serving CI-V data about a minute into
// every session. Same socket answered, same transport alive: the rig had
// simply concluded nobody was listening.
func icnPing(seq uint16, sentid, rcvdid uint32, ms uint32) []byte {
b := make([]byte, 0x15)
icnLE.PutUint32(b[0:], 0x15)
icnLE.PutUint16(b[4:], 0x07)
icnLE.PutUint16(b[6:], seq)
icnLE.PutUint32(b[8:], sentid)
icnLE.PutUint32(b[12:], rcvdid)
b[0x10] = 0x00
icnLE.PutUint32(b[0x11:], ms)
return b
}
func icnOpenClose(seq uint16, sentid, rcvdid uint32, civSeq uint16, magic byte) []byte {
b := make([]byte, 0x16)
icnLE.PutUint32(b[0:], 0x16)
@@ -1169,3 +1087,22 @@ func icnHandshakeProbe(p []byte) {
}
debugLog.Printf("icom net: handshake rx %d bytes: % X", len(p), p[:n])
}
// icnPing builds a CLIENT ping request (wfview's ping_packet: 0x15 bytes,
// type 0x07, reply=0, a monotonic time at 0x11). The rig answers each one —
// and, decisively, treats them as the client's sign of life: wfview sends one
// every 500 ms on every stream, and OpsLog, which only ever REPLIED to the
// rig's pings, watched the IC-7760 stop serving CI-V data about a minute into
// every session. Same socket answered, same transport alive: the rig had
// simply concluded nobody was listening.
func icnPing(seq uint16, sentid, rcvdid uint32, ms uint32) []byte {
b := make([]byte, 0x15)
icnLE.PutUint32(b[0:], 0x15)
icnLE.PutUint16(b[4:], 0x07)
icnLE.PutUint16(b[6:], seq)
icnLE.PutUint32(b[8:], sentid)
icnLE.PutUint32(b[12:], rcvdid)
b[0x10] = 0x00
icnLE.PutUint32(b[0x11:], ms)
return b
}