2025-12-22 12:32:41 -08:00
|
|
|
package sacn
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"net"
|
|
|
|
|
"sync"
|
2026-01-27 22:11:42 -08:00
|
|
|
|
|
|
|
|
"golang.org/x/net/ipv4"
|
2025-12-22 12:32:41 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Sender sends sACN (E1.31) packets
|
|
|
|
|
type Sender struct {
|
|
|
|
|
conn *net.UDPConn
|
|
|
|
|
sourceName string
|
|
|
|
|
cid [16]byte
|
|
|
|
|
sequences map[uint16]uint8
|
|
|
|
|
seqMu sync.Mutex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewSender creates a new sACN sender
|
2026-01-27 22:11:42 -08:00
|
|
|
func NewSender(sourceName string, ifaceName string) (*Sender, error) {
|
2025-12-22 12:32:41 -08:00
|
|
|
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 22:11:42 -08:00
|
|
|
if ifaceName != "" {
|
|
|
|
|
iface, err := net.InterfaceByName(ifaceName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
conn.Close()
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
p := ipv4.NewPacketConn(conn)
|
|
|
|
|
if err := p.SetMulticastInterface(iface); err != nil {
|
|
|
|
|
conn.Close()
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 12:32:41 -08:00
|
|
|
var cid [16]byte
|
|
|
|
|
rand.Read(cid[:])
|
|
|
|
|
|
|
|
|
|
return &Sender{
|
|
|
|
|
conn: conn,
|
|
|
|
|
sourceName: sourceName,
|
|
|
|
|
cid: cid,
|
|
|
|
|
sequences: make(map[uint16]uint8),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SendDMX sends DMX data to a universe via multicast
|
|
|
|
|
func (s *Sender) SendDMX(universe uint16, data []byte) error {
|
|
|
|
|
s.seqMu.Lock()
|
|
|
|
|
seq := s.sequences[universe]
|
|
|
|
|
s.sequences[universe] = seq + 1
|
|
|
|
|
s.seqMu.Unlock()
|
|
|
|
|
|
|
|
|
|
pkt := BuildDataPacket(universe, seq, s.sourceName, s.cid, data)
|
|
|
|
|
addr := MulticastAddr(universe)
|
|
|
|
|
|
|
|
|
|
_, err := s.conn.WriteToUDP(pkt, addr)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SendDMXUnicast sends DMX data to a specific address
|
|
|
|
|
func (s *Sender) SendDMXUnicast(addr *net.UDPAddr, universe uint16, data []byte) error {
|
|
|
|
|
s.seqMu.Lock()
|
|
|
|
|
seq := s.sequences[universe]
|
|
|
|
|
s.sequences[universe] = seq + 1
|
|
|
|
|
s.seqMu.Unlock()
|
|
|
|
|
|
|
|
|
|
pkt := BuildDataPacket(universe, seq, s.sourceName, s.cid, data)
|
|
|
|
|
|
|
|
|
|
_, err := s.conn.WriteToUDP(pkt, addr)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close closes the sender
|
|
|
|
|
func (s *Sender) Close() error {
|
|
|
|
|
return s.conn.Close()
|
|
|
|
|
}
|