diff --git a/artnet/discovery.go b/artnet/discovery.go index 6bed6a4..a037499 100644 --- a/artnet/discovery.go +++ b/artnet/discovery.go @@ -21,9 +21,12 @@ type Node struct { // Discovery handles ArtNet node discovery type Discovery struct { sender *Sender + receiver *Receiver nodes map[string]*Node // keyed by IP string nodesMu sync.RWMutex localIP [4]byte + localMAC [6]byte + broadcast net.IP shortName string longName string inputUnivs []Universe // universes we transmit TO (SwIn) @@ -48,10 +51,7 @@ func NewDiscovery(sender *Sender, shortName, longName string, inputUnivs, output // Start begins periodic discovery func (d *Discovery) Start() { - // Get local IP - d.localIP = d.getLocalIP() - - // Start periodic poll + d.detectInterface() go d.pollLoop() } @@ -187,8 +187,9 @@ func (d *Discovery) HandlePollReply(src *net.UDPAddr, pkt *PollReplyPacket) { // HandlePoll processes an incoming ArtPoll and responds func (d *Discovery) HandlePoll(src *net.UDPAddr) { - d.sendPollReplies(src, d.inputUnivs, true) - d.sendPollReplies(src, d.outputUnivs, false) + dst := &net.UDPAddr{IP: d.broadcast, Port: Port} + d.sendPollReplies(dst, d.inputUnivs, true) + d.sendPollReplies(dst, d.outputUnivs, false) } func (d *Discovery) sendPollReplies(dst *net.UDPAddr, universes []Universe, isInput bool) { @@ -205,7 +206,9 @@ func (d *Discovery) sendPollReplies(dst *net.UDPAddr, universes []Universe, isIn end = len(univs) } chunk := univs[i:end] - err := d.sender.SendPollReply(dst, d.localIP, d.shortName, d.longName, chunk, isInput) + + pkt := BuildPollReplyPacket(d.localIP, d.localMAC, d.shortName, d.longName, chunk, isInput) + err := d.receiver.SendTo(pkt, dst) if err != nil { log.Printf("[->artnet] pollreply error: dst=%s err=%v", dst.IP, err) } @@ -250,24 +253,50 @@ func (d *Discovery) GetAllNodes() []*Node { return result } -func (d *Discovery) getLocalIP() [4]byte { - var result [4]byte +func (d *Discovery) detectInterface() { + d.broadcast = net.IPv4bcast - addrs, err := net.InterfaceAddrs() + ifaces, err := net.Interfaces() if err != nil { - return result + return } - for _, addr := range addrs { - if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ip4 := ipnet.IP.To4(); ip4 != nil { - copy(result[:], ip4) - return result + for _, iface := range ifaces { + if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 { + continue + } + + addrs, err := iface.Addrs() + if err != nil { + continue + } + + for _, addr := range addrs { + ipnet, ok := addr.(*net.IPNet) + if !ok { + continue } + + ip4 := ipnet.IP.To4() + if ip4 == nil { + continue + } + + copy(d.localIP[:], ip4) + + if len(iface.HardwareAddr) == 6 { + copy(d.localMAC[:], iface.HardwareAddr) + } + + bcast := make(net.IP, 4) + for i := 0; i < 4; i++ { + bcast[i] = ip4[i] | ^ipnet.Mask[i] + } + d.broadcast = bcast + + return } } - - return result } // SetLocalIP sets the local IP for PollReply responses @@ -276,3 +305,8 @@ func (d *Discovery) SetLocalIP(ip net.IP) { copy(d.localIP[:], ip4) } } + +// SetReceiver sets the receiver for sending poll replies from port 6454 +func (d *Discovery) SetReceiver(r *Receiver) { + d.receiver = r +} diff --git a/artnet/protocol.go b/artnet/protocol.go index 5a14d0f..60b9368 100644 --- a/artnet/protocol.go +++ b/artnet/protocol.go @@ -255,8 +255,8 @@ func BuildPollPacket() []byte { // BuildPollReplyPacket creates an ArtPollReply packet // isInput: true = we transmit to network (SwIn), false = we receive from network (SwOut) -func BuildPollReplyPacket(ip [4]byte, shortName, longName string, universes []Universe, isInput bool) []byte { - buf := make([]byte, 239) +func BuildPollReplyPacket(ip [4]byte, mac [6]byte, shortName, longName string, universes []Universe, isInput bool) []byte { + buf := make([]byte, 240) copy(buf[0:8], ArtNetID[:]) binary.LittleEndian.PutUint16(buf[8:10], OpPollReply) @@ -280,17 +280,20 @@ func BuildPollReplyPacket(ip [4]byte, shortName, longName string, universes []Un for i := 0; i < numPorts; i++ { if isInput { - buf[174+i] = 0x40 // Can input to Art-Net (we transmit) - buf[178+i] = 0x80 // Data received + buf[174+i] = 0x40 + buf[178+i] = 0x80 buf[186+i] = universes[i].Universe() } else { - buf[174+i] = 0x80 // Can output from Art-Net (we receive) - buf[182+i] = 0x80 // Data transmitted + buf[174+i] = 0x80 + buf[182+i] = 0x80 buf[190+i] = universes[i].Universe() } } - buf[200] = 0x00 // StNode + copy(buf[201:207], mac[:]) + copy(buf[207:211], ip[:]) + buf[211] = 1 + buf[212] = 0x08 return buf } diff --git a/artnet/receiver.go b/artnet/receiver.go index b2681f5..e16ef83 100644 --- a/artnet/receiver.go +++ b/artnet/receiver.go @@ -96,3 +96,9 @@ func (r *Receiver) handlePacket(src *net.UDPAddr, data []byte) { func (r *Receiver) LocalAddr() net.Addr { return r.conn.LocalAddr() } + +// SendTo sends a raw packet through the receiver's socket (port 6454) +func (r *Receiver) SendTo(data []byte, addr *net.UDPAddr) error { + _, err := r.conn.WriteToUDP(data, addr) + return err +} diff --git a/artnet/sender.go b/artnet/sender.go index 65938b8..672cd2d 100644 --- a/artnet/sender.go +++ b/artnet/sender.go @@ -55,13 +55,6 @@ func (s *Sender) SendPoll(addr *net.UDPAddr) error { return err } -// SendPollReply sends an ArtPollReply to a specific address -func (s *Sender) SendPollReply(addr *net.UDPAddr, localIP [4]byte, shortName, longName string, universes []Universe, isInput bool) error { - pkt := BuildPollReplyPacket(localIP, shortName, longName, universes, isInput) - _, err := s.conn.WriteToUDP(pkt, addr) - return err -} - // Close closes the sender func (s *Sender) Close() error { return s.conn.Close() diff --git a/main.go b/main.go index bdb2579..1012745 100644 --- a/main.go +++ b/main.go @@ -149,6 +149,7 @@ func main() { log.Fatalf("artnet receiver error: %v", err) } app.artReceiver = artReceiver + discovery.SetReceiver(artReceiver) artReceiver.Start() log.Printf("[artnet] listening addr=%s", addr) }