Add --artnet-broadcast flag for smarter broadcast handling
- Accepts comma-separated list of addresses or 'auto' - Auto-detection calculates broadcast from all network interfaces - Used as fallback when no per-universe target or discovered nodes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
# artmap configuration
|
# artmap configuration
|
||||||
# Run with: go run . -config config.toml [-artnet-listen :6454] [-sacn-pcap en0]
|
# Run with: go run . --config=config.toml [flags]
|
||||||
#
|
#
|
||||||
# Use -sacn-pcap <interface> to capture sACN via pcap instead of binding port 5568.
|
# Flags:
|
||||||
# This avoids port conflicts but requires root. Use "auto" for auto-detect.
|
# --artnet-listen=:6454 ArtNet listen address (empty to disable)
|
||||||
|
# --artnet-broadcast=auto Broadcast addresses (comma-separated, or 'auto')
|
||||||
|
# --sacn-pcap=en0 Use pcap for sACN (requires root, avoids port conflicts)
|
||||||
|
|
||||||
# Target addresses for ArtNet output universes
|
# Target addresses for ArtNet output universes
|
||||||
# Each output universe needs a target IP (broadcast or unicast)
|
# Each output universe needs a target IP (broadcast or unicast)
|
||||||
|
|||||||
92
main.go
92
main.go
@@ -27,12 +27,14 @@ type App struct {
|
|||||||
discovery *artnet.Discovery
|
discovery *artnet.Discovery
|
||||||
engine *remap.Engine
|
engine *remap.Engine
|
||||||
targets map[artnet.Universe]*net.UDPAddr
|
targets map[artnet.Universe]*net.UDPAddr
|
||||||
|
broadcasts []*net.UDPAddr
|
||||||
debug bool
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configPath := flag.String("config", "config.toml", "path to config file")
|
configPath := flag.String("config", "config.toml", "path to config file")
|
||||||
artnetListen := flag.String("artnet-listen", ":6454", "artnet listen address (empty to disable)")
|
artnetListen := flag.String("artnet-listen", ":6454", "artnet listen address (empty to disable)")
|
||||||
|
artnetBroadcast := flag.String("artnet-broadcast", "", "artnet broadcast addresses (comma-separated, or 'auto')")
|
||||||
sacnPcap := flag.String("sacn-pcap", "", "use pcap for sacn on interface (e.g. en0, eth0)")
|
sacnPcap := flag.String("sacn-pcap", "", "use pcap for sacn on interface (e.g. en0, eth0)")
|
||||||
debug := flag.Bool("debug", false, "log incoming/outgoing dmx packets")
|
debug := flag.Bool("debug", false, "log incoming/outgoing dmx packets")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -69,6 +71,27 @@ func main() {
|
|||||||
log.Printf(" target %s -> %s", t.Universe, addr)
|
log.Printf(" target %s -> %s", t.Universe, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse broadcast addresses
|
||||||
|
var broadcasts []*net.UDPAddr
|
||||||
|
if *artnetBroadcast != "" {
|
||||||
|
if *artnetBroadcast == "auto" {
|
||||||
|
broadcasts = detectBroadcastAddrs()
|
||||||
|
} else {
|
||||||
|
for _, addrStr := range strings.Split(*artnetBroadcast, ",") {
|
||||||
|
addrStr = strings.TrimSpace(addrStr)
|
||||||
|
addr, err := parseTargetAddr(addrStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("broadcast error: address=%q err=%v", addrStr, err)
|
||||||
|
}
|
||||||
|
broadcasts = append(broadcasts, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, addr := range broadcasts {
|
||||||
|
pollTargets[addr.String()] = addr
|
||||||
|
log.Printf(" broadcast %s", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Convert poll targets to slice
|
// Convert poll targets to slice
|
||||||
pollTargetSlice := make([]*net.UDPAddr, 0, len(pollTargets))
|
pollTargetSlice := make([]*net.UDPAddr, 0, len(pollTargets))
|
||||||
for _, addr := range pollTargets {
|
for _, addr := range pollTargets {
|
||||||
@@ -101,6 +124,7 @@ func main() {
|
|||||||
discovery: discovery,
|
discovery: discovery,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
targets: targets,
|
targets: targets,
|
||||||
|
broadcasts: broadcasts,
|
||||||
debug: *debug,
|
debug: *debug,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,6 +261,15 @@ func (a *App) sendOutputs(outputs []remap.Output) {
|
|||||||
if err := a.artSender.SendDMX(target, out.Universe, out.Data[:]); err != nil {
|
if err := a.artSender.SendDMX(target, out.Universe, out.Data[:]); err != nil {
|
||||||
log.Printf("[->artnet] error: dst=%s err=%v", target.IP, err)
|
log.Printf("[->artnet] error: dst=%s err=%v", target.IP, err)
|
||||||
}
|
}
|
||||||
|
} else if len(a.broadcasts) > 0 {
|
||||||
|
for _, bcast := range a.broadcasts {
|
||||||
|
if a.debug {
|
||||||
|
log.Printf("[->artnet] broadcast dst=%s universe=%s", bcast.IP, out.Universe)
|
||||||
|
}
|
||||||
|
if err := a.artSender.SendDMX(bcast, out.Universe, out.Data[:]); err != nil {
|
||||||
|
log.Printf("[->artnet] error: dst=%s err=%v", bcast.IP, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[->artnet] no target: universe=%s", out.Universe)
|
log.Printf("[->artnet] no target: universe=%s", out.Universe)
|
||||||
}
|
}
|
||||||
@@ -319,3 +352,62 @@ func parseTargetAddr(s string) (*net.UDPAddr, error) {
|
|||||||
|
|
||||||
return &net.UDPAddr{IP: ip, Port: port}, nil
|
return &net.UDPAddr{IP: ip, Port: port}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectBroadcastAddrs returns broadcast addresses for all network interfaces
|
||||||
|
func detectBroadcastAddrs() []*net.UDPAddr {
|
||||||
|
var addrs []*net.UDPAddr
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
// Skip loopback and down interfaces
|
||||||
|
if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaceAddrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range ifaceAddrs {
|
||||||
|
ipnet, ok := addr.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ip4 := ipnet.IP.To4()
|
||||||
|
if ip4 == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate broadcast address: IP | ~mask
|
||||||
|
mask := ipnet.Mask
|
||||||
|
if len(mask) != 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast := make(net.IP, 4)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
broadcast[i] = ip4[i] | ^mask[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
key := broadcast.String()
|
||||||
|
if seen[key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = true
|
||||||
|
|
||||||
|
addrs = append(addrs, &net.UDPAddr{
|
||||||
|
IP: broadcast,
|
||||||
|
Port: artnet.Port,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user