2026-01-26 13:37:21 -08:00
|
|
|
package tendrils
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"sort"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/fvbommel/sortorder"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type SACNNode struct {
|
2026-01-28 21:27:35 -08:00
|
|
|
TypeID string `json:"typeid"`
|
|
|
|
|
Node *Node `json:"node"`
|
|
|
|
|
Inputs []int `json:"inputs,omitempty"`
|
|
|
|
|
Outputs []int `json:"outputs,omitempty"`
|
2026-01-26 13:37:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Tendrils) getSACNNodes() []*SACNNode {
|
|
|
|
|
t.nodes.mu.Lock()
|
|
|
|
|
t.nodes.expireMulticastMemberships()
|
|
|
|
|
t.nodes.mu.Unlock()
|
|
|
|
|
|
2026-01-28 21:27:35 -08:00
|
|
|
t.sacnSources.Expire()
|
|
|
|
|
|
2026-01-26 13:37:21 -08:00
|
|
|
t.nodes.mu.RLock()
|
|
|
|
|
defer t.nodes.mu.RUnlock()
|
|
|
|
|
|
2026-01-28 21:27:35 -08:00
|
|
|
nodeInputs := map[*Node][]int{}
|
|
|
|
|
nodeOutputs := map[*Node][]int{}
|
2026-01-26 13:37:21 -08:00
|
|
|
|
|
|
|
|
for _, gm := range t.nodes.multicastGroups {
|
|
|
|
|
if !strings.HasPrefix(gm.Group.Name, "sacn:") {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var universe int
|
|
|
|
|
if _, err := fmt.Sscanf(gm.Group.Name, "sacn:%d", &universe); err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, membership := range gm.Members {
|
|
|
|
|
if membership.Node == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-01-28 21:27:35 -08:00
|
|
|
inputs := nodeInputs[membership.Node]
|
|
|
|
|
if !containsInt(inputs, universe) {
|
|
|
|
|
nodeInputs[membership.Node] = append(inputs, universe)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.sacnSources.mu.RLock()
|
|
|
|
|
for _, source := range t.sacnSources.sources {
|
|
|
|
|
if source.SrcIP == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
node := t.nodes.getByIPLocked(source.SrcIP)
|
|
|
|
|
if node == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for _, u := range source.Universes {
|
|
|
|
|
outputs := nodeOutputs[node]
|
|
|
|
|
if !containsInt(outputs, u) {
|
|
|
|
|
nodeOutputs[node] = append(outputs, u)
|
2026-01-26 13:37:21 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-28 21:27:35 -08:00
|
|
|
t.sacnSources.mu.RUnlock()
|
|
|
|
|
|
|
|
|
|
allNodes := map[*Node]bool{}
|
|
|
|
|
for node := range nodeInputs {
|
|
|
|
|
allNodes[node] = true
|
|
|
|
|
}
|
|
|
|
|
for node := range nodeOutputs {
|
|
|
|
|
allNodes[node] = true
|
|
|
|
|
}
|
2026-01-26 13:37:21 -08:00
|
|
|
|
2026-01-28 21:27:35 -08:00
|
|
|
result := make([]*SACNNode, 0, len(allNodes))
|
|
|
|
|
for node := range allNodes {
|
|
|
|
|
inputs := nodeInputs[node]
|
|
|
|
|
outputs := nodeOutputs[node]
|
|
|
|
|
sort.Ints(inputs)
|
|
|
|
|
sort.Ints(outputs)
|
2026-01-26 13:37:21 -08:00
|
|
|
result = append(result, &SACNNode{
|
2026-01-28 21:27:35 -08:00
|
|
|
TypeID: newTypeID("sacnnode"),
|
|
|
|
|
Node: node,
|
|
|
|
|
Inputs: inputs,
|
|
|
|
|
Outputs: outputs,
|
2026-01-26 13:37:21 -08:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sort.Slice(result, func(i, j int) bool {
|
|
|
|
|
return sortorder.NaturalLess(result[i].Node.DisplayName(), result[j].Node.DisplayName())
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}
|