Compare commits
	
		
			3 Commits 
		
	
	
		
			main
			...
			animate-se
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						41604a2810 | 2 years ago | 
| 
							
							
								
								 | 
						3725f6752e | 2 years ago | 
| 
							
							
								
								 | 
						69e5ea9b96 | 2 years ago | 
@ -1,16 +0,0 @@
 | 
			
		||||
name: Wagner Home Assistant
 | 
			
		||||
version: 1.0.0
 | 
			
		||||
schema: v1
 | 
			
		||||
models:
 | 
			
		||||
  - name: Autodetect
 | 
			
		||||
    provider: ollama
 | 
			
		||||
    model: AUTODETECT
 | 
			
		||||
    apiBase: http://192.168.0.53
 | 
			
		||||
context:
 | 
			
		||||
  - provider: code
 | 
			
		||||
  - provider: docs
 | 
			
		||||
  - provider: diff
 | 
			
		||||
  - provider: terminal
 | 
			
		||||
  - provider: problems
 | 
			
		||||
  - provider: folder
 | 
			
		||||
  - provider: codebase
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
name: memory
 | 
			
		||||
version: 1.0.0 
 | 
			
		||||
schema: v1
 | 
			
		||||
mcpServers:
 | 
			
		||||
  - name: git 
 | 
			
		||||
    version: 0.0.1
 | 
			
		||||
    command: docker
 | 
			
		||||
    args:
 | 
			
		||||
      - run
 | 
			
		||||
      - -i
 | 
			
		||||
      - -v
 | 
			
		||||
      - /workspaces/rgb-led-board:/workspace
 | 
			
		||||
      - --rm
 | 
			
		||||
      - mcp/git
 | 
			
		||||
@ -1,12 +0,0 @@
 | 
			
		||||
name: memory
 | 
			
		||||
version: 1.0.0 
 | 
			
		||||
schema: v1
 | 
			
		||||
mcpServers:
 | 
			
		||||
  - name: memory 
 | 
			
		||||
    version: 0.0.1
 | 
			
		||||
    command: docker
 | 
			
		||||
    args:
 | 
			
		||||
      - run
 | 
			
		||||
      - -i
 | 
			
		||||
      - --rm
 | 
			
		||||
      - mcp/memory
 | 
			
		||||
@ -1,16 +0,0 @@
 | 
			
		||||
name: sqlite
 | 
			
		||||
version: 1.0.0 
 | 
			
		||||
schema: v1
 | 
			
		||||
mcpServers:
 | 
			
		||||
  - name: sqlite 
 | 
			
		||||
    version: 0.0.1
 | 
			
		||||
    command: docker
 | 
			
		||||
    args:
 | 
			
		||||
      - run
 | 
			
		||||
      - -i
 | 
			
		||||
      - -v
 | 
			
		||||
      - /workspaces/rgb-led-board:/sqlite
 | 
			
		||||
      - --rm
 | 
			
		||||
      - mcp/sqlite
 | 
			
		||||
      - --db-path
 | 
			
		||||
      - /sqlite/test.db
 | 
			
		||||
@ -1,12 +0,0 @@
 | 
			
		||||
name: time
 | 
			
		||||
version: 1.0.0 
 | 
			
		||||
schema: v1
 | 
			
		||||
mcpServers:
 | 
			
		||||
  - name: time 
 | 
			
		||||
    version: 0.0.1
 | 
			
		||||
    command: docker
 | 
			
		||||
    args:
 | 
			
		||||
      - run
 | 
			
		||||
      - -i
 | 
			
		||||
      - --rm
 | 
			
		||||
      - mcp/time
 | 
			
		||||
@ -1,12 +0,0 @@
 | 
			
		||||
name: wikipedia
 | 
			
		||||
version: 1.0.0 
 | 
			
		||||
schema: v1
 | 
			
		||||
mcpServers:
 | 
			
		||||
  - name: wikipedia 
 | 
			
		||||
    version: 0.0.1
 | 
			
		||||
    command: docker
 | 
			
		||||
    args:
 | 
			
		||||
      - run
 | 
			
		||||
      - -i
 | 
			
		||||
      - --rm
 | 
			
		||||
      - mcp/wikipedia-mcp
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
name: build only rgb-board
 | 
			
		||||
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
 | 
			
		||||
on: 
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - "animate-separate"
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - ".gitea/workflows/**"
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    container: 
 | 
			
		||||
      image: registry.local/catthehacker-home:act-latest
 | 
			
		||||
      volumes:
 | 
			
		||||
        - /var/run/user/1000/docker.sock:/var/run/docker.sock
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - name: short-sha
 | 
			
		||||
        id: short-sha
 | 
			
		||||
        run: echo "short-sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v2
 | 
			
		||||
        with:                  
 | 
			
		||||
          driver-opts: |
 | 
			
		||||
             image=registry.local/buildkit-wagnerca:stable-3-rootless
 | 
			
		||||
             seccomp=unconfined
 | 
			
		||||
             apparmor=unconfined
 | 
			
		||||
             systempaths=unconfined
 | 
			
		||||
             privileged=false                          
 | 
			
		||||
      - run: docker buildx ls
 | 
			
		||||
      - name: push rgbboard
 | 
			
		||||
        uses: docker/build-push-action@master
 | 
			
		||||
        with:
 | 
			
		||||
          push: false
 | 
			
		||||
          tags: registry.local/rgbboard:${{ steps.short-sha.outputs.short-sha }}
 | 
			
		||||
          platforms: linux/arm64,linux/amd64
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	b64 "encoding/base64"
 | 
			
		||||
	"image"
 | 
			
		||||
	"image/color"
 | 
			
		||||
 | 
			
		||||
	"github.com/disintegration/imaging"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Doorbell struct {
 | 
			
		||||
	image image.Image
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *Animation) animateDoorbell() {
 | 
			
		||||
	if a.countDown > 0 {
 | 
			
		||||
		a.ctx.SetColor(color.Black)
 | 
			
		||||
		a.ctx.Clear()
 | 
			
		||||
		a.ctx.DrawImageAnchored(a.doorbell.image, 0, 0, 0, 0)
 | 
			
		||||
		a.countDown -= 50
 | 
			
		||||
	} else {
 | 
			
		||||
		a.doorbell = Doorbell{}
 | 
			
		||||
		a.countDown = 5000
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func (a *Animation) loadImage(imgType string, img string) {
 | 
			
		||||
	baseImage, _ := b64.StdEncoding.DecodeString(img)
 | 
			
		||||
	bigImage, _, _ := image.Decode(bytes.NewReader(baseImage))
 | 
			
		||||
	a.doorbell.image = imaging.Resize(bigImage, 64, 64, imaging.Lanczos)
 | 
			
		||||
}
 | 
			
		||||
@ -1,151 +0,0 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"image"
 | 
			
		||||
	"image/color"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"math"
 | 
			
		||||
 | 
			
		||||
	"github.com/disintegration/imaging"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Mario struct {
 | 
			
		||||
	position image.Point
 | 
			
		||||
	dir      image.Point
 | 
			
		||||
	images   map[string]image.Image
 | 
			
		||||
	updown   string
 | 
			
		||||
	a        float64
 | 
			
		||||
	b        float64
 | 
			
		||||
	angle    float64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadMario(file string) image.Image {
 | 
			
		||||
 | 
			
		||||
	reader, err := os.Open(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	rawMario, _, err := image.Decode(reader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	mario := imaging.Resize(rawMario, 16, 16, imaging.Lanczos)
 | 
			
		||||
	return mario
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initialMap() map[string]image.Image {
 | 
			
		||||
	imageMap := make(map[string]image.Image)
 | 
			
		||||
	imageMap["marioUp"] = loadMario("marioUp.png")
 | 
			
		||||
	imageMap["marioDown"] = loadMario("marioDown.png")
 | 
			
		||||
	return imageMap
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// initializes the struct for the an play animation function, this could all be dumped into function that's wrapping go routine if I wanted
 | 
			
		||||
 | 
			
		||||
// this assumes mario context is up
 | 
			
		||||
func (a *Animation) animateMario() {
 | 
			
		||||
	defer a.updateMarioPosition()
 | 
			
		||||
	a.ctx.SetColor(color.Black)
 | 
			
		||||
	a.ctx.Clear()
 | 
			
		||||
	if a.mario.dir.X == 1 {
 | 
			
		||||
		a.ctx.DrawImageAnchored(a.mario.images[a.mario.updown], a.mario.position.X, a.mario.position.Y, 0.5, 0.5)
 | 
			
		||||
	} else {
 | 
			
		||||
		a.ctx.DrawImageAnchored(imaging.FlipH(a.mario.images[a.mario.updown]), a.mario.position.X, a.mario.position.Y, 0.5, 0.5)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *Animation) updateMarioPosition() {
 | 
			
		||||
	centerX := a.ctx.Width() / 2
 | 
			
		||||
	centerY := a.ctx.Height() / 2
 | 
			
		||||
 | 
			
		||||
	// Determine sprite size (use current updown image if available)
 | 
			
		||||
	var sprite image.Image
 | 
			
		||||
	if img, ok := a.mario.images[a.mario.updown]; ok && img != nil {
 | 
			
		||||
		sprite = img
 | 
			
		||||
	} else {
 | 
			
		||||
		for _, im := range a.mario.images {
 | 
			
		||||
			sprite = im
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// default half sizes if sprite missing
 | 
			
		||||
	halfW, halfH := 8, 8
 | 
			
		||||
	if sprite != nil {
 | 
			
		||||
		halfW = sprite.Bounds().Dx() / 2
 | 
			
		||||
		halfH = sprite.Bounds().Dy() / 2
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// allowable center range so the sprite stays fully on the panel
 | 
			
		||||
	minCenterX := halfW
 | 
			
		||||
	maxCenterX := a.ctx.Width() - 1 - halfW
 | 
			
		||||
	minCenterY := halfH
 | 
			
		||||
	maxCenterY := a.ctx.Height() - 1 - halfH
 | 
			
		||||
 | 
			
		||||
	// If the sprite is larger than the panel in a dimension, collapse
 | 
			
		||||
	// the allowed center range to the panel center so we don't get
 | 
			
		||||
	// immediate collisions every frame which makes Mario flash.
 | 
			
		||||
	if maxCenterX < minCenterX {
 | 
			
		||||
		minCenterX = centerX
 | 
			
		||||
		maxCenterX = centerX
 | 
			
		||||
	}
 | 
			
		||||
	if maxCenterY < minCenterY {
 | 
			
		||||
		minCenterY = centerY
 | 
			
		||||
		maxCenterY = centerY
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// advance angle for this frame
 | 
			
		||||
	delta := 0.05
 | 
			
		||||
	t := a.mario.angle
 | 
			
		||||
	tNext := t + delta
 | 
			
		||||
	if tNext >= 2*math.Pi {
 | 
			
		||||
		tNext -= 2 * math.Pi
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// compute next candidate center from the next parametric position
 | 
			
		||||
	marioX := int(math.Round(a.mario.a*math.Cos(tNext))) + centerX
 | 
			
		||||
	marioY := int(math.Round(a.mario.b*math.Sin(tNext))) + centerY
 | 
			
		||||
 | 
			
		||||
	// clamp into allowed center ranges so sprite stays fully on panel
 | 
			
		||||
	if marioX < minCenterX {
 | 
			
		||||
		marioX = minCenterX
 | 
			
		||||
	}
 | 
			
		||||
	if marioX > maxCenterX {
 | 
			
		||||
		marioX = maxCenterX
 | 
			
		||||
	}
 | 
			
		||||
	if marioY < minCenterY {
 | 
			
		||||
		marioY = minCenterY
 | 
			
		||||
	}
 | 
			
		||||
	if marioY > maxCenterY {
 | 
			
		||||
		marioY = maxCenterY
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// commit position and advance angle
 | 
			
		||||
	a.mario.position.X = marioX
 | 
			
		||||
	a.mario.position.Y = marioY
 | 
			
		||||
	a.mario.angle = tNext
 | 
			
		||||
 | 
			
		||||
	// Direction logic (based on the motion between t and tNext)
 | 
			
		||||
	// approximate direction by comparing positions (preferable to trig sign when clamped)
 | 
			
		||||
	prevX := int(math.Round(a.mario.a*math.Cos(t))) + centerX
 | 
			
		||||
	if prevX == marioX {
 | 
			
		||||
		// if we didn't move horizontally, keep previous direction
 | 
			
		||||
		// (this prevents flicker when clamped)
 | 
			
		||||
	} else if marioX < prevX {
 | 
			
		||||
		a.mario.dir.X = -1
 | 
			
		||||
	} else {
 | 
			
		||||
		a.mario.dir.X = 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// up/down based on vertical movement
 | 
			
		||||
	prevY := int(math.Round(a.mario.b*math.Sin(t))) + centerY
 | 
			
		||||
	if prevY == marioY {
 | 
			
		||||
		// keep previous up/down state
 | 
			
		||||
	} else if marioY < prevY {
 | 
			
		||||
		a.mario.updown = "marioUp"
 | 
			
		||||
	} else {
 | 
			
		||||
		a.mario.updown = "marioDown"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,73 +0,0 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"image"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	rgbmatrix "gitea.wagshome.duckdns.org/publicWagsHome/go-rpi-rgb-led-matrix"
 | 
			
		||||
	mqtt "github.com/eclipse/paho.mqtt.golang"
 | 
			
		||||
	"github.com/fogleman/gg"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Animation struct {
 | 
			
		||||
	ctx       *gg.Context
 | 
			
		||||
	height    int
 | 
			
		||||
	width     int
 | 
			
		||||
	stroke    int
 | 
			
		||||
	mqmsg     chan mqtt.Message
 | 
			
		||||
	msg       string
 | 
			
		||||
	countDown int
 | 
			
		||||
	mario     Mario
 | 
			
		||||
	doorbell  Doorbell
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func orchestrator(tk *rgbmatrix.ToolKit, mqMessages chan mqtt.Message) {
 | 
			
		||||
	//Playanimation comes from the toolkit, all it takes is an animation struct
 | 
			
		||||
	tk.PlayAnimation(animate(image.Point{127, 64}, mqMessages))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func animate(sz image.Point, mqMessages chan mqtt.Message) *Animation {
 | 
			
		||||
	return &Animation{
 | 
			
		||||
		ctx:       gg.NewContext(sz.X, sz.Y),
 | 
			
		||||
		height:    8,
 | 
			
		||||
		width:     8,
 | 
			
		||||
		stroke:    8,
 | 
			
		||||
		mqmsg:     mqMessages,
 | 
			
		||||
		countDown: 5000,
 | 
			
		||||
		mario: Mario{
 | 
			
		||||
			images: initialMap(),
 | 
			
		||||
			updown: "marioUp",
 | 
			
		||||
			dir:    image.Point{1, 1},
 | 
			
		||||
			a:      62,
 | 
			
		||||
			b:      31,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *Animation) useMsg() {
 | 
			
		||||
	var incoming incomingMessage
 | 
			
		||||
	select {
 | 
			
		||||
	case msg := <-a.mqmsg:
 | 
			
		||||
		json.Unmarshal([]byte(string(msg.Payload())), &incoming)
 | 
			
		||||
		fmt.Println(incoming.Type)
 | 
			
		||||
		if incoming.Type == "doorbell" {
 | 
			
		||||
			go a.loadImage(incoming.Type, incoming.Image)
 | 
			
		||||
		} else {
 | 
			
		||||
			a.ctx.DrawString(a.msg, 5, 9)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *Animation) Next() (image.Image, <-chan time.Time, error) {
 | 
			
		||||
	switch {
 | 
			
		||||
	case a.doorbell != (Doorbell{}):
 | 
			
		||||
		a.animateDoorbell()
 | 
			
		||||
	default:
 | 
			
		||||
		a.animateMario()
 | 
			
		||||
	}
 | 
			
		||||
	a.useMsg()
 | 
			
		||||
	return a.ctx.Image(), time.After(time.Millisecond * 50), nil
 | 
			
		||||
}
 | 
			
		||||
@ -1,50 +0,0 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	MQTT "github.com/eclipse/paho.mqtt.golang"
 | 
			
		||||
	mqtt "github.com/eclipse/paho.mqtt.golang"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// contents of struct mostly don't matter for toolkit.
 | 
			
		||||
type incomingMessage struct {
 | 
			
		||||
	Type  string `json:"type"`
 | 
			
		||||
	Image string `json:"image"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue