Initial implementation of ArtNet remapping proxy
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>
This commit is contained in:
138
main.go
Normal file
138
main.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/gopatchy/artmap/artnet"
|
||||
"github.com/gopatchy/artmap/config"
|
||||
"github.com/gopatchy/artmap/remap"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
cfg *config.Config
|
||||
receiver *artnet.Receiver
|
||||
sender *artnet.Sender
|
||||
discovery *artnet.Discovery
|
||||
engine *remap.Engine
|
||||
}
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config", "config.toml", "path to config file")
|
||||
flag.Parse()
|
||||
|
||||
// Load config
|
||||
cfg, err := config.Load(*configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("loaded %d mappings", len(cfg.Mappings))
|
||||
|
||||
// Create remapping engine
|
||||
engine := remap.NewEngine(cfg.Normalize())
|
||||
|
||||
// Log mappings
|
||||
for _, m := range cfg.Mappings {
|
||||
if m.Count == 512 && m.FromChannel == 1 {
|
||||
log.Printf(" %s -> %s (all channels)", m.From.Universe, m.To.Universe)
|
||||
} else {
|
||||
log.Printf(" %s[%d-%d] -> %s[%d-%d]",
|
||||
m.From.Universe, m.FromChannel, m.FromChannel+m.Count-1,
|
||||
m.To.Universe, m.ToChannel, m.ToChannel+m.Count-1)
|
||||
}
|
||||
}
|
||||
|
||||
// Create sender
|
||||
sender, err := artnet.NewSender(cfg.Settings.BroadcastAddr)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create sender: %v", err)
|
||||
}
|
||||
defer sender.Close()
|
||||
|
||||
// Create discovery
|
||||
destUniverses := engine.DestUniverses()
|
||||
discovery := artnet.NewDiscovery(sender, "artmap", "ArtNet Remapping Proxy", destUniverses)
|
||||
|
||||
// Create app
|
||||
app := &App{
|
||||
cfg: cfg,
|
||||
sender: sender,
|
||||
discovery: discovery,
|
||||
engine: engine,
|
||||
}
|
||||
|
||||
// Create receiver
|
||||
receiver, err := artnet.NewReceiver(cfg.Settings.ListenPort, app)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create receiver: %v", err)
|
||||
}
|
||||
app.receiver = receiver
|
||||
|
||||
// Start everything
|
||||
receiver.Start()
|
||||
discovery.Start()
|
||||
|
||||
log.Printf("listening on port %d", cfg.Settings.ListenPort)
|
||||
log.Printf("broadcasting to %s", cfg.Settings.BroadcastAddr)
|
||||
|
||||
// Wait for interrupt
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
log.Println("shutting down...")
|
||||
receiver.Stop()
|
||||
discovery.Stop()
|
||||
}
|
||||
|
||||
// HandleDMX implements artnet.PacketHandler
|
||||
func (a *App) HandleDMX(src *net.UDPAddr, pkt *artnet.DMXPacket) {
|
||||
// Apply remapping
|
||||
outputs := a.engine.Remap(pkt.Universe, pkt.Data)
|
||||
|
||||
// Send remapped outputs
|
||||
for _, out := range outputs {
|
||||
// Find nodes for this universe
|
||||
nodes := a.discovery.GetNodesForUniverse(out.Universe)
|
||||
|
||||
if len(nodes) > 0 {
|
||||
// Send to discovered nodes
|
||||
for _, node := range nodes {
|
||||
addr := &net.UDPAddr{
|
||||
IP: node.IP,
|
||||
Port: int(node.Port),
|
||||
}
|
||||
if err := a.sender.SendDMX(addr, out.Universe, out.Data[:]); err != nil {
|
||||
log.Printf("failed to send to %s: %v", node.IP, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Broadcast if no nodes discovered
|
||||
if err := a.sender.SendDMXBroadcast(out.Universe, out.Data[:]); err != nil {
|
||||
log.Printf("failed to broadcast: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HandlePoll implements artnet.PacketHandler
|
||||
func (a *App) HandlePoll(src *net.UDPAddr, pkt *artnet.PollPacket) {
|
||||
a.discovery.HandlePoll(src)
|
||||
}
|
||||
|
||||
// HandlePollReply implements artnet.PacketHandler
|
||||
func (a *App) HandlePollReply(src *net.UDPAddr, pkt *artnet.PollReplyPacket) {
|
||||
a.discovery.HandlePollReply(src, pkt)
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.SetFlags(log.Ltime | log.Lmicroseconds)
|
||||
fmt.Println("artmap - ArtNet Remapping Proxy")
|
||||
fmt.Println()
|
||||
}
|
||||
Reference in New Issue
Block a user