@ -1,18 +1,47 @@
package main
package main
import (
import (
"bytes"
b64 "encoding/base64"
"encoding/json"
"flag"
"flag"
"fmt"
"fmt"
"image"
"image/color"
_ "image/jpeg"
_ "image/jpeg"
"log"
"log"
"os"
"os"
"os/signal"
"os/signal"
"syscall"
"syscall"
"time"
rgbmatrix "gitea.wagshome.duckdns.org/publicWagsHome/go-rpi-rgb-led-matrix"
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"
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
images map [ string ] image . Image
updown string
mqmsg chan mqtt . Message
msg string
countDown int
}
//flags from cmd line
//flags from cmd line
var (
var (
rows = flag . Int ( "led-rows" , 64 , "number of rows supported" )
rows = flag . Int ( "led-rows" , 64 , "number of rows supported" )
@ -27,6 +56,47 @@ var (
led_slowdown_gpio = flag . Int ( "led-slowdown-gpio" , 1 , "GPIO pin slowdown" )
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
//runs before main, parses flags
func init ( ) {
func init ( ) {
flag . Parse ( )
flag . Parse ( )
@ -39,6 +109,107 @@ func fatal(err error) {
}
}
}
}
//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 {
imageMap := make ( map [ string ] image . Image )
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 )
imageMap [ "marioUp" ] = marioUp
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 )
imageMap [ "marioDown" ] = marioDown
imageSlice := [ ] 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 : imageSlice ,
images : imageMap ,
updown : "marioUp" ,
mqmsg : mqMessages ,
countDown : 5000 ,
}
}
func appendImage ( img string , a * Animation ) {
baseImage , _ := b64 . StdEncoding . DecodeString ( img )
bigImage , _ , _ := image . Decode ( bytes . NewReader ( baseImage ) )
a . images [ "doorbell" ] = 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 { }
a . animateMario ( )
if a . images [ "doorbell" ] != nil {
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]
delete ( a . images , "doorbell" )
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
}
func ( a * Animation ) animateMario ( ) {
defer a . updateMarioPosition ( )
a . ctx . SetColor ( color . Black )
a . ctx . Clear ( )
if a . dir . X == 1 {
a . ctx . DrawImageAnchored ( a . images [ a . updown ] , a . position . X , a . position . Y , 0.5 , 0.5 )
} else {
a . ctx . DrawImageAnchored ( imaging . FlipH ( a . images [ a . updown ] ) , a . position . X , a . position . Y , 0.5 , 0.5 )
}
}
//what mario does every frame
func ( a * Animation ) updateMarioPosition ( ) {
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 = "marioUp"
} else if a . position . Y - a . height < 0 {
a . updown = "marioDown"
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 ( ) {
func main ( ) {
config := & rgbmatrix . DefaultConfig
config := & rgbmatrix . DefaultConfig
config . Rows = * rows
config . Rows = * rows
@ -62,7 +233,7 @@ func main() {
tk := rgbmatrix . NewToolKit ( m )
tk := rgbmatrix . NewToolKit ( m )
defer tk . Close ( )
defer tk . Close ( )
log . Println ( "making animator" )
log . Println ( "making animator" )
go orchestr ator( tk , mqMessages )
go anim ator( tk , mqMessages )
log . Println ( "I guess I'm at the end" )
log . Println ( "I guess I'm at the end" )
sigs := make ( chan os . Signal , 1 )
sigs := make ( chan os . Signal , 1 )
signal . Notify ( sigs , syscall . SIGINT , syscall . SIGTERM )
signal . Notify ( sigs , syscall . SIGINT , syscall . SIGTERM )