Initial sACN library with protocol, sender, receiver, and discovery
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
124
discovery.go
Normal file
124
discovery.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package sacn
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
CID [16]byte
|
||||
SourceName string
|
||||
IP net.IP
|
||||
Universes []uint16
|
||||
LastSeen time.Time
|
||||
}
|
||||
|
||||
type Discovery struct {
|
||||
sources map[string]*Source
|
||||
mu sync.RWMutex
|
||||
onChange func(*Source)
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func NewDiscovery() *Discovery {
|
||||
return &Discovery{
|
||||
sources: map[string]*Source{},
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discovery) SetOnChange(fn func(*Source)) {
|
||||
d.onChange = fn
|
||||
}
|
||||
|
||||
func (d *Discovery) HandleDiscoveryPacket(src *net.UDPAddr, pkt *DiscoveryPacket) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
cidStr := FormatCID(pkt.CID)
|
||||
|
||||
source, exists := d.sources[cidStr]
|
||||
if !exists {
|
||||
source = &Source{
|
||||
CID: pkt.CID,
|
||||
}
|
||||
d.sources[cidStr] = source
|
||||
}
|
||||
|
||||
source.SourceName = pkt.SourceName
|
||||
source.IP = src.IP
|
||||
source.Universes = pkt.Universes
|
||||
source.LastSeen = time.Now()
|
||||
|
||||
if d.onChange != nil {
|
||||
d.onChange(source)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discovery) GetSource(cid string) *Source {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
return d.sources[cid]
|
||||
}
|
||||
|
||||
func (d *Discovery) GetSourceByIP(ip net.IP) *Source {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
for _, source := range d.sources {
|
||||
if source.IP != nil && source.IP.Equal(ip) {
|
||||
return source
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Discovery) GetAllSources() []*Source {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
result := make([]*Source, 0, len(d.sources))
|
||||
for _, source := range d.sources {
|
||||
result = append(result, source)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *Discovery) Expire() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
cutoff := time.Now().Add(-60 * time.Second)
|
||||
for cid, source := range d.sources {
|
||||
if source.LastSeen.Before(cutoff) {
|
||||
delete(d.sources, cid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discovery) StartCleanup() {
|
||||
go d.cleanupLoop()
|
||||
}
|
||||
|
||||
func (d *Discovery) Stop() {
|
||||
select {
|
||||
case <-d.done:
|
||||
default:
|
||||
close(d.done)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discovery) cleanupLoop() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-d.done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
d.Expire()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user