Track DMX packet senders and expose via API
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
27
main.go
27
main.go
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/gopatchy/artnet"
|
"github.com/gopatchy/artnet"
|
||||||
"github.com/gopatchy/artmap/config"
|
"github.com/gopatchy/artmap/config"
|
||||||
"github.com/gopatchy/artmap/remap"
|
"github.com/gopatchy/artmap/remap"
|
||||||
|
"github.com/gopatchy/artmap/senders"
|
||||||
"github.com/gopatchy/sacn"
|
"github.com/gopatchy/sacn"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ type App struct {
|
|||||||
sacnSender *sacn.Sender
|
sacnSender *sacn.Sender
|
||||||
discovery *artnet.Discovery
|
discovery *artnet.Discovery
|
||||||
engine *remap.Engine
|
engine *remap.Engine
|
||||||
|
senders *senders.UniverseSenders
|
||||||
artTargets map[uint16]*net.UDPAddr
|
artTargets map[uint16]*net.UDPAddr
|
||||||
sacnTargets map[uint16][]*net.UDPAddr
|
sacnTargets map[uint16][]*net.UDPAddr
|
||||||
debug bool
|
debug bool
|
||||||
@@ -144,6 +146,7 @@ func main() {
|
|||||||
sacnSender: sacnSender,
|
sacnSender: sacnSender,
|
||||||
discovery: discovery,
|
discovery: discovery,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
|
senders: senders.New(),
|
||||||
artTargets: artTargets,
|
artTargets: artTargets,
|
||||||
sacnTargets: sacnTargets,
|
sacnTargets: sacnTargets,
|
||||||
debug: *debug,
|
debug: *debug,
|
||||||
@@ -216,6 +219,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Start sender expiration
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(10 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
app.senders.Expire(30 * time.Second)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Wait for interrupt
|
// Wait for interrupt
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
@@ -238,6 +250,7 @@ func (a *App) HandleDMX(src *net.UDPAddr, pkt *artnet.DMXPacket) {
|
|||||||
src.IP, pkt.Universe, pkt.Sequence, pkt.Length)
|
src.IP, pkt.Universe, pkt.Sequence, pkt.Length)
|
||||||
}
|
}
|
||||||
u := config.Universe{Protocol: config.ProtocolArtNet, Number: uint16(pkt.Universe)}
|
u := config.Universe{Protocol: config.ProtocolArtNet, Number: uint16(pkt.Universe)}
|
||||||
|
a.senders.Record(u, src.IP)
|
||||||
a.sendOutputs(a.engine.Remap(u, pkt.Data))
|
a.sendOutputs(a.engine.Remap(u, pkt.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +276,7 @@ func (a *App) HandleSACN(src *net.UDPAddr, pkt *sacn.DataPacket) {
|
|||||||
log.Printf("[<-sacn] src=%s universe=%d seq=%d", src.IP, pkt.Universe, pkt.Sequence)
|
log.Printf("[<-sacn] src=%s universe=%d seq=%d", src.IP, pkt.Universe, pkt.Sequence)
|
||||||
}
|
}
|
||||||
u := config.Universe{Protocol: config.ProtocolSACN, Number: pkt.Universe}
|
u := config.Universe{Protocol: config.ProtocolSACN, Number: pkt.Universe}
|
||||||
|
a.senders.Record(u, src.IP)
|
||||||
a.sendOutputs(a.engine.Remap(u, pkt.Data))
|
a.sendOutputs(a.engine.Remap(u, pkt.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,10 +328,21 @@ func (a *App) sendOutputs(outputs []remap.Output) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type configResponse struct {
|
||||||
|
Targets []config.Target `json:"targets"`
|
||||||
|
Mappings []config.Mapping `json:"mappings"`
|
||||||
|
Senders []senders.SenderInfo `json:"senders"`
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) handleConfig(w http.ResponseWriter, r *http.Request) {
|
func (a *App) handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Header().Set("Server", "artmap")
|
w.Header().Set("Server", "artmap")
|
||||||
json.NewEncoder(w).Encode(a.cfg)
|
resp := configResponse{
|
||||||
|
Targets: a.cfg.Targets,
|
||||||
|
Mappings: a.cfg.Mappings,
|
||||||
|
Senders: a.senders.GetAll(),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) printStats() {
|
func (a *App) printStats() {
|
||||||
|
|||||||
67
senders/senders.go
Normal file
67
senders/senders.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package senders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gopatchy/artmap/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SenderInfo struct {
|
||||||
|
Universe config.Universe `json:"universe"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type senderKey struct {
|
||||||
|
protocol config.Protocol
|
||||||
|
universe uint16
|
||||||
|
ip string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UniverseSenders struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
entries map[senderKey]time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *UniverseSenders {
|
||||||
|
return &UniverseSenders{
|
||||||
|
entries: map[senderKey]time.Time{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UniverseSenders) Record(u config.Universe, ip net.IP) {
|
||||||
|
key := senderKey{
|
||||||
|
protocol: u.Protocol,
|
||||||
|
universe: u.Number,
|
||||||
|
ip: ip.String(),
|
||||||
|
}
|
||||||
|
s.mu.Lock()
|
||||||
|
s.entries[key] = time.Now()
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UniverseSenders) Expire(maxAge time.Duration) {
|
||||||
|
cutoff := time.Now().Add(-maxAge)
|
||||||
|
s.mu.Lock()
|
||||||
|
for k, t := range s.entries {
|
||||||
|
if t.Before(cutoff) {
|
||||||
|
delete(s.entries, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UniverseSenders) GetAll() []SenderInfo {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
result := make([]SenderInfo, 0, len(s.entries))
|
||||||
|
for k := range s.entries {
|
||||||
|
result = append(result, SenderInfo{
|
||||||
|
Universe: config.Universe{Protocol: k.protocol, Number: k.universe},
|
||||||
|
IP: k.ip,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user