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
-7
View File
@@ -159,7 +159,6 @@ const (
keyCATIcomNetUser = "cat.icom.net.user" // Icom network: Network User1 ID
keyCATIcomNetPass = "cat.icom.net.pass" // Icom network: Network User1 password
keyCATIcomNetAudio = "cat.icom.net.audio" // Icom network: stream RX audio on 50003 (experimental)
keyCATIcomNetMAC = "cat.icom.net.mac" // the rig's MAC, learned at login, for Wake-on-LAN
keyAudioMonitorOn = "audio.monitor.on" // play the network RX audio through the Listening device (the stream itself stays open for the recorder either way)
keyCATTCIHost = "cat.tci.host" // TCI host (Expert Electronics SunSDR / ExpertSDR2)
keyCATTCIPort = "cat.tci.port" // TCI WebSocket port (default 40001)
@@ -1297,12 +1296,6 @@ func (a *App) startup(ctx context.Context) {
}
a.startQSORecorderIfEnabled()
// The Icom network backend remembers the rig's MAC so a deck switched
// fully off can be woken with a magic packet when a session is wanted.
cat.SetIcomMACStore(
func(mac string) { a.setSetting(keyCATIcomNetMAC, mac) },
func() string { return a.settingOr(keyCATIcomNetMAC, "") },
)
a.reloadCAT()
// The QSO logbook lives where the ACTIVE PROFILE points it: the local SQLite
-2
View File
@@ -37,7 +37,6 @@
"Icom console: the meters use the same LED bars as the Flex and Elecraft consoles, and the mode badge says USB or LSB instead of an ambiguous SSB.",
"Cluster: spots on the standard FT8/FT4 frequencies of every band now read FT8/FT4 when the comment names no mode — 30 m, 60 m, 80 m FT4, 17 m FT4 and 12 m were falling into the generic DATA bucket.",
"OmniRig (Yaesu): with split engaged, tuning to a spot moves BOTH VFOs — on an FT-2000 the write landed on the TX VFO only, so the transmitter QSYed and the receiver stayed behind.",
"Icom network: Wake-on-LAN — a rig switched fully off (LAN server down) is woken with a magic packet to its remembered MAC whenever a session is wanted, so the console and its ON button come back without a walk to the shack.",
"Worked before: a slow lookup can no longer overwrite a newer one — typing a second callsign quickly could leave the previous calls QSOs displayed under the new calls name.",
"Icom network: OpsLog now sends its own pings every 500 ms on all three streams, as RS-BA1 and wfview do — a client that only ever replied was judged absent by the rig, which stopped serving CI-V data about a minute into every session: the recurring sound/CAT dropouts."
],
@@ -76,7 +75,6 @@
"Console Icom : les mètres utilisent les mêmes barres LED que les consoles Flex et Elecraft, et le badge de mode dit USB ou LSB au lieu dun SSB ambigu.",
"Cluster : les spots sur les fréquences standard FT8/FT4 de chaque bande lisent désormais FT8/FT4 quand le commentaire ne nomme pas de mode — 30 m, 60 m, FT4 80 m, FT4 17 m et 12 m tombaient dans le bloc DATA générique.",
"OmniRig (Yaesu) : avec le split actif, se rendre sur un spot déplace LES DEUX VFO — sur un FT-2000 l’écriture natteignait que le VFO TX, donc l’émetteur QSYait et le récepteur restait derrière.",
"Réseau Icom : Wake-on-LAN — une radio complètement éteinte (serveur LAN coupé) est réveillée par un paquet magique vers sa MAC mémorisée dès quune session est voulue, et la console avec son bouton ON revient sans aller au shack.",
"Déjà contacté : une recherche lente ne peut plus écraser une plus récente — taper un second indicatif rapidement pouvait laisser les QSO du call précédent affichés sous le nom du nouveau.",
"Réseau Icom : OpsLog envoie désormais ses propres pings toutes les 500 ms sur les trois flux, comme RS-BA1 et wfview — un client qui ne faisait que répondre était jugé absent par la radio, qui cessait de servir le CI-V au bout dune minute : les coupures récurrentes de son/CAT."
]
+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
}