Refactor Dante fields to use proper types and group flows with lastSeen
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
151
dante.go
151
dante.go
@@ -87,7 +87,7 @@ func (t *Tendrils) handlePTPPacket(ifaceName string, srcIP net.IP, data []byte)
|
|||||||
t.nodes.SetDanteClockMaster(srcIP)
|
t.nodes.SetDanteClockMaster(srcIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nodes) UpdateDanteTxChannels(name string, ip net.IP, channels string) {
|
func (n *Nodes) UpdateDanteTxChannels(name string, ip net.IP, channels int) {
|
||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
defer n.mu.Unlock()
|
defer n.mu.Unlock()
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ func (n *Nodes) GetDanteTxDeviceInGroup(groupIP net.IP) *Node {
|
|||||||
|
|
||||||
group := ParseMulticastGroup(groupIP)
|
group := ParseMulticastGroup(groupIP)
|
||||||
for _, node := range n.nodes {
|
for _, node := range n.nodes {
|
||||||
if node.DanteTxChannels != "" && node.MulticastGroups != nil {
|
if node.DanteTxChannels > 0 && node.MulticastGroups != nil {
|
||||||
if _, exists := node.MulticastGroups[group]; exists {
|
if _, exists := node.MulticastGroups[group]; exists {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
@@ -188,16 +188,24 @@ func (n *Nodes) UpdateDanteFlow(source, subscriber *Node, channelInfo string, fl
|
|||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
defer n.mu.Unlock()
|
defer n.mu.Unlock()
|
||||||
|
|
||||||
n.updateDanteTx(source, subscriber, channelInfo, flowStatus)
|
now := time.Now()
|
||||||
n.updateDanteRx(subscriber, source, channelInfo, flowStatus)
|
n.updateDanteTx(source, subscriber, channelInfo, flowStatus, now)
|
||||||
|
n.updateDanteRx(subscriber, source, channelInfo, flowStatus, now)
|
||||||
source.danteLastSeen = time.Now()
|
|
||||||
subscriber.danteLastSeen = time.Now()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nodes) updateDanteTx(source, subscriber *Node, channelInfo string, flowStatus DanteFlowStatus) {
|
func (n *Nodes) ensureDanteFlows(node *Node) *DanteFlows {
|
||||||
|
if node.DanteFlows == nil {
|
||||||
|
node.DanteFlows = &DanteFlows{}
|
||||||
|
}
|
||||||
|
return node.DanteFlows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Nodes) updateDanteTx(source, subscriber *Node, channelInfo string, flowStatus DanteFlowStatus, now time.Time) {
|
||||||
|
flows := n.ensureDanteFlows(source)
|
||||||
|
flows.lastSeen = now
|
||||||
|
|
||||||
var peer *DantePeer
|
var peer *DantePeer
|
||||||
for _, p := range source.DanteTx {
|
for _, p := range flows.Tx {
|
||||||
if p.Node == subscriber {
|
if p.Node == subscriber {
|
||||||
peer = p
|
peer = p
|
||||||
break
|
break
|
||||||
@@ -208,7 +216,7 @@ func (n *Nodes) updateDanteTx(source, subscriber *Node, channelInfo string, flow
|
|||||||
Node: subscriber,
|
Node: subscriber,
|
||||||
Status: map[string]string{},
|
Status: map[string]string{},
|
||||||
}
|
}
|
||||||
source.DanteTx = append(source.DanteTx, peer)
|
flows.Tx = append(flows.Tx, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if channelInfo != "" && !containsString(peer.Channels, channelInfo) {
|
if channelInfo != "" && !containsString(peer.Channels, channelInfo) {
|
||||||
@@ -219,14 +227,17 @@ func (n *Nodes) updateDanteTx(source, subscriber *Node, channelInfo string, flow
|
|||||||
peer.Status[channelInfo] = flowStatus.String()
|
peer.Status[channelInfo] = flowStatus.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(source.DanteTx, func(i, j int) bool {
|
sort.Slice(flows.Tx, func(i, j int) bool {
|
||||||
return sortorder.NaturalLess(source.DanteTx[i].Node.DisplayName(), source.DanteTx[j].Node.DisplayName())
|
return sortorder.NaturalLess(flows.Tx[i].Node.DisplayName(), flows.Tx[j].Node.DisplayName())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nodes) updateDanteRx(subscriber, source *Node, channelInfo string, flowStatus DanteFlowStatus) {
|
func (n *Nodes) updateDanteRx(subscriber, source *Node, channelInfo string, flowStatus DanteFlowStatus, now time.Time) {
|
||||||
|
flows := n.ensureDanteFlows(subscriber)
|
||||||
|
flows.lastSeen = now
|
||||||
|
|
||||||
var peer *DantePeer
|
var peer *DantePeer
|
||||||
for _, p := range subscriber.DanteRx {
|
for _, p := range flows.Rx {
|
||||||
if p.Node == source {
|
if p.Node == source {
|
||||||
peer = p
|
peer = p
|
||||||
break
|
break
|
||||||
@@ -237,7 +248,7 @@ func (n *Nodes) updateDanteRx(subscriber, source *Node, channelInfo string, flow
|
|||||||
Node: source,
|
Node: source,
|
||||||
Status: map[string]string{},
|
Status: map[string]string{},
|
||||||
}
|
}
|
||||||
subscriber.DanteRx = append(subscriber.DanteRx, peer)
|
flows.Rx = append(flows.Rx, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if channelInfo != "" && !containsString(peer.Channels, channelInfo) {
|
if channelInfo != "" && !containsString(peer.Channels, channelInfo) {
|
||||||
@@ -248,78 +259,86 @@ func (n *Nodes) updateDanteRx(subscriber, source *Node, channelInfo string, flow
|
|||||||
peer.Status[channelInfo] = flowStatus.String()
|
peer.Status[channelInfo] = flowStatus.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(subscriber.DanteRx, func(i, j int) bool {
|
sort.Slice(flows.Rx, func(i, j int) bool {
|
||||||
return sortorder.NaturalLess(subscriber.DanteRx[i].Node.DisplayName(), subscriber.DanteRx[j].Node.DisplayName())
|
return sortorder.NaturalLess(flows.Rx[i].Node.DisplayName(), flows.Rx[j].Node.DisplayName())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nodes) expireDante() {
|
func (n *Nodes) expireDante() {
|
||||||
expireTime := time.Now().Add(-5 * time.Minute)
|
|
||||||
for _, node := range n.nodes {
|
for _, node := range n.nodes {
|
||||||
if !node.danteLastSeen.IsZero() && node.danteLastSeen.Before(expireTime) {
|
if node.DanteFlows != nil && node.DanteFlows.Expire(5*time.Minute) {
|
||||||
node.DanteTx = nil
|
node.DanteFlows = nil
|
||||||
node.DanteRx = nil
|
|
||||||
node.danteLastSeen = time.Time{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nodes) mergeDante(keep, merge *Node) {
|
func (n *Nodes) mergeDante(keep, merge *Node) {
|
||||||
for _, peer := range merge.DanteTx {
|
if merge.DanteFlows == nil {
|
||||||
var existing *DantePeer
|
return
|
||||||
for _, p := range keep.DanteTx {
|
|
||||||
if p.Node == peer.Node {
|
|
||||||
existing = p
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existing == nil {
|
|
||||||
keep.DanteTx = append(keep.DanteTx, peer)
|
|
||||||
} else {
|
|
||||||
for _, ch := range peer.Channels {
|
|
||||||
if !containsString(existing.Channels, ch) {
|
|
||||||
existing.Channels = append(existing.Channels, ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for ch, status := range peer.Status {
|
|
||||||
existing.Status[ch] = status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, peer := range merge.DanteRx {
|
if keep.DanteFlows == nil {
|
||||||
var existing *DantePeer
|
keep.DanteFlows = merge.DanteFlows
|
||||||
for _, p := range keep.DanteRx {
|
} else {
|
||||||
if p.Node == peer.Node {
|
for _, peer := range merge.DanteFlows.Tx {
|
||||||
existing = p
|
var existing *DantePeer
|
||||||
break
|
for _, p := range keep.DanteFlows.Tx {
|
||||||
}
|
if p.Node == peer.Node {
|
||||||
}
|
existing = p
|
||||||
if existing == nil {
|
break
|
||||||
keep.DanteRx = append(keep.DanteRx, peer)
|
|
||||||
} else {
|
|
||||||
for _, ch := range peer.Channels {
|
|
||||||
if !containsString(existing.Channels, ch) {
|
|
||||||
existing.Channels = append(existing.Channels, ch)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for ch, status := range peer.Status {
|
if existing == nil {
|
||||||
existing.Status[ch] = status
|
keep.DanteFlows.Tx = append(keep.DanteFlows.Tx, peer)
|
||||||
|
} else {
|
||||||
|
for _, ch := range peer.Channels {
|
||||||
|
if !containsString(existing.Channels, ch) {
|
||||||
|
existing.Channels = append(existing.Channels, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ch, status := range peer.Status {
|
||||||
|
existing.Status[ch] = status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if merge.danteLastSeen.After(keep.danteLastSeen) {
|
for _, peer := range merge.DanteFlows.Rx {
|
||||||
keep.danteLastSeen = merge.danteLastSeen
|
var existing *DantePeer
|
||||||
|
for _, p := range keep.DanteFlows.Rx {
|
||||||
|
if p.Node == peer.Node {
|
||||||
|
existing = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existing == nil {
|
||||||
|
keep.DanteFlows.Rx = append(keep.DanteFlows.Rx, peer)
|
||||||
|
} else {
|
||||||
|
for _, ch := range peer.Channels {
|
||||||
|
if !containsString(existing.Channels, ch) {
|
||||||
|
existing.Channels = append(existing.Channels, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ch, status := range peer.Status {
|
||||||
|
existing.Status[ch] = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if merge.DanteFlows.lastSeen.After(keep.DanteFlows.lastSeen) {
|
||||||
|
keep.DanteFlows.lastSeen = merge.DanteFlows.lastSeen
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, node := range n.nodes {
|
for _, node := range n.nodes {
|
||||||
for _, peer := range node.DanteTx {
|
if node.DanteFlows == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, peer := range node.DanteFlows.Tx {
|
||||||
if peer.Node == merge {
|
if peer.Node == merge {
|
||||||
peer.Node = keep
|
peer.Node = keep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, peer := range node.DanteRx {
|
for _, peer := range node.DanteFlows.Rx {
|
||||||
if peer.Node == merge {
|
if peer.Node == merge {
|
||||||
peer.Node = keep
|
peer.Node = keep
|
||||||
}
|
}
|
||||||
@@ -340,7 +359,7 @@ func (n *Nodes) logDante() {
|
|||||||
var allNoChannelFlows []string
|
var allNoChannelFlows []string
|
||||||
|
|
||||||
for _, node := range n.nodes {
|
for _, node := range n.nodes {
|
||||||
if len(node.DanteTx) == 0 {
|
if node.DanteFlows == nil || len(node.DanteFlows.Tx) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sourceName := node.DisplayName()
|
sourceName := node.DisplayName()
|
||||||
@@ -348,7 +367,7 @@ func (n *Nodes) logDante() {
|
|||||||
sourceName = "??"
|
sourceName = "??"
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, peer := range node.DanteTx {
|
for _, peer := range node.DanteFlows.Tx {
|
||||||
subName := peer.Node.DisplayName()
|
subName := peer.Node.DisplayName()
|
||||||
if subName == "" {
|
if subName == "" {
|
||||||
subName = "??"
|
subName = "??"
|
||||||
@@ -459,7 +478,7 @@ func (t *Tendrils) queryDanteDeviceWithPort(ip net.IP, port int) *DanteDeviceInf
|
|||||||
|
|
||||||
if info.TxChannelCount > 0 {
|
if info.TxChannelCount > 0 {
|
||||||
t.queryDanteTxChannels(conn, ip, info.TxChannelCount)
|
t.queryDanteTxChannels(conn, ip, info.TxChannelCount)
|
||||||
t.nodes.UpdateDanteTxChannels(info.Name, ip, fmt.Sprintf("%d", info.TxChannelCount))
|
t.nodes.UpdateDanteTxChannels(info.Name, ip, info.TxChannelCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|||||||
8
nodes.go
8
nodes.go
@@ -487,12 +487,8 @@ func (n *Nodes) SetDanteClockMaster(ip net.IP) {
|
|||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
defer n.mu.Unlock()
|
defer n.mu.Unlock()
|
||||||
|
|
||||||
for _, node := range n.nodes {
|
|
||||||
node.IsDanteClockMaster = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if node := n.ipIndex[ip.String()]; node != nil {
|
if node := n.ipIndex[ip.String()]; node != nil {
|
||||||
node.IsDanteClockMaster = true
|
node.DanteClockMasterSeen = time.Now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,7 +505,7 @@ func (n *Nodes) logNode(node *Node) {
|
|||||||
if node.PoEBudget != nil {
|
if node.PoEBudget != nil {
|
||||||
tags = append(tags, fmt.Sprintf("poe:%.0f/%.0fW", node.PoEBudget.Power, node.PoEBudget.MaxPower))
|
tags = append(tags, fmt.Sprintf("poe:%.0f/%.0fW", node.PoEBudget.Power, node.PoEBudget.MaxPower))
|
||||||
}
|
}
|
||||||
if node.IsDanteClockMaster {
|
if node.IsDanteClockMaster() {
|
||||||
tags = append(tags, "dante-clock-master")
|
tags = append(tags, "dante-clock-master")
|
||||||
}
|
}
|
||||||
if len(tags) > 0 {
|
if len(tags) > 0 {
|
||||||
|
|||||||
@@ -1895,8 +1895,8 @@
|
|||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
const nodeId = node.id;
|
const nodeId = node.id;
|
||||||
const danteTx = node.dante_tx || [];
|
const danteTx = node.dante_flows?.tx || [];
|
||||||
const danteRx = node.dante_rx || [];
|
const danteRx = node.dante_flows?.rx || [];
|
||||||
|
|
||||||
if (danteTx.length === 0 && danteRx.length === 0) return;
|
if (danteTx.length === 0 && danteRx.length === 0) return;
|
||||||
|
|
||||||
|
|||||||
65
types.go
65
types.go
@@ -378,25 +378,40 @@ type PoEBudget struct {
|
|||||||
MaxPower float64 `json:"max_power"`
|
MaxPower float64 `json:"max_power"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DanteFlows struct {
|
||||||
|
Tx []*DantePeer `json:"tx,omitempty"`
|
||||||
|
Rx []*DantePeer `json:"rx,omitempty"`
|
||||||
|
lastSeen time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *DanteFlows) Expire(maxAge time.Duration) bool {
|
||||||
|
if f.lastSeen.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return time.Since(f.lastSeen) > maxAge
|
||||||
|
}
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Names NameSet `json:"names"`
|
Names NameSet `json:"names"`
|
||||||
Interfaces InterfaceMap `json:"interfaces"`
|
Interfaces InterfaceMap `json:"interfaces"`
|
||||||
MACTable map[string]string `json:"-"`
|
MACTable map[string]string `json:"-"`
|
||||||
PoEBudget *PoEBudget `json:"poe_budget,omitempty"`
|
PoEBudget *PoEBudget `json:"poe_budget,omitempty"`
|
||||||
IsDanteClockMaster bool `json:"is_dante_clock_master,omitempty"`
|
DanteTxChannels int `json:"dante_tx_channels,omitempty"`
|
||||||
DanteTxChannels string `json:"dante_tx_channels,omitempty"`
|
DanteClockMasterSeen time.Time `json:"-"`
|
||||||
MulticastGroups MulticastMembershipSet `json:"multicast_groups,omitempty"`
|
DanteFlows *DanteFlows `json:"dante_flows,omitempty"`
|
||||||
ArtNetInputs ArtNetUniverseSet `json:"artnet_inputs,omitempty"`
|
MulticastGroups MulticastMembershipSet `json:"multicast_groups,omitempty"`
|
||||||
ArtNetOutputs ArtNetUniverseSet `json:"artnet_outputs,omitempty"`
|
ArtNetInputs ArtNetUniverseSet `json:"artnet_inputs,omitempty"`
|
||||||
SACNOutputs SACNUniverseSet `json:"sacn_outputs,omitempty"`
|
ArtNetOutputs ArtNetUniverseSet `json:"artnet_outputs,omitempty"`
|
||||||
DanteTx []*DantePeer `json:"dante_tx,omitempty"`
|
SACNOutputs SACNUniverseSet `json:"sacn_outputs,omitempty"`
|
||||||
DanteRx []*DantePeer `json:"dante_rx,omitempty"`
|
Unreachable bool `json:"unreachable,omitempty"`
|
||||||
Unreachable bool `json:"unreachable,omitempty"`
|
errors *ErrorTracker
|
||||||
errors *ErrorTracker
|
pollTrigger chan struct{}
|
||||||
pollTrigger chan struct{}
|
cancelFunc context.CancelFunc
|
||||||
cancelFunc context.CancelFunc
|
}
|
||||||
danteLastSeen time.Time
|
|
||||||
|
func (n *Node) IsDanteClockMaster() bool {
|
||||||
|
return !n.DanteClockMasterSeen.IsZero() && time.Since(n.DanteClockMasterSeen) < 5*time.Minute
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) SetUnreachable(unreachable bool) bool {
|
func (n *Node) SetUnreachable(unreachable bool) bool {
|
||||||
@@ -507,13 +522,13 @@ func (n *Node) WithInterface(ifaceKey string) *Node {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
return &Node{
|
return &Node{
|
||||||
ID: n.ID,
|
ID: n.ID,
|
||||||
Names: n.Names,
|
Names: n.Names,
|
||||||
Interfaces: InterfaceMap{ifaceKey: iface},
|
Interfaces: InterfaceMap{ifaceKey: iface},
|
||||||
MACTable: n.MACTable,
|
MACTable: n.MACTable,
|
||||||
PoEBudget: n.PoEBudget,
|
PoEBudget: n.PoEBudget,
|
||||||
IsDanteClockMaster: n.IsDanteClockMaster,
|
DanteClockMasterSeen: n.DanteClockMasterSeen,
|
||||||
DanteTxChannels: n.DanteTxChannels,
|
DanteTxChannels: n.DanteTxChannels,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user