Channel-level DMX remapping between ArtNet universes with: - TOML configuration with multiple address formats (net.subnet.universe, plain number) - ArtPoll discovery for output nodes - Configurable channel ranges for fixture spillover handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
104 lines
1.9 KiB
Go
104 lines
1.9 KiB
Go
package artnet
|
|
|
|
import (
|
|
"log"
|
|
"net"
|
|
)
|
|
|
|
// PacketHandler is called when a packet is received
|
|
type PacketHandler interface {
|
|
HandleDMX(src *net.UDPAddr, pkt *DMXPacket)
|
|
HandlePoll(src *net.UDPAddr, pkt *PollPacket)
|
|
HandlePollReply(src *net.UDPAddr, pkt *PollReplyPacket)
|
|
}
|
|
|
|
// Receiver listens for ArtNet packets
|
|
type Receiver struct {
|
|
conn *net.UDPConn
|
|
handler PacketHandler
|
|
done chan struct{}
|
|
}
|
|
|
|
// NewReceiver creates a new ArtNet receiver
|
|
func NewReceiver(port int, handler PacketHandler) (*Receiver, error) {
|
|
addr := &net.UDPAddr{
|
|
Port: port,
|
|
IP: net.IPv4zero,
|
|
}
|
|
|
|
conn, err := net.ListenUDP("udp4", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Receiver{
|
|
conn: conn,
|
|
handler: handler,
|
|
done: make(chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
// Start begins receiving packets
|
|
func (r *Receiver) Start() {
|
|
go r.receiveLoop()
|
|
}
|
|
|
|
// Stop stops the receiver
|
|
func (r *Receiver) Stop() {
|
|
close(r.done)
|
|
r.conn.Close()
|
|
}
|
|
|
|
func (r *Receiver) receiveLoop() {
|
|
buf := make([]byte, 1024)
|
|
|
|
for {
|
|
select {
|
|
case <-r.done:
|
|
return
|
|
default:
|
|
}
|
|
|
|
n, src, err := r.conn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
select {
|
|
case <-r.done:
|
|
return
|
|
default:
|
|
log.Printf("read error: %v", err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
r.handlePacket(src, buf[:n])
|
|
}
|
|
}
|
|
|
|
func (r *Receiver) handlePacket(src *net.UDPAddr, data []byte) {
|
|
opCode, pkt, err := ParsePacket(data)
|
|
if err != nil {
|
|
// Silently ignore invalid packets
|
|
return
|
|
}
|
|
|
|
switch opCode {
|
|
case OpDmx:
|
|
if dmx, ok := pkt.(*DMXPacket); ok {
|
|
r.handler.HandleDMX(src, dmx)
|
|
}
|
|
case OpPoll:
|
|
if poll, ok := pkt.(*PollPacket); ok {
|
|
r.handler.HandlePoll(src, poll)
|
|
}
|
|
case OpPollReply:
|
|
if reply, ok := pkt.(*PollReplyPacket); ok {
|
|
r.handler.HandlePollReply(src, reply)
|
|
}
|
|
}
|
|
}
|
|
|
|
// LocalAddr returns the local address the receiver is bound to
|
|
func (r *Receiver) LocalAddr() net.Addr {
|
|
return r.conn.LocalAddr()
|
|
}
|