Replace --broadcast flag with per-universe target config

- Remove --broadcast CLI flag
- Add [[target]] config section for per-universe destination addresses
- Each ArtNet output universe can have its own target IP (broadcast or unicast)
- ArtPoll discovery broadcasts to all unique target addresses
- Falls back to configured target when no nodes discovered for universe

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ian Gulliver
2025-12-22 18:06:33 -08:00
parent b0e9ecdee7
commit 86403f1ff8
5 changed files with 162 additions and 68 deletions

View File

@@ -20,25 +20,27 @@ type Node struct {
// Discovery handles ArtNet node discovery
type Discovery struct {
sender *Sender
nodes map[string]*Node // keyed by IP string
nodesMu sync.RWMutex
localIP [4]byte
shortName string
longName string
universes []Universe
done chan struct{}
sender *Sender
nodes map[string]*Node // keyed by IP string
nodesMu sync.RWMutex
localIP [4]byte
shortName string
longName string
universes []Universe
pollTargets []*net.UDPAddr
done chan struct{}
}
// NewDiscovery creates a new discovery handler
func NewDiscovery(sender *Sender, shortName, longName string, universes []Universe) *Discovery {
func NewDiscovery(sender *Sender, shortName, longName string, universes []Universe, pollTargets []*net.UDPAddr) *Discovery {
return &Discovery{
sender: sender,
nodes: make(map[string]*Node),
shortName: shortName,
longName: longName,
universes: universes,
done: make(chan struct{}),
sender: sender,
nodes: make(map[string]*Node),
shortName: shortName,
longName: longName,
universes: universes,
pollTargets: pollTargets,
done: make(chan struct{}),
}
}
@@ -57,10 +59,8 @@ func (d *Discovery) Stop() {
}
func (d *Discovery) pollLoop() {
// Send initial poll
if err := d.sender.SendPoll(); err != nil {
log.Printf("failed to send ArtPoll: %v", err)
}
// Send initial poll to all targets
d.sendPolls()
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
@@ -73,15 +73,21 @@ func (d *Discovery) pollLoop() {
case <-d.done:
return
case <-ticker.C:
if err := d.sender.SendPoll(); err != nil {
log.Printf("failed to send ArtPoll: %v", err)
}
d.sendPolls()
case <-cleanupTicker.C:
d.cleanup()
}
}
}
func (d *Discovery) sendPolls() {
for _, target := range d.pollTargets {
if err := d.sender.SendPoll(target); err != nil {
log.Printf("failed to send ArtPoll to %s: %v", target.IP, err)
}
}
}
func (d *Discovery) cleanup() {
d.nodesMu.Lock()
defer d.nodesMu.Unlock()

View File

@@ -7,14 +7,13 @@ import (
// Sender sends ArtNet packets
type Sender struct {
conn *net.UDPConn
broadcastAddr *net.UDPAddr
sequences map[Universe]uint8
seqMu sync.Mutex
conn *net.UDPConn
sequences map[Universe]uint8
seqMu sync.Mutex
}
// NewSender creates a new ArtNet sender
func NewSender(broadcastAddr string) (*Sender, error) {
func NewSender() (*Sender, error) {
// Create a UDP socket for sending
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
@@ -27,22 +26,9 @@ func NewSender(broadcastAddr string) (*Sender, error) {
return nil, err
}
broadcast, err := net.ResolveUDPAddr("udp4", broadcastAddr+":"+string(rune(Port)))
if err != nil {
// Try parsing as IP:Port
broadcast, err = net.ResolveUDPAddr("udp4", broadcastAddr)
if err != nil {
broadcast = &net.UDPAddr{
IP: net.ParseIP(broadcastAddr),
Port: Port,
}
}
}
return &Sender{
conn: conn,
broadcastAddr: broadcast,
sequences: make(map[Universe]uint8),
conn: conn,
sequences: make(map[Universe]uint8),
}, nil
}
@@ -62,15 +48,10 @@ func (s *Sender) SendDMX(addr *net.UDPAddr, universe Universe, data []byte) erro
return err
}
// SendDMXBroadcast sends a DMX packet to the broadcast address
func (s *Sender) SendDMXBroadcast(universe Universe, data []byte) error {
return s.SendDMX(s.broadcastAddr, universe, data)
}
// SendPoll sends an ArtPoll packet to the broadcast address
func (s *Sender) SendPoll() error {
// SendPoll sends an ArtPoll packet to the specified address
func (s *Sender) SendPoll(addr *net.UDPAddr) error {
pkt := BuildPollPacket()
_, err := s.conn.WriteToUDP(pkt, s.broadcastAddr)
_, err := s.conn.WriteToUDP(pkt, addr)
return err
}
@@ -85,8 +66,3 @@ func (s *Sender) SendPollReply(addr *net.UDPAddr, localIP [4]byte, shortName, lo
func (s *Sender) Close() error {
return s.conn.Close()
}
// BroadcastAddr returns the configured broadcast address
func (s *Sender) BroadcastAddr() *net.UDPAddr {
return s.broadcastAddr
}