Files
artmap/main.go
Ian Gulliver d88ef4ccee Simplify config: encode channels in address strings
- from: "0.0.1:50-100" specifies channel range
- to: "0.0.1:50" specifies starting channel only (range implied by from)
- Remove from_channel, to_channel, count fields
- Support plain universe numbers with channels: "1:50-100"

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 09:37:09 -08:00

138 lines
3.3 KiB
Go

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")
listenPort := flag.Int("port", artnet.Port, "ArtNet listen port")
broadcastAddr := flag.String("broadcast", "2.255.255.255", "ArtNet broadcast address")
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 {
toEnd := m.To.ChannelStart + m.From.Count() - 1
log.Printf(" %s:%d-%d -> %s:%d-%d",
m.From.Universe, m.From.ChannelStart, m.From.ChannelEnd,
m.To.Universe, m.To.ChannelStart, toEnd)
}
// Create sender
sender, err := artnet.NewSender(*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(*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", *listenPort)
log.Printf("broadcasting to %s", *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()
}