Use artnet library for per-interface discovery
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,13 @@
|
||||
"Bash(snmpwalk:*)",
|
||||
"Bash(go work sync:*)",
|
||||
"Bash(GOWORK=/home/flamingcow/go.work go vet:*)",
|
||||
"Bash(git pull:*)"
|
||||
"Bash(git pull:*)",
|
||||
"Bash(GOPROXY=direct go get:*)",
|
||||
"Bash(git -C /home/flamingcow/tendrils diff)",
|
||||
"Bash(git -C /home/flamingcow/artmap diff)",
|
||||
"Bash(git -C /home/flamingcow/artnet status)",
|
||||
"Bash(git -C /home/flamingcow/tendrils status)",
|
||||
"Bash(git -C /home/flamingcow/artmap status)"
|
||||
],
|
||||
"ask": [
|
||||
"Bash(rm *)"
|
||||
|
||||
174
artnet.go
174
artnet.go
@@ -13,119 +13,69 @@ import (
|
||||
"github.com/gopatchy/artnet"
|
||||
)
|
||||
|
||||
func (t *Tendrils) startArtNetListener(ctx context.Context) {
|
||||
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: artnet.Port})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] failed to listen artnet: %v", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
t.artnetConn = conn
|
||||
|
||||
buf := make([]byte, 65536)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
n, src, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
t.handleArtNetPacket(src, buf[:n])
|
||||
}
|
||||
type artnetHandler struct {
|
||||
t *Tendrils
|
||||
discovery *artnet.Discovery
|
||||
}
|
||||
|
||||
func (t *Tendrils) startArtNetPoller(ctx context.Context, iface net.Interface) {
|
||||
func (h *artnetHandler) HandleDMX(src *net.UDPAddr, pkt *artnet.DMXPacket) {}
|
||||
|
||||
func (h *artnetHandler) HandlePoll(src *net.UDPAddr, pkt *artnet.PollPacket) {
|
||||
h.discovery.HandlePoll(src)
|
||||
}
|
||||
|
||||
func (h *artnetHandler) HandlePollReply(src *net.UDPAddr, pkt *artnet.PollReplyPacket) {
|
||||
h.discovery.HandlePollReply(src, pkt)
|
||||
}
|
||||
|
||||
func (t *Tendrils) startArtNet(ctx context.Context, iface net.Interface) {
|
||||
srcIP, broadcast := getInterfaceIPv4(iface)
|
||||
if srcIP == nil || broadcast == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sendConn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: srcIP, Port: 0})
|
||||
sender, err := artnet.NewInterfaceSender(iface.Name)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] failed to create artnet send socket on %s: %v", iface.Name, err)
|
||||
log.Printf("[ERROR] failed to create artnet sender for %s: %v", iface.Name, err)
|
||||
return
|
||||
}
|
||||
defer sendConn.Close()
|
||||
|
||||
go t.listenArtNetReplies(ctx, sendConn, iface.Name)
|
||||
discovery := artnet.NewDiscovery(sender, srcIP, broadcast, iface.HardwareAddr, "", "", nil, nil)
|
||||
discovery.SetOnChange(func(node *artnet.Node) {
|
||||
t.handleArtNetNode(node)
|
||||
})
|
||||
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
handler := &artnetHandler{t: t, discovery: discovery}
|
||||
|
||||
t.sendArtPoll(sendConn, broadcast, iface.Name)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
receiver, err := artnet.NewInterfaceReceiver(iface.Name, handler)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] failed to create artnet receiver for %s: %v", iface.Name, err)
|
||||
sender.Close()
|
||||
return
|
||||
case <-ticker.C:
|
||||
t.sendArtPoll(sendConn, broadcast, iface.Name)
|
||||
}
|
||||
}
|
||||
|
||||
discovery.SetReceiver(receiver)
|
||||
receiver.Start()
|
||||
discovery.Start()
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
discovery.Stop()
|
||||
receiver.Stop()
|
||||
sender.Close()
|
||||
}
|
||||
|
||||
func (t *Tendrils) listenArtNetReplies(ctx context.Context, conn *net.UDPConn, ifaceName string) {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
n, src, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
t.handleArtNetPacket(src, buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tendrils) handleArtNetPacket(src *net.UDPAddr, data []byte) {
|
||||
opCode, pkt, err := artnet.ParsePacket(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch opCode {
|
||||
case artnet.OpPollReply:
|
||||
if reply, ok := pkt.(*artnet.PollReplyPacket); ok {
|
||||
t.handleArtPollReply(src.IP, reply)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tendrils) handleArtPollReply(srcIP net.IP, pkt *artnet.PollReplyPacket) {
|
||||
ip := pkt.IP()
|
||||
mac := pkt.MACAddr()
|
||||
shortName := pkt.GetShortName()
|
||||
longName := pkt.GetLongName()
|
||||
func (t *Tendrils) handleArtNetNode(node *artnet.Node) {
|
||||
ip := node.IP
|
||||
mac := node.MAC
|
||||
shortName := node.ShortName
|
||||
longName := node.LongName
|
||||
|
||||
var inputs, outputs []int
|
||||
for _, u := range pkt.InputUniverses() {
|
||||
for _, u := range node.Inputs {
|
||||
inputs = append(inputs, int(u))
|
||||
}
|
||||
for _, u := range pkt.OutputUniverses() {
|
||||
for _, u := range node.Outputs {
|
||||
outputs = append(outputs, int(u))
|
||||
}
|
||||
|
||||
@@ -141,41 +91,17 @@ func (t *Tendrils) handleArtPollReply(srcIP net.IP, pkt *artnet.PollReplyPacket)
|
||||
t.nodes.Update(nil, mac, []net.IP{ip}, "", name, "artnet")
|
||||
}
|
||||
|
||||
node := t.nodes.GetByIP(ip)
|
||||
if node == nil && mac != nil {
|
||||
node = t.nodes.GetByMAC(mac)
|
||||
n := t.nodes.GetByIP(ip)
|
||||
if n == nil && mac != nil {
|
||||
n = t.nodes.GetByMAC(mac)
|
||||
}
|
||||
if node == nil && name != "" {
|
||||
node = t.nodes.GetOrCreateByName(name)
|
||||
if n == nil && name != "" {
|
||||
n = t.nodes.GetOrCreateByName(name)
|
||||
}
|
||||
if node != nil {
|
||||
t.nodes.UpdateArtNet(node, inputs, outputs)
|
||||
if n != nil {
|
||||
t.nodes.UpdateArtNet(n, inputs, outputs)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tendrils) sendArtPoll(conn *net.UDPConn, broadcast net.IP, ifaceName string) {
|
||||
packet := artnet.BuildPollPacket()
|
||||
|
||||
_, err := conn.WriteToUDP(packet, &net.UDPAddr{IP: broadcast, Port: artnet.Port})
|
||||
if err != nil {
|
||||
if t.DebugArtNet {
|
||||
log.Printf("[artnet] %s: failed to send poll: %v", ifaceName, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if t.DebugArtNet {
|
||||
log.Printf("[artnet] %s: sent poll to %s", ifaceName, broadcast)
|
||||
}
|
||||
}
|
||||
|
||||
func containsInt(slice []int, val int) bool {
|
||||
for _, v := range slice {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
t.NotifyUpdate()
|
||||
}
|
||||
|
||||
func (n *Nodes) UpdateArtNet(node *Node, inputs, outputs []int) {
|
||||
|
||||
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.25.6
|
||||
require (
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/gopatchy/artnet v0.0.0-20260128203426-0a3e9b1daf66
|
||||
github.com/gopatchy/artnet v0.0.0-20260130164309-5e7400fe514e
|
||||
github.com/gopatchy/multicast v0.0.0-20260130055828-12d0b38af995
|
||||
github.com/gopatchy/sacn v0.0.0-20260130055955-54a46fbfe1f0
|
||||
github.com/gosnmp/gosnmp v1.43.2
|
||||
|
||||
4
go.sum
4
go.sum
@@ -9,8 +9,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/gopatchy/artnet v0.0.0-20260128203426-0a3e9b1daf66 h1:QZrypvWOUbZeJsFRRx8UXf+MUbvkF/WR2KvNUynWFTM=
|
||||
github.com/gopatchy/artnet v0.0.0-20260128203426-0a3e9b1daf66/go.mod h1:V/D32mh1xfK/llCKbrqI2jxw4xL4hf6Ge2yLiIrp9/4=
|
||||
github.com/gopatchy/artnet v0.0.0-20260130164309-5e7400fe514e h1:KaCtixVKARhtTzqlqWyzXurNACaesAGxjFgnFd3jYT4=
|
||||
github.com/gopatchy/artnet v0.0.0-20260130164309-5e7400fe514e/go.mod h1:V/D32mh1xfK/llCKbrqI2jxw4xL4hf6Ge2yLiIrp9/4=
|
||||
github.com/gopatchy/multicast v0.0.0-20260130055828-12d0b38af995 h1:dwu07X0JnN6Ar3oZMLa/BokX04JRa3AeM38Nz7nwKnM=
|
||||
github.com/gopatchy/multicast v0.0.0-20260130055828-12d0b38af995/go.mod h1:mSeh6GX+fL6SWZYqxYHTdnddvzDx4qsGSBnlGwY5ZsA=
|
||||
github.com/gopatchy/sacn v0.0.0-20260130055955-54a46fbfe1f0 h1:j1uxCRJSu7G8UwlTykIDacbcQY2qoUpZ9t/SBHqLET8=
|
||||
|
||||
@@ -33,7 +33,6 @@ func getInterfaceIPv4(iface net.Interface) (srcIP, broadcast net.IP) {
|
||||
type Tendrils struct {
|
||||
activeInterfaces map[string]context.CancelFunc
|
||||
nodes *Nodes
|
||||
artnetConn *net.UDPConn
|
||||
errors *ErrorTracker
|
||||
ping *PingManager
|
||||
broadcast *BroadcastStats
|
||||
@@ -158,10 +157,6 @@ func (t *Tendrils) Run() {
|
||||
go t.pollARP(ctx)
|
||||
}
|
||||
|
||||
if !t.DisableArtNet {
|
||||
go t.startArtNetListener(ctx)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
@@ -298,7 +293,7 @@ func (t *Tendrils) startInterface(ctx context.Context, iface net.Interface) {
|
||||
go t.listenMDNS(ctx, iface)
|
||||
}
|
||||
if !t.DisableArtNet {
|
||||
go t.startArtNetPoller(ctx, iface)
|
||||
go t.startArtNet(ctx, iface)
|
||||
}
|
||||
if !t.DisableSACN {
|
||||
go t.startSACNDiscoveryListener(ctx, iface)
|
||||
|
||||
Reference in New Issue
Block a user