Add artmap polling to discover sACN unicast receivers
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
117
artmap.go
Normal file
117
artmap.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package tendrils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type artmapConfig struct {
|
||||
Targets []artmapTarget `json:"targets"`
|
||||
Mappings []artmapMapping `json:"mappings"`
|
||||
}
|
||||
|
||||
type artmapTarget struct {
|
||||
Universe artmapUniverse `json:"universe"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type artmapMapping struct {
|
||||
From artmapFromAddr `json:"from"`
|
||||
To artmapToAddr `json:"to"`
|
||||
}
|
||||
|
||||
type artmapUniverse struct {
|
||||
Protocol string `json:"protocol"`
|
||||
Number uint16 `json:"number"`
|
||||
}
|
||||
|
||||
type artmapFromAddr struct {
|
||||
Universe artmapUniverse `json:"universe"`
|
||||
ChannelStart int `json:"channel_start"`
|
||||
ChannelEnd int `json:"channel_end"`
|
||||
}
|
||||
|
||||
type artmapToAddr struct {
|
||||
Universe artmapUniverse `json:"universe"`
|
||||
ChannelStart int `json:"channel_start"`
|
||||
}
|
||||
|
||||
var artmapClient = &http.Client{Timeout: 2 * time.Second}
|
||||
|
||||
func (t *Tendrils) probeArtmap(ip net.IP) {
|
||||
url := fmt.Sprintf("http://%s:8080/api/config", ip)
|
||||
|
||||
resp, err := artmapClient.Get(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
server := resp.Header.Get("Server")
|
||||
if server != "artmap" {
|
||||
if server != "" {
|
||||
log.Printf("[artmap] unexpected server header ip=%s server=%q", ip, server)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var cfg artmapConfig
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cfg); err != nil {
|
||||
log.Printf("[artmap] decode error ip=%s: %v", ip, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[artmap] found ip=%s targets=%d mappings=%d", ip, len(cfg.Targets), len(cfg.Mappings))
|
||||
|
||||
t.processArtmapConfig(&cfg)
|
||||
}
|
||||
|
||||
func (t *Tendrils) processArtmapConfig(cfg *artmapConfig) {
|
||||
updated := false
|
||||
for _, target := range cfg.Targets {
|
||||
ip := parseTargetIP(target.Address)
|
||||
if ip == nil {
|
||||
log.Printf("[artmap] failed to parse target address %q", target.Address)
|
||||
continue
|
||||
}
|
||||
|
||||
node := t.nodes.GetByIP(ip)
|
||||
if node == nil {
|
||||
log.Printf("[artmap] target ip=%s not found as node", ip)
|
||||
continue
|
||||
}
|
||||
|
||||
universe := int(target.Universe.Number)
|
||||
switch target.Universe.Protocol {
|
||||
case "artnet":
|
||||
t.nodes.UpdateArtNet(node, []int{universe}, nil)
|
||||
log.Printf("[artmap] marked %s (%s) as artnet input for universe %d", node.DisplayName(), ip, universe)
|
||||
case "sacn":
|
||||
t.nodes.UpdateSACNUnicastInputs(node, []int{universe})
|
||||
log.Printf("[artmap] marked %s (%s) as sacn input for universe %d", node.DisplayName(), ip, universe)
|
||||
default:
|
||||
log.Printf("[artmap] unknown protocol %q for target %s", target.Universe.Protocol, target.Address)
|
||||
continue
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
if updated {
|
||||
t.NotifyUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
func parseTargetIP(addr string) net.IP {
|
||||
host := addr
|
||||
if idx := strings.LastIndex(addr, ":"); idx != -1 {
|
||||
h, _, err := net.SplitHostPort(addr)
|
||||
if err == nil {
|
||||
host = h
|
||||
}
|
||||
}
|
||||
return net.ParseIP(host)
|
||||
}
|
||||
Reference in New Issue
Block a user