207 lines
4.1 KiB
Go
207 lines
4.1 KiB
Go
package models
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/tatsushid/go-fastping"
|
|
json_data "github.com/tecamino/tecamino-json_data"
|
|
json_dataModels "github.com/tecamino/tecamino-json_data/models"
|
|
"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:"-"`
|
|
Resubscribe *[]json_dataModels.Subscription `yaml:"-" json:"resubscribe"`
|
|
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()
|
|
}
|
|
}
|
|
|
|
// status bus
|
|
func (b *Bus) Status() bool {
|
|
return b.Watchdog != nil
|
|
}
|
|
|
|
// 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 (b *Bus) ParsePayload(c *gin.Context) error {
|
|
|
|
if err := c.BindJSON(b); err != nil {
|
|
r := json_data.NewResponse()
|
|
r.SetError()
|
|
r.SetMessage("json: " + err.Error())
|
|
c.JSON(http.StatusBadRequest, r)
|
|
return err
|
|
}
|
|
|
|
if b.Name == "" {
|
|
r := json_data.NewResponse()
|
|
r.SetError()
|
|
r.SetMessage("bus name missing")
|
|
c.JSON(http.StatusBadRequest, r)
|
|
return errors.New("bus name missing")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|