diff --git a/config.example.toml b/config.example.toml index 07c99f6..b92d58c 100644 --- a/config.example.toml +++ b/config.example.toml @@ -5,9 +5,9 @@ # --artnet-listen=:6454 ArtNet listen address (empty to disable) # --artnet-broadcast=auto Broadcast addresses (comma-separated, or 'auto') -# Target addresses for ArtNet output universes -# Each output universe needs a target IP (broadcast or unicast) -# ArtPoll discovery will be sent to all unique target addresses +# Target addresses for output universes +# ArtNet: target IP (broadcast or unicast), ArtPoll discovery sent to all +# sACN: unicast targets sent in addition to multicast [[target]] universe = "artnet:0.0.0" address = "2.255.255.255" @@ -16,6 +16,10 @@ address = "2.255.255.255" universe = "artnet:0.0.5" address = "10.50.255.255" +[[target]] +universe = "sacn:1" +address = "192.168.1.100" + # Address format: # proto:universe[:channels] # diff --git a/main.go b/main.go index 00c9d16..e0e3c09 100644 --- a/main.go +++ b/main.go @@ -25,7 +25,8 @@ type App struct { sacnSender *sacn.Sender discovery *artnet.Discovery engine *remap.Engine - targets map[artnet.Universe]*net.UDPAddr + artTargets map[artnet.Universe]*net.UDPAddr + sacnTargets map[uint16][]*net.UDPAddr debug bool } @@ -53,19 +54,28 @@ func main() { } // Parse targets - targets := make(map[artnet.Universe]*net.UDPAddr) + artTargets := make(map[artnet.Universe]*net.UDPAddr) + sacnTargets := make(map[uint16][]*net.UDPAddr) pollTargets := make(map[string]*net.UDPAddr) // dedupe by address string for _, t := range cfg.Targets { - if t.Universe.Protocol != config.ProtocolArtNet { - continue // only artnet targets need addresses + switch t.Universe.Protocol { + case config.ProtocolArtNet: + addr, err := parseTargetAddr(t.Address, artnet.Port) + if err != nil { + log.Fatalf("target error: address=%q err=%v", t.Address, err) + } + artTargets[t.Universe.Universe] = addr + pollTargets[addr.String()] = addr + log.Printf("[config] target %s -> %s", t.Universe, addr) + case config.ProtocolSACN: + addr, err := parseTargetAddr(t.Address, sacn.Port) + if err != nil { + log.Fatalf("target error: address=%q err=%v", t.Address, err) + } + u := uint16(t.Universe.Universe) + sacnTargets[u] = append(sacnTargets[u], addr) + log.Printf("[config] target %s -> %s", t.Universe, addr) } - addr, err := parseTargetAddr(t.Address) - if err != nil { - log.Fatalf("target error: address=%q err=%v", t.Address, err) - } - targets[t.Universe.Universe] = addr - pollTargets[addr.String()] = addr - log.Printf("[config] target %s -> %s", t.Universe, addr) } // Parse broadcast addresses @@ -76,7 +86,7 @@ func main() { } else { for _, addrStr := range strings.Split(*artnetBroadcast, ",") { addrStr = strings.TrimSpace(addrStr) - addr, err := parseTargetAddr(addrStr) + addr, err := parseTargetAddr(addrStr, artnet.Port) if err != nil { log.Fatalf("broadcast error: address=%q err=%v", addrStr, err) } @@ -115,13 +125,14 @@ func main() { // Create app app := &App{ - cfg: cfg, - artSender: artSender, - sacnSender: sacnSender, - discovery: discovery, - engine: engine, - targets: targets, - debug: *debug, + cfg: cfg, + artSender: artSender, + sacnSender: sacnSender, + discovery: discovery, + engine: engine, + artTargets: artTargets, + sacnTargets: sacnTargets, + debug: *debug, } // Create ArtNet receiver if enabled @@ -208,16 +219,24 @@ func (a *App) sendOutputs(outputs []remap.Output) { for _, out := range outputs { switch out.Protocol { case config.ProtocolSACN: + u := uint16(out.Universe) if a.debug { - log.Printf("[->sacn] universe=%d", uint16(out.Universe)) + log.Printf("[->sacn] universe=%d", u) } - if err := a.sacnSender.SendDMX(uint16(out.Universe), out.Data[:]); err != nil { - log.Printf("[->sacn] error: universe=%d err=%v", uint16(out.Universe), err) + if err := a.sacnSender.SendDMX(u, out.Data[:]); err != nil { + log.Printf("[->sacn] error: universe=%d err=%v", u, err) + } + for _, target := range a.sacnTargets[u] { + if a.debug { + log.Printf("[->sacn] unicast dst=%s universe=%d", target.IP, u) + } + if err := a.sacnSender.SendDMXUnicast(target, u, out.Data[:]); err != nil { + log.Printf("[->sacn] error: dst=%s err=%v", target.IP, err) + } } default: // ArtNet - // Configured target takes priority over discovery - if target, ok := a.targets[out.Universe]; ok { + if target, ok := a.artTargets[out.Universe]; ok { if a.debug { log.Printf("[->artnet] dst=%s universe=%s", target.IP, out.Universe) } @@ -288,8 +307,8 @@ func parseListenAddr(s string) (*net.UDPAddr, error) { // parseTargetAddr parses target address formats: // - "host:port" -> specific host and port -// - "host" -> specific host, default ArtNet port -func parseTargetAddr(s string) (*net.UDPAddr, error) { +// - "host" -> specific host, default port +func parseTargetAddr(s string, defaultPort int) (*net.UDPAddr, error) { var host string var port int @@ -305,7 +324,7 @@ func parseTargetAddr(s string) (*net.UDPAddr, error) { } } else { host = s - port = artnet.Port + port = defaultPort } ip := net.ParseIP(host)