package main import ( "bytes" b64 "encoding/base64" "encoding/json" "flag" "fmt" "image" "image/color" _ "image/jpeg" "log" "os" "os/signal" "syscall" "time" rgbmatrix "gitea.wagshome.duckdns.org/publicWagsHome/go-rpi-rgb-led-matrix" "github.com/disintegration/imaging" MQTT "github.com/eclipse/paho.mqtt.golang" mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/fogleman/gg" ) // contents of struct mostly don't matter for toolkit. type incomingImage struct { Image string `json:"image"` } type Animation struct { ctx *gg.Context position image.Point dir image.Point height int width int stroke int image []image.Image updown int mqmsg chan mqtt.Message msg string countDown int } //flags from cmd line var ( rows = flag.Int("led-rows", 64, "number of rows supported") cols = flag.Int("led-cols", 64, "number of columns supported") parallel = flag.Int("led-parallel", 1, "number of daisy-chained panels") chain = flag.Int("led-chain", 2, "number of displays daisy-chained") brightness = flag.Int("brightness", 100, "brightness (0-100)") hardware_mapping = flag.String("led-gpio-mapping", "regular", "Name of GPIO mapping used.") show_refresh = flag.Bool("led-show-refresh", false, "Show refresh rate.") inverse_colors = flag.Bool("led-inverse", false, "Switch if your matrix has inverse colors on.") disable_hardware_pulsing = flag.Bool("led-no-hardware-pulse", false, "Don't use hardware pin-pulse generation.") led_slowdown_gpio = flag.Int("led-slowdown-gpio", 1, "GPIO pin slowdown") ) //listens on topic for messages func listener(mqMessages chan mqtt.Message) { opts := setupMQTT() client := MQTT.NewClient(opts) topic := "home/rgbboard" if token := client.Connect(); token.Wait() && token.Error() != nil { log.Println(fmt.Sprintf("failed to connect to mq: %s", token.Error().Error())) panic(token.Error()) } log.Println("connected") client.Subscribe(topic, 0, func(client mqtt.Client, msg mqtt.Message) { log.Println("Receiving ", string(msg.Payload()), " on topic: ", msg.Topic()) mqMessages <- msg }) } //animator is a wrapping function for go routine that can receive an mq channel func animator(tk *rgbmatrix.ToolKit, mqMessages chan mqtt.Message) { //Playanimation comes from the toolkit, all it takes is an animation struct tk.PlayAnimation(NewAnimation(image.Point{128, 64}, mqMessages)) } //connection lost management func onConnectionLostHandler(c MQTT.Client, reason error) { log.Fatalf(reason.Error()) } //setup connection to mqtt, topic to listen to, qos func setupMQTT() *mqtt.ClientOptions { opts := MQTT.NewClientOptions() opts.AddBroker(fmt.Sprintf("tcp://%s:%s", os.Getenv("MQTTBROKER"), os.Getenv("MQTTPORT"))) opts.SetUsername(os.Getenv("MQTTUSER")) opts.SetPassword(os.Getenv("MQTTPASSWORD")) opts.SetClientID("rgbboard") opts.SetAutoReconnect(true) opts.SetConnectionLostHandler(onConnectionLostHandler) return opts } //runs before main, parses flags func init() { flag.Parse() } //small function for handling fatal errors func fatal(err error) { if err != nil { panic(err) } } //initializes the struct for the an play animation function, this could all be dumped into function that's wrapping go routine if I wanted func NewAnimation(sz image.Point, mqMessages chan mqtt.Message) *Animation { reader, err := os.Open("marioUp.png") if err != nil { log.Fatal(err) } rawMario, _, err := image.Decode(reader) //marioUp := imaging.FlipH(imaging.Resize(rawMario, 16, 16, imaging.Lanczos)) marioUp := imaging.Resize(rawMario, 16, 16, imaging.Lanczos) reader, err = os.Open("marioDown.png") if err != nil { log.Fatal(err) } rawMario, _, err = image.Decode(reader) //marioDown := imaging.FlipH(imaging.Resize(rawMario, 16, 16, imaging.Lanczos)) marioDown := imaging.Resize(rawMario, 16, 16, imaging.Lanczos) images := []image.Image{marioUp, marioDown} return &Animation{ ctx: gg.NewContext(sz.X, sz.Y), dir: image.Point{1, 1}, height: 8, width: 8, stroke: 8, image: images, updown: 0, mqmsg: mqMessages, countDown: 5000, } } func appendImage(img string, a *Animation) { baseImage, _ := b64.StdEncoding.DecodeString(img) bigImage, _, _ := image.Decode(bytes.NewReader(baseImage)) a.image = append(a.image, imaging.Resize(bigImage, 64, 64, imaging.Lanczos)) } // what happens each frame, at an interval of 50 milliseconds func (a *Animation) Next() (image.Image, <-chan time.Time, error) { incoming := incomingImage{} defer a.updatePosition() a.ctx.SetColor(color.Black) a.ctx.Clear() if a.dir.X == 1 { a.ctx.DrawImageAnchored(a.image[a.updown], a.position.X, a.position.Y, 0.5, 0.5) } else { a.ctx.DrawImageAnchored(imaging.FlipH(a.image[a.updown]), a.position.X, a.position.Y, 0.5, 0.5) } if len(a.image) == 3 { if a.countDown > 0 { a.ctx.DrawImageAnchored(a.image[2], 0, 0, 0, 0) a.countDown -= 50 } else { a.image = a.image[:len(a.image)-1] a.countDown = 5000 } } a.ctx.SetColor(color.White) select { case msg := <-a.mqmsg: json.Unmarshal([]byte(string(msg.Payload())), &incoming) if incoming.Image == "" { a.msg = string(msg.Payload()) a.ctx.DrawString(a.msg, 5, 9) } else { go appendImage(incoming.Image, a) } default: } return a.ctx.Image(), time.After(time.Millisecond * 50), nil } //what mario does every frame func (a *Animation) updatePosition() { a.position.X += 1 * a.dir.X a.position.Y += 1 * a.dir.Y if a.position.Y+a.height > a.ctx.Height() { a.dir.Y = -1 a.updown = 0 } else if a.position.Y-a.height < 0 { a.updown = 1 a.dir.Y = 1 } if a.position.X+a.width > a.ctx.Width() { a.dir.X = -1 } else if a.position.X-a.width < 0 { a.dir.X = 1 } } func main() { config := &rgbmatrix.DefaultConfig config.Rows = *rows config.Cols = *cols config.Parallel = *parallel config.ChainLength = *chain config.Brightness = *brightness config.HardwareMapping = *hardware_mapping config.ShowRefreshRate = *show_refresh config.InverseColors = *inverse_colors config.DisableHardwarePulsing = *disable_hardware_pulsing var rt rgbmatrix.RunTimeConfig rt.Gpio_slowdown = *led_slowdown_gpio setupMQTT() m, err := rgbmatrix.NewRGBLedMatrix(config, rt) fatal(err) mqMessages := make(chan mqtt.Message) log.Println("making listener") go listener(mqMessages) tk := rgbmatrix.NewToolKit(m) defer tk.Close() log.Println("making animator") go animator(tk, mqMessages) log.Println("I guess I'm at the end") sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) done := make(chan bool, 1) go func() { sig := <-sigs fmt.Println() fmt.Println(sig) done <- true }() fmt.Println("awaiting signal") <-done fmt.Println("exiting") }