package models import ( "context" "fmt" "net" "time" "github.com/tatsushid/go-fastping" "github.com/tecamino/tecamino-logger/logging" ) // Art-Net constants const ( artPort = 6454 ) // Art-Net Interface type Bus struct { Name string `yaml:"name" json:"name"` Ip string `yaml:"ip" json:"ip"` Port *int `yaml:"port" json:"port,omitempty"` Data *DMX `yaml:"-" json:"-"` Watchdog context.CancelFunc `yaml:"-" json:"-"` Reachable bool `yaml:"-" json:"-"` } // adds new Art-Net interface to driver port 0 = 6454 (default art-net) func NewBus(name, ip string, port int) *Bus { if port == 0 { port = artPort } i := Bus{ Name: name, Ip: ip, Port: &port, Data: NewDMXUniverse(), } return &i } // get bus port from pointer func (b *Bus) GetPort() int { if b.Port == nil { return artPort } return *b.Port } // start polling dmx data in milliseconds 0 = aprox. 44Hertz func (b *Bus) Poll(interval time.Duration) error { if interval == 0 { interval = 23 } // Send packet over UDP conn, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: net.ParseIP(b.Ip), Port: *b.Port, }) if err != nil { return err } defer conn.Close() var errCount int for { go func() { for { if reached, _ := isUDPReachable(b.Ip); !reached { if errCount > 20 { break } else { errCount += 1 return } } else { errCount = 0 break } } }() _, err = conn.Write(NewArtNetPackage(b.Data)) if err != nil { return err } if errCount > 5 { return fmt.Errorf("device not reachable") } time.Sleep(23 * time.Millisecond) } } // start bus func (b *Bus) Start(log *logging.Logger) { var ctx context.Context ctx, b.Watchdog = context.WithCancel(context.Background()) go func() { var interval time.Duration = 10 * time.Second log.Info("bus.Start", fmt.Sprintf("device:%s ip:%s watchdog stopped", b.Name, b.Ip)) for { select { case <-ctx.Done(): log.Info("bus.Start", fmt.Sprintf("device:%s ip:%s watchdog stopped", b.Name, b.Ip)) b.Reachable = false return default: b.Reachable = false if reached, err := isUDPReachable(b.Ip); err != nil { log.Error("bus.Start", err) interval = 5 * time.Second } else if !reached { log.Error("bus.Start", fmt.Sprintf("device:%s ip:%s not reached", b.Name, b.Ip)) interval = 5 * time.Second } else { b.Reachable = true log.Info("bus.Start", fmt.Sprintf("device:%s ip:%s watchdog running", b.Name, b.Ip)) interval = 30 * time.Second } time.Sleep(interval) } } }() } // stop bus func (b *Bus) Stop() { if b.Watchdog != nil { b.Watchdog() } } // send dmx data func (b *Bus) SendData() error { if !b.Reachable { return nil } // Send packet over UDP conn, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: net.ParseIP(b.Ip), Port: *b.Port, }) if err != nil { return err } defer conn.Close() _, err = conn.Write(NewArtNetPackage(b.Data)) return err } func isUDPReachable(ip string) (recieved bool, err error) { p := fastping.NewPinger() ra, err := net.ResolveIPAddr("ip4:icmp", ip) if err != nil { return } p.AddIPAddr(ra) p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) { recieved = true } p.OnIdle = func() {} err = p.Run() return }