add hyperdeck and videohub discovery via tcp probing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ian Gulliver
2026-01-23 08:58:41 -08:00
parent 26f0aac587
commit e73c591e34
2 changed files with 82 additions and 6 deletions

71
bmd.go
View File

@@ -10,6 +10,10 @@ import (
) )
func (t *Tendrils) listenBMD(ctx context.Context, iface net.Interface) { func (t *Tendrils) listenBMD(ctx context.Context, iface net.Interface) {
go t.discoverATEMs(ctx, iface)
}
func (t *Tendrils) discoverATEMs(ctx context.Context, iface net.Interface) {
addrs, err := iface.Addrs() addrs, err := iface.Addrs()
if err != nil { if err != nil {
return return
@@ -197,3 +201,70 @@ func (t *Tendrils) parseATEMCommands(data []byte, sess *atemSession) {
offset += cmdLen offset += cmdLen
} }
} }
func (t *Tendrils) probeBMDDevice(ip net.IP) {
if name := t.probeHyperDeck(ip); name != "" {
t.nodes.Update(nil, nil, []net.IP{ip}, "", name, "bmd")
return
}
if name := t.probeVideoHub(ip); name != "" {
t.nodes.Update(nil, nil, []net.IP{ip}, "", name, "bmd")
return
}
}
func (t *Tendrils) probeHyperDeck(ip net.IP) string {
conn, err := net.DialTimeout("tcp", ip.String()+":9993", 500*time.Millisecond)
if err != nil {
return ""
}
defer conn.Close()
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil || n == 0 {
return ""
}
response := string(buf[:n])
for _, line := range strings.Split(response, "\r\n") {
if strings.HasPrefix(line, "model: ") {
model := strings.TrimPrefix(line, "model: ")
if t.DebugBMD {
log.Printf("[bmd] hyperdeck %s at %s", model, ip)
}
return model
}
}
return ""
}
func (t *Tendrils) probeVideoHub(ip net.IP) string {
conn, err := net.DialTimeout("tcp", ip.String()+":9990", 500*time.Millisecond)
if err != nil {
return ""
}
defer conn.Close()
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil || n == 0 {
return ""
}
response := string(buf[:n])
for _, line := range strings.Split(response, "\n") {
if strings.HasPrefix(line, "Model name: ") {
model := strings.TrimSpace(strings.TrimPrefix(line, "Model name: "))
if t.DebugBMD {
log.Printf("[bmd] videohub %s at %s", model, ip)
}
return model
}
}
return ""
}

17
snmp.go
View File

@@ -80,10 +80,6 @@ func (t *Tendrils) connectSNMP(ip net.IP) (*gosnmp.GoSNMP, error) {
} }
func (t *Tendrils) pollNode(node *Node) { func (t *Tendrils) pollNode(node *Node) {
if t.DisableSNMP {
return
}
t.nodes.mu.RLock() t.nodes.mu.RLock()
var ips []net.IP var ips []net.IP
for _, iface := range node.Interfaces { for _, iface := range node.Interfaces {
@@ -93,10 +89,19 @@ func (t *Tendrils) pollNode(node *Node) {
} }
} }
} }
nodeName := node.Name
t.nodes.mu.RUnlock() t.nodes.mu.RUnlock()
for _, ip := range ips { if !t.DisableSNMP {
t.querySNMPDevice(node, ip) for _, ip := range ips {
t.querySNMPDevice(node, ip)
}
}
if !t.DisableBMD && nodeName == "" {
for _, ip := range ips {
t.probeBMDDevice(ip)
}
} }
} }