add blackmagic atem discovery via udp 9910
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
111
bmd.go
Normal file
111
bmd.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package tendrils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Tendrils) listenBMD(ctx context.Context, iface net.Interface) {
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var srcIP net.IP
|
||||||
|
var broadcast net.IP
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.To4() != nil {
|
||||||
|
srcIP = ipnet.IP.To4()
|
||||||
|
mask := ipnet.Mask
|
||||||
|
broadcast = make(net.IP, 4)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
broadcast[i] = srcIP[i] | ^mask[i]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if srcIP == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: srcIP, Port: 0})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
t.sendATEMDiscovery(conn, broadcast, iface.Name)
|
||||||
|
|
||||||
|
go t.receiveATEMResponses(ctx, conn, iface.Name)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
t.sendATEMDiscovery(conn, broadcast, iface.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tendrils) sendATEMDiscovery(conn *net.UDPConn, broadcast net.IP, ifaceName string) {
|
||||||
|
// ATEM protocol hello packet
|
||||||
|
// Flag 0x10 (hello), length 20 bytes
|
||||||
|
packet := []byte{
|
||||||
|
0x10, 0x14, // Flags (0x10 = hello) + length (20 = 0x14)
|
||||||
|
0x00, 0x00, // Session ID
|
||||||
|
0x00, 0x00, // Ack number
|
||||||
|
0x00, 0x00, // Local sequence
|
||||||
|
0x00, 0x00, // Remote sequence
|
||||||
|
0x00, 0x00, // Unknown
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Client hello data
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.WriteToUDP(packet, &net.UDPAddr{IP: broadcast, Port: 9910})
|
||||||
|
|
||||||
|
if t.DebugBMD {
|
||||||
|
log.Printf("[bmd] %s: sent atem discovery to %s", ifaceName, broadcast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tendrils) receiveATEMResponses(ctx context.Context, conn *net.UDPConn, ifaceName string) {
|
||||||
|
seen := map[string]bool{}
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||||
|
n, src, err := conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 12 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ipKey := src.IP.String()
|
||||||
|
if seen[ipKey] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[ipKey] = true
|
||||||
|
|
||||||
|
if t.DebugBMD {
|
||||||
|
log.Printf("[bmd] %s: atem at %s", ifaceName, src.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.nodes.Update(nil, nil, []net.IP{src.IP}, "", "atem", "bmd")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ func main() {
|
|||||||
noMDNS := flag.Bool("no-mdns", false, "disable mDNS discovery")
|
noMDNS := flag.Bool("no-mdns", false, "disable mDNS discovery")
|
||||||
noArtNet := flag.Bool("no-artnet", false, "disable Art-Net discovery")
|
noArtNet := flag.Bool("no-artnet", false, "disable Art-Net discovery")
|
||||||
noDante := flag.Bool("no-dante", false, "disable Dante discovery")
|
noDante := flag.Bool("no-dante", false, "disable Dante discovery")
|
||||||
|
noBMD := flag.Bool("no-bmd", false, "disable Blackmagic discovery")
|
||||||
logEvents := flag.Bool("log-events", false, "log node events")
|
logEvents := flag.Bool("log-events", false, "log node events")
|
||||||
logNodes := flag.Bool("log-nodes", false, "log full node details on changes")
|
logNodes := flag.Bool("log-nodes", false, "log full node details on changes")
|
||||||
debugARP := flag.Bool("debug-arp", false, "debug ARP discovery")
|
debugARP := flag.Bool("debug-arp", false, "debug ARP discovery")
|
||||||
@@ -24,6 +25,7 @@ func main() {
|
|||||||
debugMDNS := flag.Bool("debug-mdns", false, "debug mDNS discovery")
|
debugMDNS := flag.Bool("debug-mdns", false, "debug mDNS discovery")
|
||||||
debugArtNet := flag.Bool("debug-artnet", false, "debug Art-Net discovery")
|
debugArtNet := flag.Bool("debug-artnet", false, "debug Art-Net discovery")
|
||||||
debugDante := flag.Bool("debug-dante", false, "debug Dante discovery")
|
debugDante := flag.Bool("debug-dante", false, "debug Dante discovery")
|
||||||
|
debugBMD := flag.Bool("debug-bmd", false, "debug Blackmagic discovery")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
t := tendrils.New()
|
t := tendrils.New()
|
||||||
@@ -35,6 +37,7 @@ func main() {
|
|||||||
t.DisableMDNS = *noMDNS
|
t.DisableMDNS = *noMDNS
|
||||||
t.DisableArtNet = *noArtNet
|
t.DisableArtNet = *noArtNet
|
||||||
t.DisableDante = *noDante
|
t.DisableDante = *noDante
|
||||||
|
t.DisableBMD = *noBMD
|
||||||
t.LogEvents = *logEvents
|
t.LogEvents = *logEvents
|
||||||
t.LogNodes = *logNodes
|
t.LogNodes = *logNodes
|
||||||
t.DebugARP = *debugARP
|
t.DebugARP = *debugARP
|
||||||
@@ -44,5 +47,6 @@ func main() {
|
|||||||
t.DebugMDNS = *debugMDNS
|
t.DebugMDNS = *debugMDNS
|
||||||
t.DebugArtNet = *debugArtNet
|
t.DebugArtNet = *debugArtNet
|
||||||
t.DebugDante = *debugDante
|
t.DebugDante = *debugDante
|
||||||
|
t.DebugBMD = *debugBMD
|
||||||
t.Run()
|
t.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type Tendrils struct {
|
|||||||
DisableMDNS bool
|
DisableMDNS bool
|
||||||
DisableArtNet bool
|
DisableArtNet bool
|
||||||
DisableDante bool
|
DisableDante bool
|
||||||
|
DisableBMD bool
|
||||||
LogEvents bool
|
LogEvents bool
|
||||||
LogNodes bool
|
LogNodes bool
|
||||||
DebugARP bool
|
DebugARP bool
|
||||||
@@ -32,6 +33,7 @@ type Tendrils struct {
|
|||||||
DebugMDNS bool
|
DebugMDNS bool
|
||||||
DebugArtNet bool
|
DebugArtNet bool
|
||||||
DebugDante bool
|
DebugDante bool
|
||||||
|
DebugBMD bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Tendrils {
|
func New() *Tendrils {
|
||||||
@@ -200,4 +202,7 @@ func (t *Tendrils) startInterface(ctx context.Context, iface net.Interface) {
|
|||||||
if !t.DisableDante {
|
if !t.DisableDante {
|
||||||
go t.listenDante(ctx, iface)
|
go t.listenDante(ctx, iface)
|
||||||
}
|
}
|
||||||
|
if !t.DisableBMD {
|
||||||
|
go t.listenBMD(ctx, iface)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user