package main import ( "encoding/binary" "fmt" "gen/ledd" "github.com/golang/protobuf/proto" "github.com/op/go-logging" "golang.org/x/exp/io/i2c" "gopkg.in/yaml.v2" "io/ioutil" "math" "net" "os" "os/signal" "syscall" ) // CONSTANTS const VERSION = "0.1" const RESOLUTION = 4095 const CHANNEL = 16 // STRUCTS type config struct { Name string Ledd struct { Host string Port int } Pca9685 struct { Device string Address int MinPulse uint16 MaxPulse uint16 Gamma float64 } } type LedDaemon struct { name string socket net.Conn data chan []byte } var log = logging.MustGetLogger("LedD") var ledDaemon *LedDaemon var pca9685 *PCA9685 var pwmMap map[int32]*Pwm var readConfig config func check(e error) { if e != nil { panic(e) } } func (daemon *LedDaemon) receive() { for { message := make([]byte, 8192) length, err := daemon.socket.Read(message) if err != nil { daemon.socket.Close() break } if length > 0 { log.Debugf("[%s] Read %d bytes", daemon.socket.RemoteAddr(), length) for i := 0; i < length; { msgLen := int(binary.BigEndian.Uint32(message[i : i+4])) log.Debugf("[%s] Reading protobuf after %d (len=%d)", daemon.socket.RemoteAddr(), i, msgLen) if i+msgLen+4 > len(message)-1 { log.Warningf("[%s] Buffer overflow. At least one message has been discarded!", daemon.socket.RemoteAddr()) break } backendMsg := &ledd.BackendWrapperMessage{} err = proto.Unmarshal(message[i+4:i+msgLen+4], backendMsg) i += msgLen + 4 if err != nil { log.Warningf("[%s] Couldn't decode protobuf msg!", daemon.name) continue } switch msg := backendMsg.Msg.(type) { case *ledd.BackendWrapperMessage_MLedd: daemon.name = msg.MLedd.Name log.Infof("Connection with %s established; backend registered", msg.MLedd.Name) case *ledd.BackendWrapperMessage_MSetChannel: for c, v := range msg.MSetChannel.Values { if c > CHANNEL { log.Warningf("[%s] Channel index %d is higher then this device's max channel index %d. Skipping.", daemon.name, c, CHANNEL) continue } v = math.Pow(v, 1/readConfig.Pca9685.Gamma) if pwm, ok := pwmMap[c]; ok { pwm.setPercentage(float32(v) * 100 * msg.MSetChannel.Correction[c]) } else { pwmMap[c] = pca9685.NewPwm(int(c)) pwm.setPercentage(float32(v) * 100 * msg.MSetChannel.Correction[c]) } } } } } } } func (daemon *LedDaemon) send() { defer daemon.socket.Close() for { select { case message, ok := <-daemon.data: if !ok { return } daemon.socket.Write(message) } } } func prepareProtobuf(data []byte) []byte { size := make([]byte, 4) binary.BigEndian.PutUint32(size, uint32(len(data))) return append(size, data...) } func main() { killSignals := make(chan os.Signal, 1) signal.Notify(killSignals, syscall.SIGINT, syscall.SIGTERM) log.Info("LedD PCA9685 backend", VERSION) content, err := ioutil.ReadFile("config.yaml") check(err) err = yaml.Unmarshal(content, &readConfig) check(err) i2cDevice, err := i2c.Open(&i2c.Devfs{Dev: readConfig.Pca9685.Device}, readConfig.Pca9685.Address) check(err) defer i2cDevice.Close() pca9685 = createPCA9685(i2cDevice, readConfig.Name, readConfig.Pca9685.MinPulse, readConfig.Pca9685.MaxPulse, logging.MustGetLogger("PCA9685")) pca9685.Init() pwmMap = make(map[int32]*Pwm, 0) conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", readConfig.Ledd.Host, readConfig.Ledd.Port)) check(err) ledDaemon = &LedDaemon{ socket: conn, data: make(chan []byte, 20), name: "?", } go ledDaemon.send() go ledDaemon.receive() wrapperMsg := &ledd.BackendWrapperMessage{ Msg: &ledd.BackendWrapperMessage_MBackend{ MBackend: &ledd.Backend{ Name: readConfig.Name, Channel: CHANNEL, Type: "PCA9685", Resolution: RESOLUTION, Version: VERSION, }, }, } data, err := proto.Marshal(wrapperMsg) check(err) ledDaemon.data <- prepareProtobuf(data) <-killSignals keys := make([]int, 0, len(pwmMap)) for c := range pwmMap { keys = append(keys, int(c)) } pca9685.SwitchOff(keys) }