feat(icom): Wake-on-LAN for a deck that sleeps with its server
A 7760 switched fully off takes its LAN server down, and no session means no console and no ON button — the one thing remote operation cannot tolerate. The rig's MAC is learned from every login and persisted; when a dial finds nobody home, the magic packet goes out to it before the next attempt. The rig wakes into standby, the session opens, and the ON button does the rest.
This commit is contained in:
@@ -21,6 +21,7 @@ package cat
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -84,6 +85,23 @@ 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
|
||||
}
|
||||
@@ -740,6 +758,11 @@ 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()
|
||||
@@ -1004,6 +1027,47 @@ 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
|
||||
}
|
||||
|
||||
func icnOpenClose(seq uint16, sentid, rcvdid uint32, civSeq uint16, magic byte) []byte {
|
||||
b := make([]byte, 0x16)
|
||||
icnLE.PutUint32(b[0:], 0x16)
|
||||
|
||||
Reference in New Issue
Block a user