adds display package
This commit is contained in:
+100
@@ -0,0 +1,100 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.dvdt.dev/david/bitcoin-ticker-pi/display"
|
||||||
|
"git.dvdt.dev/david/bitcoin-ticker-pi/fonts"
|
||||||
|
rgbmatrix "github.com/mcuadros/go-rpi-rgb-led-matrix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := rgbmatrix.DefaultConfig
|
||||||
|
cfg.Rows = 32
|
||||||
|
cfg.Cols = 64
|
||||||
|
|
||||||
|
m, _ := rgbmatrix.NewRGBLedMatrix(&cfg)
|
||||||
|
c := rgbmatrix.NewCanvas(m)
|
||||||
|
|
||||||
|
d := display.New(c,
|
||||||
|
display.NewSlot(9, 5, 4, 0, 0),
|
||||||
|
display.NewSlot(5, 5, 4, 44, 0),
|
||||||
|
display.NewSlot(7, 9, 6, 10, 10),
|
||||||
|
display.NewSlot(12, 5, 4, 7, 27),
|
||||||
|
display.NewSlot(8, 5, 4, 28, 27),
|
||||||
|
)
|
||||||
|
err := d.UpdateTopLeft([]display.Segment{
|
||||||
|
{Text: "dollar", Color: fonts.Green},
|
||||||
|
{Text: "4", Color: fonts.White},
|
||||||
|
{Text: "2", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.UpdateTopRight([]display.Segment{
|
||||||
|
{Text: "satoshi", Color: fonts.Orange},
|
||||||
|
{Text: "2", Color: fonts.White},
|
||||||
|
{Text: ".", Color: fonts.White},
|
||||||
|
{Text: "5", Color: fonts.White},
|
||||||
|
{Text: "K", Color: fonts.White},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.UpdateCenter([]display.Segment{
|
||||||
|
{Text: "height", Color: fonts.Blue},
|
||||||
|
{Text: "8", Color: fonts.White},
|
||||||
|
{Text: "2", Color: fonts.White},
|
||||||
|
{Text: "1", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.UpdateBottomLeft([]display.Segment{
|
||||||
|
{Text: "low", Color: fonts.Green},
|
||||||
|
{Text: "1", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "medium", Color: fonts.Orange},
|
||||||
|
{Text: "2", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "high", Color: fonts.Red},
|
||||||
|
{Text: "3", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.UpdateBottomRight([]display.Segment{
|
||||||
|
{Text: "1", Color: fonts.White},
|
||||||
|
{Text: "2", Color: fonts.White},
|
||||||
|
{Text: ":", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: ":", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
{Text: "0", Color: fonts.White},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Render()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package display
|
||||||
|
|
||||||
|
import (
|
||||||
|
rgbmatrix "git.dvdt.dev/david/bitcoin-ticker-pi/go-rpi-rgb-led-matrix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type display struct {
|
||||||
|
canvas *rgbmatrix.Canvas
|
||||||
|
|
||||||
|
tl, tr, c, bl, br *Slot
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(canvas *rgbmatrix.Canvas, tl, tr, c, bl, br *Slot) *display {
|
||||||
|
return &display{
|
||||||
|
canvas: canvas,
|
||||||
|
tl: tl,
|
||||||
|
tr: tr,
|
||||||
|
c: c,
|
||||||
|
bl: bl,
|
||||||
|
br: br,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *display) UpdateTopLeft(text []Segment) error {
|
||||||
|
err := d.tl.Update(text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.tl.Draw(d.canvas)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *display) UpdateTopRight(text []Segment) error {
|
||||||
|
err := d.tr.Update(text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.tr.Draw(d.canvas)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *display) UpdateCenter(text []Segment) error {
|
||||||
|
err := d.c.Update(text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.c.Draw(d.canvas)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *display) UpdateBottomLeft(text []Segment) error {
|
||||||
|
err := d.bl.Update(text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.bl.Draw(d.canvas)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *display) UpdateBottomRight(text []Segment) error {
|
||||||
|
err := d.br.Update(text)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.bl.Draw(d.canvas)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *display) Render() error {
|
||||||
|
return d.canvas.Render()
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package display
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"git.dvdt.dev/david/bitcoin-ticker-pi/fonts"
|
||||||
|
rgbmatrix "git.dvdt.dev/david/bitcoin-ticker-pi/go-rpi-rgb-led-matrix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnsupportedSlotHeight = errors.New("unsupported slot height")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Segment struct {
|
||||||
|
Text string
|
||||||
|
Color color.RGBA
|
||||||
|
bitmap [][]color.RGBA
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Segment) Draw(canvas *rgbmatrix.Canvas, x, y int) {
|
||||||
|
for i, row := range s.bitmap {
|
||||||
|
drawRow(canvas, row, x, y+i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawRow(canvas *rgbmatrix.Canvas, row []color.RGBA, x, y int) {
|
||||||
|
for i := 0; i < len(row); i++ {
|
||||||
|
canvas.Set(x+i, y, row[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Slot struct {
|
||||||
|
segmentLimit int
|
||||||
|
segmentHeight int
|
||||||
|
segmentWidth int
|
||||||
|
x, y int
|
||||||
|
text []Segment
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSlot(limit, height, width, x, y int) *Slot {
|
||||||
|
return &Slot{
|
||||||
|
segmentLimit: limit,
|
||||||
|
segmentHeight: height,
|
||||||
|
segmentWidth: width,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
text: []Segment{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slot) Update(text []Segment) error {
|
||||||
|
if len(text) > s.segmentLimit {
|
||||||
|
text = text[:s.segmentLimit-3]
|
||||||
|
text = append(text, []Segment{
|
||||||
|
{Text: ".", Color: fonts.White},
|
||||||
|
{Text: ".", Color: fonts.White},
|
||||||
|
{Text: ".", Color: fonts.White},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var font fonts.Font
|
||||||
|
switch s.segmentHeight {
|
||||||
|
case 5:
|
||||||
|
font = fonts.Face5x4
|
||||||
|
case 7:
|
||||||
|
font = fonts.Face7x5
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %d", ErrUnsupportedSlotHeight, s.segmentHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range text {
|
||||||
|
c, err := font.ToColor(t.Text, t.Color)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
text[i].bitmap = c
|
||||||
|
}
|
||||||
|
s.text = text
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slot) Draw(canvas *rgbmatrix.Canvas) {
|
||||||
|
for i, segment := range s.text {
|
||||||
|
segment.Draw(canvas, s.x+(i*s.segmentWidth), s.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
package fonts
|
||||||
|
|
||||||
|
var (
|
||||||
|
Face5x4 = Font{
|
||||||
|
"1": {
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
{1, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
{1, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{1, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
{0, 1, 1, 0},
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"0": {
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"satoshi": {
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"dollar": {
|
||||||
|
{0, 1, 1, 1},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{0, 1, 1, 0},
|
||||||
|
{0, 0, 1, 1},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"low": {
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
},
|
||||||
|
"high": {
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
},
|
||||||
|
"K": {
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 1, 0, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
},
|
||||||
|
".": {
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
":": {
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
+178
@@ -0,0 +1,178 @@
|
|||||||
|
package fonts
|
||||||
|
|
||||||
|
var (
|
||||||
|
Face7x5 = Font{
|
||||||
|
"1": {
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 1, 0, 0},
|
||||||
|
{0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
{0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 1, 1, 0},
|
||||||
|
{0, 1, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
{0, 0, 0, 1, 0},
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 0, 0},
|
||||||
|
{0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
{0, 1, 1, 1, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
{0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 1, 1, 0},
|
||||||
|
{0, 0, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
{0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"0": {
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 1, 1, 0},
|
||||||
|
{1, 1, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"satoshi": {
|
||||||
|
{0, 0, 1, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1},
|
||||||
|
{0, 0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"dollar": {
|
||||||
|
{0, 0, 1, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1},
|
||||||
|
{0, 0, 0, 0, 1},
|
||||||
|
{1, 1, 1, 1, 1},
|
||||||
|
{0, 0, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
{1, 1, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
{1, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"low": {
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
},
|
||||||
|
"high": {
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 1, 1, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
},
|
||||||
|
"K": {
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0, 0},
|
||||||
|
{1, 1, 0, 0, 0},
|
||||||
|
{1, 0, 1, 0, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0},
|
||||||
|
},
|
||||||
|
".": {
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
":": {
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
+216
@@ -0,0 +1,216 @@
|
|||||||
|
package fonts
|
||||||
|
|
||||||
|
var (
|
||||||
|
Face9x6 = Font{
|
||||||
|
"1": {
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
{1, 1, 0, 1, 1, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1, 1, 0},
|
||||||
|
{0, 0, 1, 1, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
{1, 1, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
{1, 0, 0, 1, 0, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 0, 1, 0, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1, 1, 0},
|
||||||
|
{0, 0, 1, 0, 1, 0},
|
||||||
|
{0, 1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 0, 0, 0},
|
||||||
|
{0, 0, 0, 1, 0, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
{0, 0, 1, 1, 0, 0},
|
||||||
|
{0, 1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 1, 1, 0, 0},
|
||||||
|
{1, 1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{0, 1, 1, 1, 1, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 0, 1, 0},
|
||||||
|
{0, 1, 0, 0, 1, 0},
|
||||||
|
{0, 0, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"0": {
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 1, 1, 0},
|
||||||
|
{1, 0, 1, 0, 1, 0},
|
||||||
|
{1, 1, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{0, 1, 0, 0, 1, 0},
|
||||||
|
{0, 0, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"satoshi": {
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
"dollar": {
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
{1, 0, 1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 1, 0, 0},
|
||||||
|
{1, 0, 1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 0, 1, 0},
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
{1, 0, 1, 0, 1, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{0, 0, 1, 0, 0, 0},
|
||||||
|
{1, 0, 1, 0, 1, 0},
|
||||||
|
{0, 1, 1, 1, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"low": {
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 1, 0, 1, 1, 0},
|
||||||
|
{1, 0, 1, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
},
|
||||||
|
"high": {
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 1, 1, 1, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
},
|
||||||
|
"K": {
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
{1, 0, 0, 0, 0, 0},
|
||||||
|
{1, 0, 0, 1, 0, 0},
|
||||||
|
{1, 0, 1, 0, 0, 0},
|
||||||
|
{1, 1, 1, 0, 0, 0},
|
||||||
|
{1, 1, 0, 0, 0, 0},
|
||||||
|
{1, 0, 1, 0, 0, 0},
|
||||||
|
{1, 0, 0, 1, 0, 0},
|
||||||
|
{1, 0, 0, 0, 1, 0},
|
||||||
|
},
|
||||||
|
".": {
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
":": {
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
{0, 1, 1, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package fonts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Font map[string][][]byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCharNotFound = errors.New("unrecognized character")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f Font) ToColor(char string, c color.RGBA) ([][]color.RGBA, error) {
|
||||||
|
bitmap, found := f[char]
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrCharNotFound, char)
|
||||||
|
}
|
||||||
|
|
||||||
|
colorMappedChar := make([][]color.RGBA, 0, len(bitmap))
|
||||||
|
for _, row := range bitmap {
|
||||||
|
crow := make([]color.RGBA, 0, len(row))
|
||||||
|
for _, pixel := range row {
|
||||||
|
crowColor := c
|
||||||
|
crowColor.R = crowColor.R * pixel
|
||||||
|
crowColor.G = crowColor.G * pixel
|
||||||
|
crowColor.B = crowColor.B * pixel
|
||||||
|
crowColor.A = 0xff * pixel
|
||||||
|
crow = append(crow, crowColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
colorMappedChar = append(colorMappedChar, crow)
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorMappedChar, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
White = color.RGBA{
|
||||||
|
R: 255, G: 255, B: 255, A: 255,
|
||||||
|
}
|
||||||
|
Black = color.RGBA{
|
||||||
|
R: 0, G: 0, B: 0, A: 255,
|
||||||
|
}
|
||||||
|
DarkGrey = color.RGBA{
|
||||||
|
R: 70, G: 70, B: 70, A: 255,
|
||||||
|
}
|
||||||
|
Green = color.RGBA{
|
||||||
|
R: 34, G: 177, B: 76, A: 255,
|
||||||
|
}
|
||||||
|
Orange = color.RGBA{
|
||||||
|
R: 255, G: 126, B: 0, A: 255,
|
||||||
|
}
|
||||||
|
Red = color.RGBA{
|
||||||
|
R: 153, G: 0, B: 48, A: 255,
|
||||||
|
}
|
||||||
|
Blue = color.RGBA{
|
||||||
|
R: 0, G: 183, B: 239, A: 255,
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package fonts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFont_ToColor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
font Font
|
||||||
|
char string
|
||||||
|
color color.RGBA
|
||||||
|
wantMappedChar [][]color.RGBA
|
||||||
|
wantError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "it should error if char mapping doesn't exist",
|
||||||
|
font: Font{},
|
||||||
|
char: "not found",
|
||||||
|
wantError: fmt.Errorf("%w: %s", ErrCharNotFound, "not found"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "it should produce a color mapped character",
|
||||||
|
font: Face5x4,
|
||||||
|
char: "1",
|
||||||
|
color: White,
|
||||||
|
wantMappedChar: [][]color.RGBA{
|
||||||
|
{{R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 255}, {R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 0}},
|
||||||
|
{{R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 255}, {R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 0}},
|
||||||
|
{{R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 255}, {R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 0}},
|
||||||
|
{{R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 255}, {R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 0}},
|
||||||
|
{{R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 255}, {R: 255, G: 255, B: 255, A: 0}, {R: 255, G: 255, B: 255, A: 0}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
mappedChar, err := test.font.ToColor(test.char, test.color)
|
||||||
|
|
||||||
|
assert.Equal(t, test.wantMappedChar, mappedChar)
|
||||||
|
assert.Equal(t, test.wantError, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "lib/rpi-rgb-led-matrix"]
|
||||||
|
path = lib/rpi-rgb-led-matrix
|
||||||
|
url = https://github.com/hzeller/rpi-rgb-led-matrix.git
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
- tip
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- cd $GOPATH/src/github.com/mcuadros/go-rpi-rgb-led-matrix/lib/rpi-rgb-led-matrix/
|
||||||
|
- git submodule update --init
|
||||||
|
- make
|
||||||
|
- cd $GOPATH/src/github.com/mcuadros/go-rpi-rgb-led-matrix/
|
||||||
|
- go get -t -v ./...
|
||||||
|
- go install -v ./...
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Máximo Cuadros
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# go-rpi-rgb-led-matrix [](https://godoc.org/github.com/mcuadros/go-rpi-rgb-led-matrix) [](https://travis-ci.org/mcuadros/go-rpi-rgb-led-matrix)
|
||||||
|
<img width="250" src="https://cloud.githubusercontent.com/assets/1573114/20248154/c17c1f2e-a9dd-11e6-805b-bf7d8ee73121.gif" align="right" />
|
||||||
|
|
||||||
|
Go binding for [`rpi-rgb-led-matrix`](https://github.com/hzeller/rpi-rgb-led-matrix) an excellent C++ library to control [RGB LED displays](https://learn.adafruit.com/32x16-32x32-rgb-led-matrix/overview) with Raspberry Pi GPIO.
|
||||||
|
|
||||||
|
This library includes the basic bindings to control de LED Matrix directly and also a convenient [ToolKit](https://godoc.org/github.com/mcuadros/go-rpi-rgb-led-matrix#ToolKit) with more high level functions. Also some [examples](https://github.com/mcuadros/go-rpi-rgb-led-matrix/tree/master/examples) are included to test the library and the configuration.
|
||||||
|
|
||||||
|
The [`Canvas`](https://godoc.org/github.com/mcuadros/go-rpi-rgb-led-matrix#Canvas) struct implements the [`image.Image`](https://golang.org/pkg/image/#Image) interface from the Go standard library. This makes the interaction with the matrix simple as work with a normal image in Go, allowing the usage of any Go library build around the `image.Image` interface.
|
||||||
|
|
||||||
|
To learn about the configuration and the wiring go to the [original library](https://github.com/hzeller/rpi-rgb-led-matrix), is highly detailed and well explained.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
The recommended way to install `go-rpi-rgb-led-matrix` is:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get github.com/mcuadros/go-rpi-rgb-led-matrix
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you will get an **expected** error like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
# github.com/mcuadros/go-rpi-rgb-led-matrix
|
||||||
|
/usr/bin/ld: cannot find -lrgbmatrix
|
||||||
|
collect2: error: ld returned 1 exit status
|
||||||
|
```
|
||||||
|
|
||||||
|
This happens because you need to compile the `rgbmatrix` C bindings:
|
||||||
|
```sh
|
||||||
|
cd $GOPATH/src/github.com/mcuadros/go-rpi-rgb-led-matrix/lib/rpi-rgb-led-matrix/
|
||||||
|
git submodule update --init
|
||||||
|
make
|
||||||
|
cd $GOPATH/src/github.com/mcuadros/go-rpi-rgb-led-matrix/
|
||||||
|
go install -v ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
Setting all the pixels to white:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// create a new Matrix instance with the DefaultConfig
|
||||||
|
m, _ := rgbmatrix.NewRGBLedMatrix(&rgbmatrix.DefaultConfig)
|
||||||
|
|
||||||
|
// create the Canvas, implements the image.Image interface
|
||||||
|
c := rgbmatrix.NewCanvas(m)
|
||||||
|
defer c.Close() // don't forgot close the Matrix, if not your leds will remain on
|
||||||
|
|
||||||
|
// using the standard draw.Draw function we copy a white image onto the Canvas
|
||||||
|
draw.Draw(c, c.Bounds(), &image.Uniform{color.White}, image.ZP, draw.Src)
|
||||||
|
|
||||||
|
// don't forget call Render to display the new led status
|
||||||
|
c.Render()
|
||||||
|
```
|
||||||
|
|
||||||
|
Playing a GIF into your matrix during 30 seconds:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// create a new Matrix instance with the DefaultConfig
|
||||||
|
m, _ := rgbmatrix.NewRGBLedMatrix(&rgbmatrix.DefaultConfig)
|
||||||
|
|
||||||
|
// create a ToolKit instance
|
||||||
|
tk := rgbmatrix.NewToolKit(m)
|
||||||
|
defer tk.Close() // don't forgot close the Matrix, if not your leds will remain on
|
||||||
|
|
||||||
|
// open the gif file for reading
|
||||||
|
file, _ := os.Open("mario.gif")
|
||||||
|
|
||||||
|
// play of the gif using the io.Reader
|
||||||
|
close, _ := tk.PlayGIF(f)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
// we wait 30 seconds and then we stop the playing gif sending a True to the returned chan
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
close <- true
|
||||||
|
```
|
||||||
|
|
||||||
|
The image of the header was recorded using this few lines, the running _Mario_ gif, and three 32x64 pannels.
|
||||||
|
<img src="https://cloud.githubusercontent.com/assets/1573114/20248173/2e2f97ae-a9de-11e6-95e6-e0548199501d.gif" align="right" width="100" />
|
||||||
|
|
||||||
|
Check the folder [`examples`](https://github.com/mcuadros/go-rpi-rgb-led-matrix/tree/master/examples) folder for more examples
|
||||||
|
|
||||||
|
|
||||||
|
Matrix Emulation
|
||||||
|
----------------
|
||||||
|
|
||||||
|
As part of the library an small Matrix emulator is provided. The emulator renderize a virtual RGB matrix on a window in your desktop, without needing a real RGB matrix connected to your computer.
|
||||||
|
|
||||||
|
To execute the emulator set the `MATRIX_EMULATOR` environment variable to `1`, then when `NewRGBLedMatrix` is used, a `emulator.Emulator` is returned instead of a interface the real board.
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
MIT, see [LICENSE](LICENSE)
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package rgbmatrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Canvas is a image.Image representation of a WS281x matrix, it implements
|
||||||
|
// image.Image interface and can be used with draw.Draw for example
|
||||||
|
type Canvas struct {
|
||||||
|
w, h int
|
||||||
|
m Matrix
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCanvas returns a new Canvas using the given width and height and creates
|
||||||
|
// a new WS281x matrix using the given config
|
||||||
|
func NewCanvas(m Matrix) *Canvas {
|
||||||
|
w, h := m.Geometry()
|
||||||
|
return &Canvas{
|
||||||
|
w: w,
|
||||||
|
h: h,
|
||||||
|
m: m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render update the display with the data from the LED buffer
|
||||||
|
func (c *Canvas) Render() error {
|
||||||
|
return c.m.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorModel returns the canvas' color model, always color.RGBAModel
|
||||||
|
func (c *Canvas) ColorModel() color.Model {
|
||||||
|
return color.RGBAModel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds return the topology of the Canvas
|
||||||
|
func (c *Canvas) Bounds() image.Rectangle {
|
||||||
|
return image.Rect(0, 0, c.w, c.h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At returns the color of the pixel at (x, y)
|
||||||
|
func (c *Canvas) At(x, y int) color.Color {
|
||||||
|
return c.m.At(c.position(x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set set LED at position x,y to the provided 24-bit color value
|
||||||
|
func (c *Canvas) Set(x, y int, color color.Color) {
|
||||||
|
c.m.Set(c.position(x, y), color)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Canvas) position(x, y int) int {
|
||||||
|
return x + (y * c.w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear set all the leds on the matrix with color.Black
|
||||||
|
func (c *Canvas) Clear() error {
|
||||||
|
draw.Draw(c, c.Bounds(), &image.Uniform{color.Black}, image.ZP, draw.Src)
|
||||||
|
return c.m.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close clears the matrix and close the matrix
|
||||||
|
func (c *Canvas) Close() error {
|
||||||
|
c.Clear()
|
||||||
|
return c.m.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matrix is an interface that represent any RGB matrix, very useful for testing
|
||||||
|
type Matrix interface {
|
||||||
|
Geometry() (width, height int)
|
||||||
|
At(position int) color.Color
|
||||||
|
Set(position int, c color.Color)
|
||||||
|
Apply([]color.Color) error
|
||||||
|
Render() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package rgbmatrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) { TestingT(t) }
|
||||||
|
|
||||||
|
type CanvasSuite struct{}
|
||||||
|
|
||||||
|
var _ = Suite(&CanvasSuite{})
|
||||||
|
|
||||||
|
func (s *CanvasSuite) TestNewCanvas(c *C) {
|
||||||
|
canvas := NewCanvas(NewMatrixMock())
|
||||||
|
c.Assert(canvas, NotNil)
|
||||||
|
c.Assert(canvas.w, Equals, 64)
|
||||||
|
c.Assert(canvas.h, Equals, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CanvasSuite) TestRender(c *C) {
|
||||||
|
m := NewMatrixMock()
|
||||||
|
canvas := &Canvas{m: m}
|
||||||
|
canvas.Render()
|
||||||
|
|
||||||
|
c.Assert(m.called["Render"], Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CanvasSuite) TestColorModel(c *C) {
|
||||||
|
canvas := &Canvas{}
|
||||||
|
|
||||||
|
c.Assert(canvas.ColorModel(), Equals, color.RGBAModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CanvasSuite) TestBounds(c *C) {
|
||||||
|
|
||||||
|
canvas := &Canvas{w: 10, h: 20}
|
||||||
|
|
||||||
|
b := canvas.Bounds()
|
||||||
|
c.Assert(b.Min.X, Equals, 0)
|
||||||
|
c.Assert(b.Min.Y, Equals, 0)
|
||||||
|
c.Assert(b.Max.X, Equals, 10)
|
||||||
|
c.Assert(b.Max.Y, Equals, 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CanvasSuite) TestAt(c *C) {
|
||||||
|
m := NewMatrixMock()
|
||||||
|
canvas := &Canvas{w: 10, h: 20, m: m}
|
||||||
|
canvas.At(5, 15)
|
||||||
|
|
||||||
|
c.Assert(m.called["At"], Equals, 155)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CanvasSuite) TestSet(c *C) {
|
||||||
|
m := NewMatrixMock()
|
||||||
|
canvas := &Canvas{w: 10, h: 20, m: m}
|
||||||
|
canvas.Set(5, 15, color.White)
|
||||||
|
|
||||||
|
c.Assert(m.called["Set"], Equals, 155)
|
||||||
|
c.Assert(m.colors[155], Equals, color.White)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CanvasSuite) TestClear(c *C) {
|
||||||
|
m := NewMatrixMock()
|
||||||
|
|
||||||
|
canvas := &Canvas{w: 10, h: 20, m: m}
|
||||||
|
err := canvas.Clear()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
for _, px := range m.colors {
|
||||||
|
c.Assert(px, Equals, color.Black)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(m.called["Render"], Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CanvasSuite) TestClose(c *C) {
|
||||||
|
m := NewMatrixMock()
|
||||||
|
canvas := &Canvas{w: 10, h: 20, m: m}
|
||||||
|
err := canvas.Close()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
for _, px := range m.colors {
|
||||||
|
c.Assert(px, Equals, color.Black)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(m.called["Render"], Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatrixMock struct {
|
||||||
|
called map[string]interface{}
|
||||||
|
colors []color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMatrixMock() *MatrixMock {
|
||||||
|
return &MatrixMock{
|
||||||
|
called: make(map[string]interface{}, 0),
|
||||||
|
colors: make([]color.Color, 200),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MatrixMock) Geometry() (width, height int) {
|
||||||
|
return 64, 32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MatrixMock) Initialize() error {
|
||||||
|
m.called["Initialize"] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MatrixMock) At(position int) color.Color {
|
||||||
|
m.called["At"] = position
|
||||||
|
return color.Black
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MatrixMock) Set(position int, c color.Color) {
|
||||||
|
m.called["Set"] = position
|
||||||
|
m.colors[position] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MatrixMock) Apply(leds []color.Color) error {
|
||||||
|
for position, l := range leds {
|
||||||
|
m.Set(position, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MatrixMock) Render() error {
|
||||||
|
m.called["Render"] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MatrixMock) Close() error {
|
||||||
|
m.called["Close"] = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package emulator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/exp/shiny/driver"
|
||||||
|
"golang.org/x/exp/shiny/screen"
|
||||||
|
"golang.org/x/mobile/event/paint"
|
||||||
|
"golang.org/x/mobile/event/size"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultPixelPitch = 12
|
||||||
|
const windowTitle = "RGB led matrix emulator"
|
||||||
|
|
||||||
|
type Emulator struct {
|
||||||
|
PixelPitch int
|
||||||
|
Gutter int
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
GutterColor color.Color
|
||||||
|
PixelPitchToGutterRatio int
|
||||||
|
Margin int
|
||||||
|
|
||||||
|
leds []color.Color
|
||||||
|
w screen.Window
|
||||||
|
s screen.Screen
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
isReady bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEmulator(w, h, pixelPitch int, autoInit bool) *Emulator {
|
||||||
|
e := &Emulator{
|
||||||
|
Width: w,
|
||||||
|
Height: h,
|
||||||
|
GutterColor: color.Gray{Y: 20},
|
||||||
|
PixelPitchToGutterRatio: 2,
|
||||||
|
Margin: 10,
|
||||||
|
}
|
||||||
|
e.updatePixelPitchForGutter(pixelPitch / e.PixelPitchToGutterRatio)
|
||||||
|
|
||||||
|
if autoInit {
|
||||||
|
e.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initialize the emulator, creating a new Window and waiting until is
|
||||||
|
// painted. If something goes wrong the function panics
|
||||||
|
func (e *Emulator) Init() {
|
||||||
|
e.leds = make([]color.Color, e.Width*e.Height)
|
||||||
|
|
||||||
|
e.wg.Add(1)
|
||||||
|
go driver.Main(e.mainWindowLoop)
|
||||||
|
e.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emulator) mainWindowLoop(s screen.Screen) {
|
||||||
|
var err error
|
||||||
|
e.s = s
|
||||||
|
// Calculate initial window size based on whatever our gutter/pixel pitch currently is.
|
||||||
|
dims := e.matrixWithMarginsRect()
|
||||||
|
e.w, err = s.NewWindow(&screen.NewWindowOptions{
|
||||||
|
Title: windowTitle,
|
||||||
|
Width: dims.Max.X,
|
||||||
|
Height: dims.Max.Y,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer e.w.Release()
|
||||||
|
|
||||||
|
var sz size.Event
|
||||||
|
for {
|
||||||
|
evn := e.w.NextEvent()
|
||||||
|
switch evn := evn.(type) {
|
||||||
|
case paint.Event:
|
||||||
|
e.drawContext(sz)
|
||||||
|
if e.isReady {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Apply(make([]color.Color, e.Width*e.Height))
|
||||||
|
e.wg.Done()
|
||||||
|
e.isReady = true
|
||||||
|
case size.Event:
|
||||||
|
sz = evn
|
||||||
|
|
||||||
|
case error:
|
||||||
|
fmt.Fprintln(os.Stderr, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emulator) drawContext(sz size.Event) {
|
||||||
|
e.updatePixelPitchForGutter(e.calculateGutterForViewableArea(sz.Size()))
|
||||||
|
// Fill entire background with white.
|
||||||
|
e.w.Fill(sz.Bounds(), color.White, screen.Src)
|
||||||
|
// Fill matrix display rectangle with the gutter color.
|
||||||
|
e.w.Fill(e.matrixWithMarginsRect(), e.GutterColor, screen.Src)
|
||||||
|
// Set all LEDs to black.
|
||||||
|
e.Apply(make([]color.Color, e.Width*e.Height))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some formulas that allowed me to better understand the drawable area. I found that the math was
|
||||||
|
// easiest when put in terms of the Gutter width, hence the addition of PixelPitchToGutterRatio.
|
||||||
|
//
|
||||||
|
// PixelPitch = PixelPitchToGutterRatio * Gutter
|
||||||
|
// DisplayWidth = (PixelPitch * LEDColumns) + (Gutter * (LEDColumns - 1)) + (2 * Margin)
|
||||||
|
// Gutter = (DisplayWidth - (2 * Margin)) / (PixelPitchToGutterRatio * LEDColumns + LEDColumns - 1)
|
||||||
|
//
|
||||||
|
// MMMMMMMMMMMMMMMM.....MMMM
|
||||||
|
// MGGGGGGGGGGGGGGG.....GGGM
|
||||||
|
// MGLGLGLGLGLGLGLG.....GLGM
|
||||||
|
// MGGGGGGGGGGGGGGG.....GGGM
|
||||||
|
// MGLGLGLGLGLGLGLG.....GLGM
|
||||||
|
// MGGGGGGGGGGGGGGG.....GGGM
|
||||||
|
// .........................
|
||||||
|
// MGGGGGGGGGGGGGGG.....GGGM
|
||||||
|
// MGLGLGLGLGLGLGLG.....GLGM
|
||||||
|
// MGGGGGGGGGGGGGGG.....GGGM
|
||||||
|
// MMMMMMMMMMMMMMMM.....MMMM
|
||||||
|
//
|
||||||
|
// where:
|
||||||
|
// M = Margin
|
||||||
|
// G = Gutter
|
||||||
|
// L = LED
|
||||||
|
|
||||||
|
// matrixWithMarginsRect Returns a Rectangle that describes entire emulated RGB Matrix, including margins.
|
||||||
|
func (e *Emulator) matrixWithMarginsRect() image.Rectangle {
|
||||||
|
upperLeftLED := e.ledRect(0, 0)
|
||||||
|
lowerRightLED := e.ledRect(e.Width-1, e.Height-1)
|
||||||
|
return image.Rect(upperLeftLED.Min.X-e.Margin, upperLeftLED.Min.Y-e.Margin, lowerRightLED.Max.X+e.Margin, lowerRightLED.Max.Y+e.Margin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ledRect Returns a Rectangle for the LED at col and row.
|
||||||
|
func (e *Emulator) ledRect(col int, row int) image.Rectangle {
|
||||||
|
x := (col * (e.PixelPitch + e.Gutter)) + e.Margin
|
||||||
|
y := (row * (e.PixelPitch + e.Gutter)) + e.Margin
|
||||||
|
return image.Rect(x, y, x+e.PixelPitch, y+e.PixelPitch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateGutterForViewableArea As the name states, calculates the size of the gutter for a given viewable area.
|
||||||
|
// It's easier to understand the geometry of the matrix on screen when put in terms of the gutter,
|
||||||
|
// hence the shift toward calculating the gutter size.
|
||||||
|
func (e *Emulator) calculateGutterForViewableArea(size image.Point) int {
|
||||||
|
maxGutterInX := (size.X - 2*e.Margin) / (e.PixelPitchToGutterRatio*e.Width + e.Width - 1)
|
||||||
|
maxGutterInY := (size.Y - 2*e.Margin) / (e.PixelPitchToGutterRatio*e.Height + e.Height - 1)
|
||||||
|
if maxGutterInX < maxGutterInY {
|
||||||
|
return maxGutterInX
|
||||||
|
}
|
||||||
|
return maxGutterInY
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emulator) updatePixelPitchForGutter(gutterWidth int) {
|
||||||
|
e.PixelPitch = e.PixelPitchToGutterRatio * gutterWidth
|
||||||
|
e.Gutter = gutterWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emulator) Geometry() (width, height int) {
|
||||||
|
return e.Width, e.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emulator) Apply(leds []color.Color) error {
|
||||||
|
defer func() { e.leds = make([]color.Color, e.Height*e.Width) }()
|
||||||
|
|
||||||
|
var c color.Color
|
||||||
|
for col := 0; col < e.Width; col++ {
|
||||||
|
for row := 0; row < e.Height; row++ {
|
||||||
|
c = e.At(col + (row * e.Width))
|
||||||
|
e.w.Fill(e.ledRect(col, row), c, screen.Over)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.w.Publish()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emulator) Render() error {
|
||||||
|
return e.Apply(e.leds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emulator) At(position int) color.Color {
|
||||||
|
if e.leds[position] == nil {
|
||||||
|
return color.Black
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.leds[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emulator) Set(position int, c color.Color) {
|
||||||
|
e.leds[position] = color.RGBAModel.Convert(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Emulator) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/RockKeeper/go-rpi-rgb-led-matrix"
|
||||||
|
"github.com/fogleman/gg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rows = flag.Int("led-rows", 32, "number of rows supported")
|
||||||
|
cols = flag.Int("led-cols", 32, "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.")
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
m, err := rgbmatrix.NewRGBLedMatrix(config)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
tk := rgbmatrix.NewToolKit(m)
|
||||||
|
defer tk.Close()
|
||||||
|
|
||||||
|
tk.PlayAnimation(NewAnimation(image.Point{64, 32}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Animation struct {
|
||||||
|
ctx *gg.Context
|
||||||
|
position image.Point
|
||||||
|
dir image.Point
|
||||||
|
stroke int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnimation(sz image.Point) *Animation {
|
||||||
|
return &Animation{
|
||||||
|
ctx: gg.NewContext(sz.X, sz.Y),
|
||||||
|
dir: image.Point{1, 1},
|
||||||
|
stroke: 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animation) Next() (image.Image, <-chan time.Time, error) {
|
||||||
|
defer a.updatePosition()
|
||||||
|
|
||||||
|
a.ctx.SetColor(color.Black)
|
||||||
|
a.ctx.Clear()
|
||||||
|
|
||||||
|
a.ctx.DrawCircle(float64(a.position.X), float64(a.position.Y), float64(a.stroke))
|
||||||
|
a.ctx.SetColor(color.RGBA{255, 0, 0, 255})
|
||||||
|
a.ctx.Fill()
|
||||||
|
return a.ctx.Image(), time.After(time.Millisecond * 50), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animation) updatePosition() {
|
||||||
|
a.position.X += 1 * a.dir.X
|
||||||
|
a.position.Y += 1 * a.dir.Y
|
||||||
|
|
||||||
|
if a.position.Y+a.stroke > a.ctx.Height() {
|
||||||
|
a.dir.Y = -1
|
||||||
|
} else if a.position.Y-a.stroke < 0 {
|
||||||
|
a.dir.Y = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.position.X+a.stroke > a.ctx.Width() {
|
||||||
|
a.dir.X = -1
|
||||||
|
} else if a.position.X-a.stroke < 0 {
|
||||||
|
a.dir.X = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/RockKeeper/go-rpi-rgb-led-matrix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rows = flag.Int("led-rows", 32, "number of rows supported")
|
||||||
|
cols = flag.Int("led-cols", 32, "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.")
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
m, err := rgbmatrix.NewRGBLedMatrix(config)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
c := rgbmatrix.NewCanvas(m)
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
bounds := c.Bounds()
|
||||||
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||||
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||||
|
fmt.Println("x", x, "y", y)
|
||||||
|
c.Set(x, y, color.RGBA{255, 0, 0, 255})
|
||||||
|
c.Render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/RockKeeper/go-rpi-rgb-led-matrix"
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rows = flag.Int("led-rows", 32, "number of rows supported")
|
||||||
|
cols = flag.Int("led-cols", 32, "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.")
|
||||||
|
img = flag.String("image", "", "image path")
|
||||||
|
|
||||||
|
rotate = flag.Int("rotate", 0, "rotate angle, 90, 180, 270")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
f, err := os.Open(*img)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
m, err := rgbmatrix.NewRGBLedMatrix(config)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
tk := rgbmatrix.NewToolKit(m)
|
||||||
|
defer tk.Close()
|
||||||
|
|
||||||
|
switch *rotate {
|
||||||
|
case 90:
|
||||||
|
tk.Transform = imaging.Rotate90
|
||||||
|
case 180:
|
||||||
|
tk.Transform = imaging.Rotate180
|
||||||
|
case 270:
|
||||||
|
tk.Transform = imaging.Rotate270
|
||||||
|
}
|
||||||
|
|
||||||
|
close, err := tk.PlayGIF(f)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
close <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/RockKeeper/go-rpi-rgb-led-matrix"
|
||||||
|
"github.com/RockKeeper/go-rpi-rgb-led-matrix/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
img = flag.String("image", "", "image path")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
f, err := os.Open(*img)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
m, err := rpc.NewClient("tcp", "10.20.20.20:1234")
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
tk := rgbmatrix.NewToolKit(m)
|
||||||
|
close, err := tk.PlayGIF(f)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
close <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"github.com/RockKeeper/go-rpi-rgb-led-matrix"
|
||||||
|
"github.com/RockKeeper/go-rpi-rgb-led-matrix/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rows = flag.Int("led-rows", 32, "number of rows supported")
|
||||||
|
cols = flag.Int("led-cols", 32, "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.")
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
m, err := rgbmatrix.NewRGBLedMatrix(config)
|
||||||
|
fatal(err)
|
||||||
|
|
||||||
|
rpc.Serve(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
package rgbmatrix
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo CFLAGS: -std=c99 -I${SRCDIR}/lib/rpi-rgb-led-matrix/include -DSHOW_REFRESH_RATE
|
||||||
|
#cgo LDFLAGS: -lrgbmatrix -L${SRCDIR}/lib/rpi-rgb-led-matrix/lib -lstdc++ -lm
|
||||||
|
#include <led-matrix-c.h>
|
||||||
|
|
||||||
|
void led_matrix_swap(struct RGBLedMatrix *matrix, struct LedCanvas *offscreen_canvas,
|
||||||
|
int width, int height, const uint32_t pixels[]) {
|
||||||
|
|
||||||
|
|
||||||
|
int i, x, y;
|
||||||
|
uint32_t color;
|
||||||
|
for (x = 0; x < width; ++x) {
|
||||||
|
for (y = 0; y < height; ++y) {
|
||||||
|
i = x + (y * width);
|
||||||
|
color = pixels[i];
|
||||||
|
|
||||||
|
led_canvas_set_pixel(offscreen_canvas, x, y,
|
||||||
|
(color >> 16) & 255, (color >> 8) & 255, color & 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offscreen_canvas = led_matrix_swap_on_vsync(matrix, offscreen_canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_show_refresh_rate(struct RGBLedMatrixOptions *o, int show_refresh_rate) {
|
||||||
|
o->show_refresh_rate = show_refresh_rate != 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_disable_hardware_pulsing(struct RGBLedMatrixOptions *o, int disable_hardware_pulsing) {
|
||||||
|
o->disable_hardware_pulsing = disable_hardware_pulsing != 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_inverse_colors(struct RGBLedMatrixOptions *o, int inverse_colors) {
|
||||||
|
o->inverse_colors = inverse_colors != 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"git.dvdt.dev/david/bitcoin-ticker-pi/go-rpi-rgb-led-matrix/emulator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultConfig default WS281x configuration
|
||||||
|
var DefaultConfig = HardwareConfig{
|
||||||
|
Rows: 32,
|
||||||
|
Cols: 32,
|
||||||
|
ChainLength: 1,
|
||||||
|
Parallel: 1,
|
||||||
|
PWMBits: 11,
|
||||||
|
PWMLSBNanoseconds: 130,
|
||||||
|
Brightness: 100,
|
||||||
|
ScanMode: Progressive,
|
||||||
|
}
|
||||||
|
|
||||||
|
// HardwareConfig rgb-led-matrix configuration
|
||||||
|
type HardwareConfig struct {
|
||||||
|
// Rows the number of rows supported by the display, so 32 or 16.
|
||||||
|
Rows int
|
||||||
|
// Cols the number of columns supported by the display, so 32 or 64 .
|
||||||
|
Cols int
|
||||||
|
// ChainLengthis the number of displays daisy-chained together
|
||||||
|
// (output of one connected to input of next).
|
||||||
|
ChainLength int
|
||||||
|
// Parallel is the number of parallel chains connected to the Pi; in old Pis
|
||||||
|
// with 26 GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that
|
||||||
|
// can also be 2 or 3. The effective number of pixels in vertical direction is
|
||||||
|
// then thus rows * parallel.
|
||||||
|
Parallel int
|
||||||
|
// Set PWM bits used for output. Default is 11, but if you only deal with
|
||||||
|
// limited comic-colors, 1 might be sufficient. Lower require less CPU and
|
||||||
|
// increases refresh-rate.
|
||||||
|
PWMBits int
|
||||||
|
// Change the base time-unit for the on-time in the lowest significant bit in
|
||||||
|
// nanoseconds. Higher numbers provide better quality (more accurate color,
|
||||||
|
// less ghosting), but have a negative impact on the frame rate.
|
||||||
|
PWMLSBNanoseconds int // the DMA channel to use
|
||||||
|
// Brightness is the initial brightness of the panel in percent. Valid range
|
||||||
|
// is 1..100
|
||||||
|
Brightness int
|
||||||
|
// ScanMode progressive or interlaced
|
||||||
|
ScanMode ScanMode // strip color layout
|
||||||
|
// Disable the PWM hardware subsystem to create pulses. Typically, you don't
|
||||||
|
// want to disable hardware pulsing, this is mostly for debugging and figuring
|
||||||
|
// out if there is interference with the sound system.
|
||||||
|
// This won't do anything if output enable is not connected to GPIO 18 in
|
||||||
|
// non-standard wirings.
|
||||||
|
DisableHardwarePulsing bool
|
||||||
|
|
||||||
|
ShowRefreshRate bool
|
||||||
|
InverseColors bool
|
||||||
|
|
||||||
|
// Name of GPIO mapping used
|
||||||
|
HardwareMapping string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HardwareConfig) geometry() (width, height int) {
|
||||||
|
return c.Cols * c.ChainLength, c.Rows * c.Parallel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HardwareConfig) toC() *C.struct_RGBLedMatrixOptions {
|
||||||
|
o := &C.struct_RGBLedMatrixOptions{}
|
||||||
|
o.rows = C.int(c.Rows)
|
||||||
|
o.cols = C.int(c.Cols)
|
||||||
|
o.chain_length = C.int(c.ChainLength)
|
||||||
|
o.parallel = C.int(c.Parallel)
|
||||||
|
o.pwm_bits = C.int(c.PWMBits)
|
||||||
|
o.pwm_lsb_nanoseconds = C.int(c.PWMLSBNanoseconds)
|
||||||
|
o.brightness = C.int(c.Brightness)
|
||||||
|
o.scan_mode = C.int(c.ScanMode)
|
||||||
|
o.hardware_mapping = C.CString(c.HardwareMapping)
|
||||||
|
|
||||||
|
if c.ShowRefreshRate == true {
|
||||||
|
C.set_show_refresh_rate(o, C.int(1))
|
||||||
|
} else {
|
||||||
|
C.set_show_refresh_rate(o, C.int(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DisableHardwarePulsing == true {
|
||||||
|
C.set_disable_hardware_pulsing(o, C.int(1))
|
||||||
|
} else {
|
||||||
|
C.set_disable_hardware_pulsing(o, C.int(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.InverseColors == true {
|
||||||
|
C.set_inverse_colors(o, C.int(1))
|
||||||
|
} else {
|
||||||
|
C.set_inverse_colors(o, C.int(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScanMode int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
Progressive ScanMode = 0
|
||||||
|
Interlaced ScanMode = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// RGBLedMatrix matrix representation for ws281x
|
||||||
|
type RGBLedMatrix struct {
|
||||||
|
Config *HardwareConfig
|
||||||
|
|
||||||
|
height int
|
||||||
|
width int
|
||||||
|
matrix *C.struct_RGBLedMatrix
|
||||||
|
buffer *C.struct_LedCanvas
|
||||||
|
leds []C.uint32_t
|
||||||
|
}
|
||||||
|
|
||||||
|
const MatrixEmulatorENV = "MATRIX_EMULATOR"
|
||||||
|
|
||||||
|
// NewRGBLedMatrix returns a new matrix using the given size and config
|
||||||
|
func NewRGBLedMatrix(config *HardwareConfig) (c Matrix, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
var ok bool
|
||||||
|
err, ok = r.(error)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("error creating matrix: %v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if isMatrixEmulator() {
|
||||||
|
return buildMatrixEmulator(config), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w, h := config.geometry()
|
||||||
|
m := C.led_matrix_create_from_options(config.toC(), nil, nil)
|
||||||
|
b := C.led_matrix_create_offscreen_canvas(m)
|
||||||
|
c = &RGBLedMatrix{
|
||||||
|
Config: config,
|
||||||
|
width: w, height: h,
|
||||||
|
matrix: m,
|
||||||
|
buffer: b,
|
||||||
|
leds: make([]C.uint32_t, w*h),
|
||||||
|
}
|
||||||
|
if m == nil {
|
||||||
|
return nil, fmt.Errorf("unable to allocate memory")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMatrixEmulator() bool {
|
||||||
|
if os.Getenv(MatrixEmulatorENV) == "1" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMatrixEmulator(config *HardwareConfig) Matrix {
|
||||||
|
w, h := config.geometry()
|
||||||
|
return emulator.NewEmulator(w, h, emulator.DefaultPixelPitch, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initialize library, must be called once before other functions are
|
||||||
|
// called.
|
||||||
|
func (c *RGBLedMatrix) Initialize() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Geometry returns the width and the height of the matrix
|
||||||
|
func (c *RGBLedMatrix) Geometry() (width, height int) {
|
||||||
|
return c.width, c.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply set all the pixels to the values contained in leds
|
||||||
|
func (c *RGBLedMatrix) Apply(leds []color.Color) error {
|
||||||
|
for position, l := range leds {
|
||||||
|
c.Set(position, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render update the display with the data from the LED buffer
|
||||||
|
func (c *RGBLedMatrix) Render() error {
|
||||||
|
w, h := c.Config.geometry()
|
||||||
|
|
||||||
|
C.led_matrix_swap(
|
||||||
|
c.matrix,
|
||||||
|
c.buffer,
|
||||||
|
C.int(w), C.int(h),
|
||||||
|
(*C.uint32_t)(unsafe.Pointer(&c.leds[0])),
|
||||||
|
)
|
||||||
|
|
||||||
|
c.leds = make([]C.uint32_t, w*h)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// At return an Color which allows access to the LED display data as
|
||||||
|
// if it were a sequence of 24-bit RGB values.
|
||||||
|
func (c *RGBLedMatrix) At(position int) color.Color {
|
||||||
|
return uint32ToColor(c.leds[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set set LED at position x,y to the provided 24-bit color value.
|
||||||
|
func (c *RGBLedMatrix) Set(position int, color color.Color) {
|
||||||
|
c.leds[position] = C.uint32_t(colorToUint32(color))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close finalizes the ws281x interface
|
||||||
|
func (c *RGBLedMatrix) Close() error {
|
||||||
|
C.led_matrix_delete(c.matrix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorToUint32(c color.Color) uint32 {
|
||||||
|
if c == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// A color's RGBA method returns values in the range [0, 65535]
|
||||||
|
red, green, blue, _ := c.RGBA()
|
||||||
|
return (red>>8)<<16 | (green>>8)<<8 | blue>>8
|
||||||
|
}
|
||||||
|
|
||||||
|
func uint32ToColor(u C.uint32_t) color.Color {
|
||||||
|
return color.RGBA{
|
||||||
|
uint8(u>>16) & 255,
|
||||||
|
uint8(u>>8) & 255,
|
||||||
|
uint8(u>>0) & 255,
|
||||||
|
0,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"image/color"
|
||||||
|
"net/rpc"
|
||||||
|
|
||||||
|
"github.com/RockKeeper/go-rpi-rgb-led-matrix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(color.RGBA{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGBLedMatrix matrix representation for ws281x
|
||||||
|
type Client struct {
|
||||||
|
network string
|
||||||
|
addr string
|
||||||
|
client *rpc.Client
|
||||||
|
leds []color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRGBLedMatrix returns a new matrix using the given size and config
|
||||||
|
func NewClient(network, addr string) (rgbmatrix.Matrix, error) {
|
||||||
|
client, err := rpc.DialHTTP(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
network: network,
|
||||||
|
addr: addr,
|
||||||
|
client: client,
|
||||||
|
leds: make([]color.Color, 2048),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Geometry returns the width and the height of the matrix
|
||||||
|
func (c *Client) Geometry() (width, height int) {
|
||||||
|
var reply *GeometryReply
|
||||||
|
err := c.client.Call("RPCMatrix.Geometry", &GeometryArgs{}, &reply)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.Width, reply.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Apply(leds []color.Color) error {
|
||||||
|
defer func() { c.leds = make([]color.Color, 2048) }()
|
||||||
|
|
||||||
|
var reply *ApplyReply
|
||||||
|
return c.client.Call("RPCMatrix.Apply", &ApplyArgs{Colors: leds}, &reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render update the display with the data from the LED buffer
|
||||||
|
func (c *Client) Render() error {
|
||||||
|
return c.Apply(c.leds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At return an Color which allows access to the LED display data as
|
||||||
|
// if it were a sequence of 24-bit RGB values.
|
||||||
|
func (c *Client) At(position int) color.Color {
|
||||||
|
if c.leds[position] == nil {
|
||||||
|
return color.Black
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.leds[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set set LED at position x,y to the provided 24-bit color value.
|
||||||
|
func (m *Client) Set(position int, c color.Color) {
|
||||||
|
m.leds[position] = color.RGBAModel.Convert(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close finalizes the ws281x interface
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
return c.Apply(make([]color.Color, 2048))
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/rpc"
|
||||||
|
|
||||||
|
"github.com/RockKeeper/go-rpi-rgb-led-matrix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RPCMatrix struct {
|
||||||
|
m rgbmatrix.Matrix
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeometryArgs struct{}
|
||||||
|
type GeometryReply struct{ Width, Height int }
|
||||||
|
|
||||||
|
func (m *RPCMatrix) Geometry(_ *GeometryArgs, reply *GeometryReply) error {
|
||||||
|
w, h := m.m.Geometry()
|
||||||
|
reply.Width = w
|
||||||
|
reply.Height = h
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApplyArgs struct{ Colors []color.Color }
|
||||||
|
type ApplyReply struct{}
|
||||||
|
|
||||||
|
func (m *RPCMatrix) Apply(args *ApplyArgs, reply *ApplyReply) error {
|
||||||
|
return m.m.Apply(args.Colors)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloseArgs struct{}
|
||||||
|
type CloseReply struct{}
|
||||||
|
|
||||||
|
func (m *RPCMatrix) Close(_ *CloseArgs, _ *CloseReply) error {
|
||||||
|
return m.m.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Serve(m rgbmatrix.Matrix) {
|
||||||
|
rpc.Register(&RPCMatrix{m})
|
||||||
|
|
||||||
|
rpc.HandleHTTP()
|
||||||
|
l, e := net.Listen("tcp", ":1234")
|
||||||
|
if e != nil {
|
||||||
|
log.Fatal("listen error:", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(l)
|
||||||
|
http.Serve(l, nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package rgbmatrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
"image/gif"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToolKit is a convinient set of function to operate with a led of Matrix
|
||||||
|
type ToolKit struct {
|
||||||
|
// Canvas is the Canvas wrapping the Matrix, if you want to instanciate
|
||||||
|
// a ToolKit with a custom Canvas you can use directly the struct,
|
||||||
|
// without calling NewToolKit
|
||||||
|
Canvas *Canvas
|
||||||
|
|
||||||
|
// Transform function if present is applied just before draw the image to
|
||||||
|
// the Matrix, this is a small example:
|
||||||
|
// tk.Transform = func(img image.Image) *image.NRGBA {
|
||||||
|
// return imaging.Fill(img, 64, 96, imaging.Center, imaging.Lanczos)
|
||||||
|
// }
|
||||||
|
Transform func(img image.Image) *image.NRGBA
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewToolKit returns a new ToolKit wrapping the given Matrix
|
||||||
|
func NewToolKit(m Matrix) *ToolKit {
|
||||||
|
return &ToolKit{
|
||||||
|
Canvas: NewCanvas(m),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlayImage draws the given image during the given delay
|
||||||
|
func (tk *ToolKit) PlayImage(i image.Image, delay time.Duration) error {
|
||||||
|
start := time.Now()
|
||||||
|
defer func() { time.Sleep(delay - time.Since(start)) }()
|
||||||
|
|
||||||
|
if tk.Transform != nil {
|
||||||
|
i = tk.Transform(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
draw.Draw(tk.Canvas, tk.Canvas.Bounds(), i, image.ZP, draw.Over)
|
||||||
|
return tk.Canvas.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Animation interface {
|
||||||
|
Next() (image.Image, <-chan time.Time, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlayAnimation play the image during the delay returned by Next, until an err
|
||||||
|
// is returned, if io.EOF is returned, PlayAnimation finish without an error
|
||||||
|
func (tk *ToolKit) PlayAnimation(a Animation) error {
|
||||||
|
var err error
|
||||||
|
var i image.Image
|
||||||
|
var n <-chan time.Time
|
||||||
|
|
||||||
|
for {
|
||||||
|
i, n, err = a.Next()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tk.PlayImageUntil(i, n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlayImageUntil draws the given image until is notified to stop
|
||||||
|
func (tk *ToolKit) PlayImageUntil(i image.Image, notify <-chan time.Time) error {
|
||||||
|
defer func() {
|
||||||
|
<-notify
|
||||||
|
}()
|
||||||
|
|
||||||
|
if tk.Transform != nil {
|
||||||
|
i = tk.Transform(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
draw.Draw(tk.Canvas, tk.Canvas.Bounds(), i, image.ZP, draw.Over)
|
||||||
|
return tk.Canvas.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlayImages draws a sequence of images during the given delays, the len of
|
||||||
|
// images should be equal to the len of delay. If loop is true the function
|
||||||
|
// loops over images until a true is sent to the returned chan
|
||||||
|
func (tk *ToolKit) PlayImages(images []image.Image, delay []time.Duration, loop int) chan bool {
|
||||||
|
quit := make(chan bool, 0)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
l := len(images)
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
tk.PlayImage(images[i], delay[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
if i >= l {
|
||||||
|
if loop == 0 {
|
||||||
|
i = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return quit
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlayGIF reads and draw a gif file from r. It use the contained images and
|
||||||
|
// delays and loops over it, until a true is sent to the returned chan
|
||||||
|
func (tk *ToolKit) PlayGIF(r io.Reader) (chan bool, error) {
|
||||||
|
gif, err := gif.DecodeAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := make([]time.Duration, len(gif.Delay))
|
||||||
|
images := make([]image.Image, len(gif.Image))
|
||||||
|
for i, image := range gif.Image {
|
||||||
|
images[i] = image
|
||||||
|
delay[i] = time.Millisecond * time.Duration(gif.Delay[i]) * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
return tk.PlayImages(images, delay, gif.LoopCount), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close the toolkit and the inner canvas
|
||||||
|
func (tk *ToolKit) Close() error {
|
||||||
|
return tk.Canvas.Close()
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package mempool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotWebsocket = errors.New("provided addr was not websocket")
|
||||||
|
)
|
||||||
|
|
||||||
|
type mempool struct {
|
||||||
|
addr string
|
||||||
|
ws *websocket.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(addr string) *mempool {
|
||||||
|
return &mempool{
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mempool) Init() error {
|
||||||
|
u, err := url.Parse(m.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "wss" && u.Scheme != "ws" {
|
||||||
|
return fmt.Errorf("%w: got %s", ErrNotWebsocket, u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.ws = c
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mempool) Listen(ctx context.Context, blocks chan Block, errs chan error, done chan struct{}) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
close(blocks)
|
||||||
|
close(errs)
|
||||||
|
close(done)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
_, message, err := m.ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
errs <- err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var b Block
|
||||||
|
err = json.Unmarshal(message, &b)
|
||||||
|
if err != nil {
|
||||||
|
errs <- err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks <- b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mempool) Shutdown(ctx context.Context) error {
|
||||||
|
err := m.ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.ws.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package mempool
|
||||||
|
|
||||||
|
type Block struct {
|
||||||
|
Height int64 `json:"height"`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user