2026-01-28 21:13:22 -08:00
|
|
|
package tendrils
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"log"
|
|
|
|
|
"net"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-01-28 21:36:46 -08:00
|
|
|
"github.com/gopatchy/sacn"
|
2026-01-28 21:13:22 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type SACNSource struct {
|
2026-01-28 21:27:35 -08:00
|
|
|
CID string
|
|
|
|
|
SourceName string
|
|
|
|
|
Universes []int
|
|
|
|
|
SrcIP net.IP
|
|
|
|
|
LastSeen time.Time
|
2026-01-28 21:13:22 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SACNSources struct {
|
|
|
|
|
mu sync.RWMutex
|
|
|
|
|
sources map[string]*SACNSource
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewSACNSources() *SACNSources {
|
|
|
|
|
return &SACNSources{
|
|
|
|
|
sources: map[string]*SACNSource{},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 21:36:46 -08:00
|
|
|
func (s *SACNSources) Update(cid [16]byte, sourceName string, universes []uint16, srcIP net.IP) {
|
2026-01-28 21:13:22 -08:00
|
|
|
s.mu.Lock()
|
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
|
2026-01-28 21:36:46 -08:00
|
|
|
cidStr := sacn.FormatCID(cid)
|
|
|
|
|
intUniverses := make([]int, len(universes))
|
|
|
|
|
for i, u := range universes {
|
|
|
|
|
intUniverses[i] = int(u)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 21:13:22 -08:00
|
|
|
existing, exists := s.sources[cidStr]
|
|
|
|
|
if exists {
|
|
|
|
|
existing.SourceName = sourceName
|
2026-01-28 21:36:46 -08:00
|
|
|
existing.Universes = intUniverses
|
2026-01-28 21:27:35 -08:00
|
|
|
existing.SrcIP = srcIP
|
2026-01-28 21:13:22 -08:00
|
|
|
existing.LastSeen = time.Now()
|
|
|
|
|
} else {
|
|
|
|
|
s.sources[cidStr] = &SACNSource{
|
|
|
|
|
CID: cidStr,
|
2026-01-28 21:27:35 -08:00
|
|
|
SourceName: sourceName,
|
2026-01-28 21:36:46 -08:00
|
|
|
Universes: intUniverses,
|
2026-01-28 21:27:35 -08:00
|
|
|
SrcIP: srcIP,
|
2026-01-28 21:13:22 -08:00
|
|
|
LastSeen: time.Now(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *SACNSources) Expire() {
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
expireTime := time.Now().Add(-60 * time.Second)
|
|
|
|
|
for cid, source := range s.sources {
|
|
|
|
|
if source.LastSeen.Before(expireTime) {
|
|
|
|
|
delete(s.sources, cid)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tendrils) startSACNDiscoveryListener(ctx context.Context, iface net.Interface) {
|
2026-01-28 21:36:46 -08:00
|
|
|
receiver, err := sacn.NewReceiver("")
|
2026-01-28 21:13:22 -08:00
|
|
|
if err != nil {
|
2026-01-28 21:36:46 -08:00
|
|
|
log.Printf("[ERROR] failed to create sacn receiver: %v", err)
|
2026-01-28 21:13:22 -08:00
|
|
|
return
|
|
|
|
|
}
|
2026-01-28 21:36:46 -08:00
|
|
|
defer receiver.Stop()
|
2026-01-28 21:13:22 -08:00
|
|
|
|
2026-01-28 21:36:46 -08:00
|
|
|
if err := receiver.JoinDiscovery(&iface); err != nil {
|
2026-01-28 21:13:22 -08:00
|
|
|
log.Printf("[ERROR] failed to join sacn discovery multicast on %s: %v", iface.Name, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if t.DebugSACN {
|
|
|
|
|
log.Printf("[sacn] listening for discovery on %s", iface.Name)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 21:36:46 -08:00
|
|
|
receiver.SetHandler(func(src *net.UDPAddr, pkt interface{}) {
|
|
|
|
|
if disc, ok := pkt.(*sacn.DiscoveryPacket); ok {
|
|
|
|
|
t.handleSACNDiscoveryPacket(src.IP, disc)
|
2026-01-28 21:27:35 -08:00
|
|
|
}
|
2026-01-28 21:36:46 -08:00
|
|
|
})
|
2026-01-28 21:27:35 -08:00
|
|
|
|
2026-01-28 21:36:46 -08:00
|
|
|
receiver.Start()
|
|
|
|
|
<-ctx.Done()
|
2026-01-28 21:13:22 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 21:36:46 -08:00
|
|
|
func (t *Tendrils) handleSACNDiscoveryPacket(srcIP net.IP, pkt *sacn.DiscoveryPacket) {
|
2026-01-28 21:13:22 -08:00
|
|
|
if t.DebugSACN {
|
2026-01-28 21:36:46 -08:00
|
|
|
log.Printf("[sacn] discovery from %q cid=%s ip=%s universes=%v", pkt.SourceName, sacn.FormatCID(pkt.CID), srcIP, pkt.Universes)
|
2026-01-28 21:27:35 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 21:36:46 -08:00
|
|
|
if srcIP != nil && pkt.SourceName != "" {
|
|
|
|
|
t.nodes.Update(nil, nil, []net.IP{srcIP}, "", pkt.SourceName, "sacn")
|
2026-01-28 21:13:22 -08:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 21:36:46 -08:00
|
|
|
t.sacnSources.Update(pkt.CID, pkt.SourceName, pkt.Universes, srcIP)
|
2026-01-28 21:13:22 -08:00
|
|
|
t.NotifyUpdate()
|
|
|
|
|
}
|