├── .gitmodules
├── .travis.yml
├── examples
├── rpc
│ ├── client
│ │ └── main.go
│ └── server
│ │ └── main.go
├── basic
│ └── main.go
├── image
│ └── main.go
└── animation
│ └── main.go
├── LICENSE
├── rpc
├── server.go
└── client.go
├── canvas.go
├── canvas_test.go
├── toolkit.go
├── README.md
├── emulator
└── emulator.go
└── matrix.go
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vendor/rpi-rgb-led-matrix"]
2 | path = vendor/rpi-rgb-led-matrix
3 | url = https://github.com/hzeller/rpi-rgb-led-matrix.git
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - 1.6
5 | - 1.7
6 | - tip
7 |
8 | before_install:
9 | - cd $GOPATH/src/github.com/mcuadros/go-rpi-rgb-led-matrix/vendor/rpi-rgb-led-matrix/
10 | - git submodule update --init
11 | - make
12 | - cd $GOPATH/src/github.com/mcuadros/go-rpi-rgb-led-matrix/
13 | - go get -t -v ./...
14 | - go install -v ./...
15 |
--------------------------------------------------------------------------------
/examples/rpc/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "os"
6 | "time"
7 |
8 | "github.com/mcuadros/go-rpi-rgb-led-matrix"
9 | "github.com/mcuadros/go-rpi-rgb-led-matrix/rpc"
10 | )
11 |
12 | var (
13 | img = flag.String("image", "", "image path")
14 | )
15 |
16 | func main() {
17 | f, err := os.Open(*img)
18 | fatal(err)
19 |
20 | m, err := rpc.NewClient("tcp", "10.20.20.20:1234")
21 | fatal(err)
22 |
23 | tk := rgbmatrix.NewToolKit(m)
24 | close, err := tk.PlayGIF(f)
25 | fatal(err)
26 |
27 | time.Sleep(time.Second * 3)
28 | close <- true
29 | }
30 |
31 | func init() {
32 | flag.Parse()
33 | }
34 |
35 | func fatal(err error) {
36 | if err != nil {
37 | panic(err)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Máximo Cuadros
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/rpc/server.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "fmt"
5 | "image/color"
6 | "log"
7 | "net"
8 | "net/http"
9 | "net/rpc"
10 |
11 | "github.com/mcuadros/go-rpi-rgb-led-matrix"
12 | )
13 |
14 | type RPCMatrix struct {
15 | m rgbmatrix.Matrix
16 | }
17 |
18 | type GeometryArgs struct{}
19 | type GeometryReply struct{ Width, Height int }
20 |
21 | func (m *RPCMatrix) Geometry(_ *GeometryArgs, reply *GeometryReply) error {
22 | w, h := m.m.Geometry()
23 | reply.Width = w
24 | reply.Height = h
25 |
26 | return nil
27 | }
28 |
29 | type ApplyArgs struct{ Colors []color.Color }
30 | type ApplyReply struct{}
31 |
32 | func (m *RPCMatrix) Apply(args *ApplyArgs, reply *ApplyReply) error {
33 | return m.m.Apply(args.Colors)
34 | }
35 |
36 | type CloseArgs struct{}
37 | type CloseReply struct{}
38 |
39 | func (m *RPCMatrix) Close(_ *CloseArgs, _ *CloseReply) error {
40 | return m.m.Close()
41 | }
42 |
43 | func Serve(m rgbmatrix.Matrix) {
44 | rpc.Register(&RPCMatrix{m})
45 |
46 | rpc.HandleHTTP()
47 | l, e := net.Listen("tcp", ":1234")
48 | if e != nil {
49 | log.Fatal("listen error:", e)
50 | }
51 |
52 | fmt.Println(l)
53 | http.Serve(l, nil)
54 | }
55 |
--------------------------------------------------------------------------------
/examples/rpc/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 |
6 | "github.com/mcuadros/go-rpi-rgb-led-matrix"
7 | "github.com/mcuadros/go-rpi-rgb-led-matrix/rpc"
8 | )
9 |
10 | var (
11 | rows = flag.Int("led-rows", 32, "number of rows supported")
12 | cols = flag.Int("led-cols", 32, "number of columns supported")
13 | parallel = flag.Int("led-parallel", 1, "number of daisy-chained panels")
14 | chain = flag.Int("led-chain", 2, "number of displays daisy-chained")
15 | brightness = flag.Int("brightness", 100, "brightness (0-100)")
16 | hardware_mapping = flag.String("led-gpio-mapping", "regular", "Name of GPIO mapping used.")
17 | show_refresh = flag.Bool("led-show-refresh", false, "Show refresh rate.")
18 | inverse_colors = flag.Bool("led-inverse", false, "Switch if your matrix has inverse colors on.")
19 | disable_hardware_pulsing = flag.Bool("led-no-hardware-pulse", false, "Don't use hardware pin-pulse generation.")
20 | )
21 |
22 | func main() {
23 | config := &rgbmatrix.DefaultConfig
24 | config.Rows = *rows
25 | config.Cols = *cols
26 | config.Parallel = *parallel
27 | config.ChainLength = *chain
28 | config.Brightness = *brightness
29 | config.HardwareMapping = *hardware_mapping
30 | config.ShowRefreshRate = *show_refresh
31 | config.InverseColors = *inverse_colors
32 | config.DisableHardwarePulsing = *disable_hardware_pulsing
33 |
34 | m, err := rgbmatrix.NewRGBLedMatrix(config)
35 | fatal(err)
36 |
37 | rpc.Serve(m)
38 | }
39 |
40 | func fatal(err error) {
41 | if err != nil {
42 | panic(err)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/basic/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "image/color"
7 |
8 | "github.com/mcuadros/go-rpi-rgb-led-matrix"
9 | )
10 |
11 | var (
12 | rows = flag.Int("led-rows", 32, "number of rows supported")
13 | cols = flag.Int("led-cols", 32, "number of columns supported")
14 | parallel = flag.Int("led-parallel", 1, "number of daisy-chained panels")
15 | chain = flag.Int("led-chain", 2, "number of displays daisy-chained")
16 | brightness = flag.Int("brightness", 100, "brightness (0-100)")
17 | hardware_mapping = flag.String("led-gpio-mapping", "regular", "Name of GPIO mapping used.")
18 | show_refresh = flag.Bool("led-show-refresh", false, "Show refresh rate.")
19 | inverse_colors = flag.Bool("led-inverse", false, "Switch if your matrix has inverse colors on.")
20 | disable_hardware_pulsing = flag.Bool("led-no-hardware-pulse", false, "Don't use hardware pin-pulse generation.")
21 | )
22 |
23 | func main() {
24 | config := &rgbmatrix.DefaultConfig
25 | config.Rows = *rows
26 | config.Cols = *cols
27 | config.Parallel = *parallel
28 | config.ChainLength = *chain
29 | config.Brightness = *brightness
30 | config.HardwareMapping = *hardware_mapping
31 | config.ShowRefreshRate = *show_refresh
32 | config.InverseColors = *inverse_colors
33 | config.DisableHardwarePulsing = *disable_hardware_pulsing
34 |
35 | m, err := rgbmatrix.NewRGBLedMatrix(config)
36 | fatal(err)
37 |
38 | c := rgbmatrix.NewCanvas(m)
39 | defer c.Close()
40 |
41 | bounds := c.Bounds()
42 | for x := bounds.Min.X; x < bounds.Max.X; x++ {
43 | for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
44 | fmt.Println("x", x, "y", y)
45 | c.Set(x, y, color.RGBA{255, 0, 0, 255})
46 | c.Render()
47 | }
48 | }
49 | }
50 |
51 | func init() {
52 | flag.Parse()
53 | }
54 |
55 | func fatal(err error) {
56 | if err != nil {
57 | panic(err)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/rpc/client.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "encoding/gob"
5 | "image/color"
6 | "net/rpc"
7 |
8 | "github.com/mcuadros/go-rpi-rgb-led-matrix"
9 | )
10 |
11 | func init() {
12 | gob.Register(color.RGBA{})
13 | }
14 |
15 | // RGBLedMatrix matrix representation for ws281x
16 | type Client struct {
17 | network string
18 | addr string
19 | client *rpc.Client
20 | leds []color.Color
21 | }
22 |
23 | // NewRGBLedMatrix returns a new matrix using the given size and config
24 | func NewClient(network, addr string) (rgbmatrix.Matrix, error) {
25 | client, err := rpc.DialHTTP(network, addr)
26 | if err != nil {
27 | return nil, err
28 | }
29 |
30 | return &Client{
31 | network: network,
32 | addr: addr,
33 | client: client,
34 | leds: make([]color.Color, 2048),
35 | }, nil
36 | }
37 |
38 | // Geometry returns the width and the height of the matrix
39 | func (c *Client) Geometry() (width, height int) {
40 | var reply *GeometryReply
41 | err := c.client.Call("RPCMatrix.Geometry", &GeometryArgs{}, &reply)
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | return reply.Width, reply.Height
47 | }
48 |
49 | func (c *Client) Apply(leds []color.Color) error {
50 | defer func() { c.leds = make([]color.Color, 2048) }()
51 |
52 | var reply *ApplyReply
53 | return c.client.Call("RPCMatrix.Apply", &ApplyArgs{Colors: leds}, &reply)
54 | }
55 |
56 | // Render update the display with the data from the LED buffer
57 | func (c *Client) Render() error {
58 | return c.Apply(c.leds)
59 | }
60 |
61 | // At return an Color which allows access to the LED display data as
62 | // if it were a sequence of 24-bit RGB values.
63 | func (c *Client) At(position int) color.Color {
64 | if c.leds[position] == nil {
65 | return color.Black
66 | }
67 |
68 | return c.leds[position]
69 | }
70 |
71 | // Set set LED at position x,y to the provided 24-bit color value.
72 | func (m *Client) Set(position int, c color.Color) {
73 | m.leds[position] = color.RGBAModel.Convert(c)
74 | }
75 |
76 | // Close finalizes the ws281x interface
77 | func (c *Client) Close() error {
78 | return c.Apply(make([]color.Color, 2048))
79 | }
80 |
--------------------------------------------------------------------------------
/canvas.go:
--------------------------------------------------------------------------------
1 | package rgbmatrix
2 |
3 | import (
4 | "image"
5 | "image/color"
6 | "image/draw"
7 | )
8 |
9 | // Canvas is a image.Image representation of a WS281x matrix, it implements
10 | // image.Image interface and can be used with draw.Draw for example
11 | type Canvas struct {
12 | w, h int
13 | m Matrix
14 | closed bool
15 | }
16 |
17 | // NewCanvas returns a new Canvas using the given width and height and creates
18 | // a new WS281x matrix using the given config
19 | func NewCanvas(m Matrix) *Canvas {
20 | w, h := m.Geometry()
21 | return &Canvas{
22 | w: w,
23 | h: h,
24 | m: m,
25 | }
26 | }
27 |
28 | // Render update the display with the data from the LED buffer
29 | func (c *Canvas) Render() error {
30 | return c.m.Render()
31 | }
32 |
33 | // ColorModel returns the canvas' color model, always color.RGBAModel
34 | func (c *Canvas) ColorModel() color.Model {
35 | return color.RGBAModel
36 | }
37 |
38 | // Bounds return the topology of the Canvas
39 | func (c *Canvas) Bounds() image.Rectangle {
40 | return image.Rect(0, 0, c.w, c.h)
41 | }
42 |
43 | // At returns the color of the pixel at (x, y)
44 | func (c *Canvas) At(x, y int) color.Color {
45 | return c.m.At(c.position(x, y))
46 | }
47 |
48 | // Set set LED at position x,y to the provided 24-bit color value
49 | func (c *Canvas) Set(x, y int, color color.Color) {
50 | c.m.Set(c.position(x, y), color)
51 | }
52 |
53 | func (c *Canvas) position(x, y int) int {
54 | return x + (y * c.w)
55 | }
56 |
57 | // Clear set all the leds on the matrix with color.Black
58 | func (c *Canvas) Clear() error {
59 | draw.Draw(c, c.Bounds(), &image.Uniform{color.Black}, image.ZP, draw.Src)
60 | return c.m.Render()
61 | }
62 |
63 | // Close clears the matrix and close the matrix
64 | func (c *Canvas) Close() error {
65 | c.Clear()
66 | return c.m.Close()
67 | }
68 |
69 | // Matrix is an interface that represent any RGB matrix, very useful for testing
70 | type Matrix interface {
71 | Geometry() (width, height int)
72 | At(position int) color.Color
73 | Set(position int, c color.Color)
74 | Apply([]color.Color) error
75 | Render() error
76 | Close() error
77 | }
78 |
--------------------------------------------------------------------------------
/examples/image/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "os"
6 | "time"
7 |
8 | "github.com/disintegration/imaging"
9 | "github.com/mcuadros/go-rpi-rgb-led-matrix"
10 | )
11 |
12 | var (
13 | rows = flag.Int("led-rows", 32, "number of rows supported")
14 | cols = flag.Int("led-cols", 32, "number of columns supported")
15 | parallel = flag.Int("led-parallel", 1, "number of daisy-chained panels")
16 | chain = flag.Int("led-chain", 2, "number of displays daisy-chained")
17 | brightness = flag.Int("brightness", 100, "brightness (0-100)")
18 | hardware_mapping = flag.String("led-gpio-mapping", "regular", "Name of GPIO mapping used.")
19 | show_refresh = flag.Bool("led-show-refresh", false, "Show refresh rate.")
20 | inverse_colors = flag.Bool("led-inverse", false, "Switch if your matrix has inverse colors on.")
21 | disable_hardware_pulsing = flag.Bool("led-no-hardware-pulse", false, "Don't use hardware pin-pulse generation.")
22 | img = flag.String("image", "", "image path")
23 |
24 | rotate = flag.Int("rotate", 0, "rotate angle, 90, 180, 270")
25 | )
26 |
27 | func main() {
28 | f, err := os.Open(*img)
29 | fatal(err)
30 |
31 | config := &rgbmatrix.DefaultConfig
32 | config.Rows = *rows
33 | config.Cols = *cols
34 | config.Parallel = *parallel
35 | config.ChainLength = *chain
36 | config.Brightness = *brightness
37 | config.HardwareMapping = *hardware_mapping
38 | config.ShowRefreshRate = *show_refresh
39 | config.InverseColors = *inverse_colors
40 | config.DisableHardwarePulsing = *disable_hardware_pulsing
41 |
42 | m, err := rgbmatrix.NewRGBLedMatrix(config)
43 | fatal(err)
44 |
45 | tk := rgbmatrix.NewToolKit(m)
46 | defer tk.Close()
47 |
48 | switch *rotate {
49 | case 90:
50 | tk.Transform = imaging.Rotate90
51 | case 180:
52 | tk.Transform = imaging.Rotate180
53 | case 270:
54 | tk.Transform = imaging.Rotate270
55 | }
56 |
57 | close, err := tk.PlayGIF(f)
58 | fatal(err)
59 |
60 | time.Sleep(time.Second * 30)
61 | close <- true
62 | }
63 |
64 | func init() {
65 | flag.Parse()
66 | }
67 |
68 | func fatal(err error) {
69 | if err != nil {
70 | panic(err)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/examples/animation/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "image"
6 | "image/color"
7 | "time"
8 |
9 | "github.com/fogleman/gg"
10 | "github.com/mcuadros/go-rpi-rgb-led-matrix"
11 | )
12 |
13 | var (
14 | rows = flag.Int("led-rows", 32, "number of rows supported")
15 | cols = flag.Int("led-cols", 32, "number of columns supported")
16 | parallel = flag.Int("led-parallel", 1, "number of daisy-chained panels")
17 | chain = flag.Int("led-chain", 2, "number of displays daisy-chained")
18 | brightness = flag.Int("brightness", 100, "brightness (0-100)")
19 | hardware_mapping = flag.String("led-gpio-mapping", "regular", "Name of GPIO mapping used.")
20 | show_refresh = flag.Bool("led-show-refresh", false, "Show refresh rate.")
21 | inverse_colors = flag.Bool("led-inverse", false, "Switch if your matrix has inverse colors on.")
22 | disable_hardware_pulsing = flag.Bool("led-no-hardware-pulse", false, "Don't use hardware pin-pulse generation.")
23 | )
24 |
25 | func main() {
26 | config := &rgbmatrix.DefaultConfig
27 | config.Rows = *rows
28 | config.Cols = *cols
29 | config.Parallel = *parallel
30 | config.ChainLength = *chain
31 | config.Brightness = *brightness
32 | config.HardwareMapping = *hardware_mapping
33 | config.ShowRefreshRate = *show_refresh
34 | config.InverseColors = *inverse_colors
35 | config.DisableHardwarePulsing = *disable_hardware_pulsing
36 |
37 | m, err := rgbmatrix.NewRGBLedMatrix(config)
38 | fatal(err)
39 |
40 | tk := rgbmatrix.NewToolKit(m)
41 | defer tk.Close()
42 |
43 | tk.PlayAnimation(NewAnimation(image.Point{64, 32}))
44 | }
45 |
46 | func init() {
47 | flag.Parse()
48 | }
49 |
50 | func fatal(err error) {
51 | if err != nil {
52 | panic(err)
53 | }
54 | }
55 |
56 | type Animation struct {
57 | ctx *gg.Context
58 | position image.Point
59 | dir image.Point
60 | stroke int
61 | }
62 |
63 | func NewAnimation(sz image.Point) *Animation {
64 | return &Animation{
65 | ctx: gg.NewContext(sz.X, sz.Y),
66 | dir: image.Point{1, 1},
67 | stroke: 5,
68 | }
69 | }
70 |
71 | func (a *Animation) Next() (image.Image, <-chan time.Time, error) {
72 | defer a.updatePosition()
73 |
74 | a.ctx.SetColor(color.Black)
75 | a.ctx.Clear()
76 |
77 | a.ctx.DrawCircle(float64(a.position.X), float64(a.position.Y), float64(a.stroke))
78 | a.ctx.SetColor(color.RGBA{255, 0, 0, 255})
79 | a.ctx.Fill()
80 | return a.ctx.Image(), time.After(time.Millisecond * 50), nil
81 | }
82 |
83 | func (a *Animation) updatePosition() {
84 | a.position.X += 1 * a.dir.X
85 | a.position.Y += 1 * a.dir.Y
86 |
87 | if a.position.Y+a.stroke > a.ctx.Height() {
88 | a.dir.Y = -1
89 | } else if a.position.Y-a.stroke < 0 {
90 | a.dir.Y = 1
91 | }
92 |
93 | if a.position.X+a.stroke > a.ctx.Width() {
94 | a.dir.X = -1
95 | } else if a.position.X-a.stroke < 0 {
96 | a.dir.X = 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/canvas_test.go:
--------------------------------------------------------------------------------
1 | package rgbmatrix
2 |
3 | import (
4 | "image/color"
5 | "testing"
6 |
7 | . "gopkg.in/check.v1"
8 | )
9 |
10 | func Test(t *testing.T) { TestingT(t) }
11 |
12 | type CanvasSuite struct{}
13 |
14 | var _ = Suite(&CanvasSuite{})
15 |
16 | func (s *CanvasSuite) TestNewCanvas(c *C) {
17 | canvas := NewCanvas(NewMatrixMock())
18 | c.Assert(canvas, NotNil)
19 | c.Assert(canvas.w, Equals, 64)
20 | c.Assert(canvas.h, Equals, 32)
21 | }
22 |
23 | func (s *CanvasSuite) TestRender(c *C) {
24 | m := NewMatrixMock()
25 | canvas := &Canvas{m: m}
26 | canvas.Render()
27 |
28 | c.Assert(m.called["Render"], Equals, true)
29 | }
30 |
31 | func (s *CanvasSuite) TestColorModel(c *C) {
32 | canvas := &Canvas{}
33 |
34 | c.Assert(canvas.ColorModel(), Equals, color.RGBAModel)
35 | }
36 |
37 | func (s *CanvasSuite) TestBounds(c *C) {
38 |
39 | canvas := &Canvas{w: 10, h: 20}
40 |
41 | b := canvas.Bounds()
42 | c.Assert(b.Min.X, Equals, 0)
43 | c.Assert(b.Min.Y, Equals, 0)
44 | c.Assert(b.Max.X, Equals, 10)
45 | c.Assert(b.Max.Y, Equals, 20)
46 | }
47 |
48 | func (s *CanvasSuite) TestAt(c *C) {
49 | m := NewMatrixMock()
50 | canvas := &Canvas{w: 10, h: 20, m: m}
51 | canvas.At(5, 15)
52 |
53 | c.Assert(m.called["At"], Equals, 155)
54 | }
55 |
56 | func (s *CanvasSuite) TestSet(c *C) {
57 | m := NewMatrixMock()
58 | canvas := &Canvas{w: 10, h: 20, m: m}
59 | canvas.Set(5, 15, color.White)
60 |
61 | c.Assert(m.called["Set"], Equals, 155)
62 | c.Assert(m.colors[155], Equals, color.White)
63 | }
64 |
65 | func (s *CanvasSuite) TestClear(c *C) {
66 | m := NewMatrixMock()
67 |
68 | canvas := &Canvas{w: 10, h: 20, m: m}
69 | err := canvas.Clear()
70 | c.Assert(err, IsNil)
71 |
72 | for _, px := range m.colors {
73 | c.Assert(px, Equals, color.Black)
74 | }
75 |
76 | c.Assert(m.called["Render"], Equals, true)
77 | }
78 |
79 | func (s *CanvasSuite) TestClose(c *C) {
80 | m := NewMatrixMock()
81 | canvas := &Canvas{w: 10, h: 20, m: m}
82 | err := canvas.Close()
83 | c.Assert(err, IsNil)
84 |
85 | for _, px := range m.colors {
86 | c.Assert(px, Equals, color.Black)
87 | }
88 |
89 | c.Assert(m.called["Render"], Equals, true)
90 | }
91 |
92 | type MatrixMock struct {
93 | called map[string]interface{}
94 | colors []color.Color
95 | }
96 |
97 | func NewMatrixMock() *MatrixMock {
98 | return &MatrixMock{
99 | called: make(map[string]interface{}, 0),
100 | colors: make([]color.Color, 200),
101 | }
102 | }
103 |
104 | func (m *MatrixMock) Geometry() (width, height int) {
105 | return 64, 32
106 | }
107 |
108 | func (m *MatrixMock) Initialize() error {
109 | m.called["Initialize"] = true
110 | return nil
111 | }
112 |
113 | func (m *MatrixMock) At(position int) color.Color {
114 | m.called["At"] = position
115 | return color.Black
116 | }
117 |
118 | func (m *MatrixMock) Set(position int, c color.Color) {
119 | m.called["Set"] = position
120 | m.colors[position] = c
121 | }
122 |
123 | func (m *MatrixMock) Apply(leds []color.Color) error {
124 | for position, l := range leds {
125 | m.Set(position, l)
126 | }
127 |
128 | return m.Render()
129 | }
130 |
131 | func (m *MatrixMock) Render() error {
132 | m.called["Render"] = true
133 | return nil
134 | }
135 |
136 | func (m *MatrixMock) Close() error {
137 | m.called["Close"] = true
138 | return nil
139 | }
140 |
--------------------------------------------------------------------------------
/toolkit.go:
--------------------------------------------------------------------------------
1 | package rgbmatrix
2 |
3 | import (
4 | "image"
5 | "image/draw"
6 | "image/gif"
7 | "io"
8 | "time"
9 | )
10 |
11 | // ToolKit is a convinient set of function to operate with a led of Matrix
12 | type ToolKit struct {
13 | // Canvas is the Canvas wrapping the Matrix, if you want to instanciate
14 | // a ToolKit with a custom Canvas you can use directly the struct,
15 | // without calling NewToolKit
16 | Canvas *Canvas
17 |
18 | // Transform function if present is applied just before draw the image to
19 | // the Matrix, this is a small example:
20 | // tk.Transform = func(img image.Image) *image.NRGBA {
21 | // return imaging.Fill(img, 64, 96, imaging.Center, imaging.Lanczos)
22 | // }
23 | Transform func(img image.Image) *image.NRGBA
24 | }
25 |
26 | // NewToolKit returns a new ToolKit wrapping the given Matrix
27 | func NewToolKit(m Matrix) *ToolKit {
28 | return &ToolKit{
29 | Canvas: NewCanvas(m),
30 | }
31 | }
32 |
33 | // PlayImage draws the given image during the given delay
34 | func (tk *ToolKit) PlayImage(i image.Image, delay time.Duration) error {
35 | start := time.Now()
36 | defer func() { time.Sleep(delay - time.Since(start)) }()
37 |
38 | if tk.Transform != nil {
39 | i = tk.Transform(i)
40 | }
41 |
42 | draw.Draw(tk.Canvas, tk.Canvas.Bounds(), i, image.ZP, draw.Over)
43 | return tk.Canvas.Render()
44 | }
45 |
46 | type Animation interface {
47 | Next() (image.Image, <-chan time.Time, error)
48 | }
49 |
50 | // PlayAnimation play the image during the delay returned by Next, until an err
51 | // is returned, if io.EOF is returned, PlayAnimation finish without an error
52 | func (tk *ToolKit) PlayAnimation(a Animation) error {
53 | var err error
54 | var i image.Image
55 | var n <-chan time.Time
56 |
57 | for {
58 | i, n, err = a.Next()
59 | if err != nil {
60 | break
61 | }
62 |
63 | if err := tk.PlayImageUntil(i, n); err != nil {
64 | return err
65 | }
66 | }
67 |
68 | if err == io.EOF {
69 | return nil
70 | }
71 |
72 | return err
73 | }
74 |
75 | // PlayImageUntil draws the given image until is notified to stop
76 | func (tk *ToolKit) PlayImageUntil(i image.Image, notify <-chan time.Time) error {
77 | defer func() {
78 | <-notify
79 | }()
80 |
81 | if tk.Transform != nil {
82 | i = tk.Transform(i)
83 | }
84 |
85 | draw.Draw(tk.Canvas, tk.Canvas.Bounds(), i, image.ZP, draw.Over)
86 | return tk.Canvas.Render()
87 | }
88 |
89 | // PlayImages draws a sequence of images during the given delays, the len of
90 | // images should be equal to the len of delay. If loop is true the function
91 | // loops over images until a true is sent to the returned chan
92 | func (tk *ToolKit) PlayImages(images []image.Image, delay []time.Duration, loop int) chan bool {
93 | quit := make(chan bool, 0)
94 |
95 | go func() {
96 | l := len(images)
97 | i := 0
98 | for {
99 | select {
100 | case <-quit:
101 | return
102 | default:
103 | tk.PlayImage(images[i], delay[i])
104 | }
105 |
106 | i++
107 | if i >= l {
108 | if loop == 0 {
109 | i = 0
110 | continue
111 | }
112 |
113 | break
114 | }
115 | }
116 | }()
117 |
118 | return quit
119 | }
120 |
121 | // PlayGIF reads and draw a gif file from r. It use the contained images and
122 | // delays and loops over it, until a true is sent to the returned chan
123 | func (tk *ToolKit) PlayGIF(r io.Reader) (chan bool, error) {
124 | gif, err := gif.DecodeAll(r)
125 | if err != nil {
126 | return nil, err
127 | }
128 |
129 | delay := make([]time.Duration, len(gif.Delay))
130 | images := make([]image.Image, len(gif.Image))
131 | for i, image := range gif.Image {
132 | images[i] = image
133 | delay[i] = time.Millisecond * time.Duration(gif.Delay[i]) * 10
134 | }
135 |
136 | return tk.PlayImages(images, delay, gif.LoopCount), nil
137 | }
138 |
139 | // Close close the toolkit and the inner canvas
140 | func (tk *ToolKit) Close() error {
141 | return tk.Canvas.Close()
142 | }
143 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 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)
2 |
3 |
4 | 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.
5 |
6 | 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.
7 |
8 | 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.
9 |
10 | 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.
11 |
12 | Installation
13 | ------------
14 |
15 | The recommended way to install `go-rpi-rgb-led-matrix` is:
16 |
17 | ```sh
18 | go get github.com/mcuadros/go-rpi-rgb-led-matrix
19 | ```
20 |
21 | Then you will get an **expected** error like this:
22 |
23 | ```
24 | # github.com/mcuadros/go-rpi-rgb-led-matrix
25 | /usr/bin/ld: cannot find -lrgbmatrix
26 | collect2: error: ld returned 1 exit status
27 | ```
28 |
29 | This happens because you need to compile the `rgbmatrix` C bindings:
30 | ```sh
31 | cd $GOPATH/src/github.com/mcuadros/go-rpi-rgb-led-matrix/vendor/rpi-rgb-led-matrix/
32 | git submodule update --init
33 | make
34 | cd $GOPATH/src/github.com/mcuadros/go-rpi-rgb-led-matrix/
35 | go install -v ./...
36 | ```
37 |
38 | Examples
39 | --------
40 |
41 | Setting all the pixels to white:
42 |
43 | ```go
44 | // create a new Matrix instance with the DefaultConfig
45 | m, _ := rgbmatrix.NewRGBLedMatrix(&rgbmatrix.DefaultConfig)
46 |
47 | // create the Canvas, implements the image.Image interface
48 | c := rgbmatrix.NewCanvas(m)
49 | defer c.Close() // don't forgot close the Matrix, if not your leds will remain on
50 |
51 | // using the standard draw.Draw function we copy a white image onto the Canvas
52 | draw.Draw(c, c.Bounds(), &image.Uniform{color.White}, image.ZP, draw.Src)
53 |
54 | // don't forget call Render to display the new led status
55 | c.Render()
56 | ```
57 |
58 | Playing a GIF into your matrix during 30 seconds:
59 |
60 | ```go
61 | // create a new Matrix instance with the DefaultConfig
62 | m, _ := rgbmatrix.NewRGBLedMatrix(&rgbmatrix.DefaultConfig)
63 |
64 | // create a ToolKit instance
65 | tk := rgbmatrix.NewToolKit(m)
66 | defer tk.Close() // don't forgot close the Matrix, if not your leds will remain on
67 |
68 | // open the gif file for reading
69 | file, _ := os.Open("mario.gif")
70 |
71 | // play of the gif using the io.Reader
72 | close, _ := tk.PlayGIF(f)
73 | fatal(err)
74 |
75 | // we wait 30 seconds and then we stop the playing gif sending a True to the returned chan
76 | time.Sleep(time.Second * 30)
77 | close <- true
78 | ```
79 |
80 | The image of the header was recorded using this few lines, the running _Mario_ gif, and three 32x64 pannels.
81 |
82 |
83 | Check the folder [`examples`](https://github.com/mcuadros/go-rpi-rgb-led-matrix/tree/master/examples) folder for more examples
84 |
85 |
86 | Matrix Emulation
87 | ----------------
88 |
89 | 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.
90 |
91 | 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.
92 |
93 |
94 | License
95 | -------
96 |
97 | MIT, see [LICENSE](LICENSE)
98 |
--------------------------------------------------------------------------------
/emulator/emulator.go:
--------------------------------------------------------------------------------
1 | package emulator
2 |
3 | import (
4 | "fmt"
5 | "image"
6 | "image/color"
7 | "os"
8 | "sync"
9 |
10 | "golang.org/x/exp/shiny/driver"
11 | "golang.org/x/exp/shiny/screen"
12 | "golang.org/x/mobile/event/paint"
13 | "golang.org/x/mobile/event/size"
14 | )
15 |
16 | const DefaultPixelPitch = 12
17 | const windowTitle = "RGB led matrix emulator"
18 |
19 | type Emulator struct {
20 | PixelPitch int
21 | Gutter int
22 | Width int
23 | Height int
24 | GutterColor color.Color
25 | PixelPitchToGutterRatio int
26 | Margin int
27 |
28 | leds []color.Color
29 | w screen.Window
30 | s screen.Screen
31 | wg sync.WaitGroup
32 |
33 | isReady bool
34 | }
35 |
36 | func NewEmulator(w, h, pixelPitch int, autoInit bool) *Emulator {
37 | e := &Emulator{
38 | Width: w,
39 | Height: h,
40 | GutterColor: color.Gray{Y: 20},
41 | PixelPitchToGutterRatio: 2,
42 | Margin: 10,
43 | }
44 | e.updatePixelPitchForGutter(pixelPitch / e.PixelPitchToGutterRatio)
45 |
46 | if autoInit {
47 | e.Init()
48 | }
49 |
50 | return e
51 | }
52 |
53 | // Init initialize the emulator, creating a new Window and waiting until is
54 | // painted. If something goes wrong the function panics
55 | func (e *Emulator) Init() {
56 | e.leds = make([]color.Color, e.Width*e.Height)
57 |
58 | e.wg.Add(1)
59 | go driver.Main(e.mainWindowLoop)
60 | e.wg.Wait()
61 | }
62 |
63 | func (e *Emulator) mainWindowLoop(s screen.Screen) {
64 | var err error
65 | e.s = s
66 | // Calculate initial window size based on whatever our gutter/pixel pitch currently is.
67 | dims := e.matrixWithMarginsRect()
68 | e.w, err = s.NewWindow(&screen.NewWindowOptions{
69 | Title: windowTitle,
70 | Width: dims.Max.X,
71 | Height: dims.Max.Y,
72 | })
73 |
74 | if err != nil {
75 | panic(err)
76 | }
77 |
78 | defer e.w.Release()
79 |
80 | var sz size.Event
81 | for {
82 | evn := e.w.NextEvent()
83 | switch evn := evn.(type) {
84 | case paint.Event:
85 | e.drawContext(sz)
86 | if e.isReady {
87 | continue
88 | }
89 |
90 | e.Apply(make([]color.Color, e.Width*e.Height))
91 | e.wg.Done()
92 | e.isReady = true
93 | case size.Event:
94 | sz = evn
95 |
96 | case error:
97 | fmt.Fprintln(os.Stderr, e)
98 | }
99 | }
100 | }
101 |
102 | func (e *Emulator) drawContext(sz size.Event) {
103 | e.updatePixelPitchForGutter(e.calculateGutterForViewableArea(sz.Size()))
104 | // Fill entire background with white.
105 | e.w.Fill(sz.Bounds(), color.White, screen.Src)
106 | // Fill matrix display rectangle with the gutter color.
107 | e.w.Fill(e.matrixWithMarginsRect(), e.GutterColor, screen.Src)
108 | // Set all LEDs to black.
109 | e.Apply(make([]color.Color, e.Width*e.Height))
110 | }
111 |
112 | // Some formulas that allowed me to better understand the drawable area. I found that the math was
113 | // easiest when put in terms of the Gutter width, hence the addition of PixelPitchToGutterRatio.
114 | //
115 | // PixelPitch = PixelPitchToGutterRatio * Gutter
116 | // DisplayWidth = (PixelPitch * LEDColumns) + (Gutter * (LEDColumns - 1)) + (2 * Margin)
117 | // Gutter = (DisplayWidth - (2 * Margin)) / (PixelPitchToGutterRatio * LEDColumns + LEDColumns - 1)
118 | //
119 | // MMMMMMMMMMMMMMMM.....MMMM
120 | // MGGGGGGGGGGGGGGG.....GGGM
121 | // MGLGLGLGLGLGLGLG.....GLGM
122 | // MGGGGGGGGGGGGGGG.....GGGM
123 | // MGLGLGLGLGLGLGLG.....GLGM
124 | // MGGGGGGGGGGGGGGG.....GGGM
125 | // .........................
126 | // MGGGGGGGGGGGGGGG.....GGGM
127 | // MGLGLGLGLGLGLGLG.....GLGM
128 | // MGGGGGGGGGGGGGGG.....GGGM
129 | // MMMMMMMMMMMMMMMM.....MMMM
130 | //
131 | // where:
132 | // M = Margin
133 | // G = Gutter
134 | // L = LED
135 |
136 | // matrixWithMarginsRect Returns a Rectangle that describes entire emulated RGB Matrix, including margins.
137 | func (e *Emulator) matrixWithMarginsRect() image.Rectangle {
138 | upperLeftLED := e.ledRect(0, 0)
139 | lowerRightLED := e.ledRect(e.Width-1, e.Height-1)
140 | return image.Rect(upperLeftLED.Min.X-e.Margin, upperLeftLED.Min.Y-e.Margin, lowerRightLED.Max.X+e.Margin, lowerRightLED.Max.Y+e.Margin)
141 | }
142 |
143 | // ledRect Returns a Rectangle for the LED at col and row.
144 | func (e *Emulator) ledRect(col int, row int) image.Rectangle {
145 | x := (col * (e.PixelPitch + e.Gutter)) + e.Margin
146 | y := (row * (e.PixelPitch + e.Gutter)) + e.Margin
147 | return image.Rect(x, y, x+e.PixelPitch, y+e.PixelPitch)
148 | }
149 |
150 | // calculateGutterForViewableArea As the name states, calculates the size of the gutter for a given viewable area.
151 | // It's easier to understand the geometry of the matrix on screen when put in terms of the gutter,
152 | // hence the shift toward calculating the gutter size.
153 | func (e *Emulator) calculateGutterForViewableArea(size image.Point) int {
154 | maxGutterInX := (size.X - 2*e.Margin) / (e.PixelPitchToGutterRatio*e.Width + e.Width - 1)
155 | maxGutterInY := (size.Y - 2*e.Margin) / (e.PixelPitchToGutterRatio*e.Height + e.Height - 1)
156 | if maxGutterInX < maxGutterInY {
157 | return maxGutterInX
158 | }
159 | return maxGutterInY
160 | }
161 |
162 | func (e *Emulator) updatePixelPitchForGutter(gutterWidth int) {
163 | e.PixelPitch = e.PixelPitchToGutterRatio * gutterWidth
164 | e.Gutter = gutterWidth
165 | }
166 |
167 | func (e *Emulator) Geometry() (width, height int) {
168 | return e.Width, e.Height
169 | }
170 |
171 | func (e *Emulator) Apply(leds []color.Color) error {
172 | defer func() { e.leds = make([]color.Color, e.Height*e.Width) }()
173 |
174 | var c color.Color
175 | for col := 0; col < e.Width; col++ {
176 | for row := 0; row < e.Height; row++ {
177 | c = e.At(col + (row * e.Width))
178 | e.w.Fill(e.ledRect(col, row), c, screen.Over)
179 | }
180 | }
181 |
182 | e.w.Publish()
183 | return nil
184 | }
185 |
186 | func (e *Emulator) Render() error {
187 | return e.Apply(e.leds)
188 | }
189 |
190 | func (e *Emulator) At(position int) color.Color {
191 | if e.leds[position] == nil {
192 | return color.Black
193 | }
194 |
195 | return e.leds[position]
196 | }
197 |
198 | func (e *Emulator) Set(position int, c color.Color) {
199 | e.leds[position] = color.RGBAModel.Convert(c)
200 | }
201 |
202 | func (e *Emulator) Close() error {
203 | return nil
204 | }
205 |
--------------------------------------------------------------------------------
/matrix.go:
--------------------------------------------------------------------------------
1 | package rgbmatrix
2 |
3 | /*
4 | #cgo CFLAGS: -std=c99 -I${SRCDIR}/vendor/rpi-rgb-led-matrix/include -DSHOW_REFRESH_RATE
5 | #cgo LDFLAGS: -lrgbmatrix -L${SRCDIR}/vendor/rpi-rgb-led-matrix/lib -lstdc++ -lm
6 | #include
7 |
8 | void led_matrix_swap(struct RGBLedMatrix *matrix, struct LedCanvas *offscreen_canvas,
9 | int width, int height, const uint32_t pixels[]) {
10 |
11 |
12 | int i, x, y;
13 | uint32_t color;
14 | for (x = 0; x < width; ++x) {
15 | for (y = 0; y < height; ++y) {
16 | i = x + (y * width);
17 | color = pixels[i];
18 |
19 | led_canvas_set_pixel(offscreen_canvas, x, y,
20 | (color >> 16) & 255, (color >> 8) & 255, color & 255);
21 | }
22 | }
23 |
24 | offscreen_canvas = led_matrix_swap_on_vsync(matrix, offscreen_canvas);
25 | }
26 |
27 | void set_show_refresh_rate(struct RGBLedMatrixOptions *o, int show_refresh_rate) {
28 | o->show_refresh_rate = show_refresh_rate != 0 ? 1 : 0;
29 | }
30 |
31 | void set_disable_hardware_pulsing(struct RGBLedMatrixOptions *o, int disable_hardware_pulsing) {
32 | o->disable_hardware_pulsing = disable_hardware_pulsing != 0 ? 1 : 0;
33 | }
34 |
35 | void set_inverse_colors(struct RGBLedMatrixOptions *o, int inverse_colors) {
36 | o->inverse_colors = inverse_colors != 0 ? 1 : 0;
37 | }
38 | */
39 | import "C"
40 | import (
41 | "fmt"
42 | "image/color"
43 | "os"
44 | "unsafe"
45 |
46 | "github.com/mcuadros/go-rpi-rgb-led-matrix/emulator"
47 | )
48 |
49 | // DefaultConfig default WS281x configuration
50 | var DefaultConfig = HardwareConfig{
51 | Rows: 32,
52 | Cols: 32,
53 | ChainLength: 1,
54 | Parallel: 1,
55 | PWMBits: 11,
56 | PWMLSBNanoseconds: 130,
57 | Brightness: 100,
58 | ScanMode: Progressive,
59 | }
60 |
61 | // HardwareConfig rgb-led-matrix configuration
62 | type HardwareConfig struct {
63 | // Rows the number of rows supported by the display, so 32 or 16.
64 | Rows int
65 | // Cols the number of columns supported by the display, so 32 or 64 .
66 | Cols int
67 | // ChainLengthis the number of displays daisy-chained together
68 | // (output of one connected to input of next).
69 | ChainLength int
70 | // Parallel is the number of parallel chains connected to the Pi; in old Pis
71 | // with 26 GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that
72 | // can also be 2 or 3. The effective number of pixels in vertical direction is
73 | // then thus rows * parallel.
74 | Parallel int
75 | // Set PWM bits used for output. Default is 11, but if you only deal with
76 | // limited comic-colors, 1 might be sufficient. Lower require less CPU and
77 | // increases refresh-rate.
78 | PWMBits int
79 | // Change the base time-unit for the on-time in the lowest significant bit in
80 | // nanoseconds. Higher numbers provide better quality (more accurate color,
81 | // less ghosting), but have a negative impact on the frame rate.
82 | PWMLSBNanoseconds int // the DMA channel to use
83 | // Brightness is the initial brightness of the panel in percent. Valid range
84 | // is 1..100
85 | Brightness int
86 | // ScanMode progressive or interlaced
87 | ScanMode ScanMode // strip color layout
88 | // Disable the PWM hardware subsystem to create pulses. Typically, you don't
89 | // want to disable hardware pulsing, this is mostly for debugging and figuring
90 | // out if there is interference with the sound system.
91 | // This won't do anything if output enable is not connected to GPIO 18 in
92 | // non-standard wirings.
93 | DisableHardwarePulsing bool
94 |
95 | ShowRefreshRate bool
96 | InverseColors bool
97 |
98 | // Name of GPIO mapping used
99 | HardwareMapping string
100 | }
101 |
102 | func (c *HardwareConfig) geometry() (width, height int) {
103 | return c.Cols * c.ChainLength, c.Rows * c.Parallel
104 | }
105 |
106 | func (c *HardwareConfig) toC() *C.struct_RGBLedMatrixOptions {
107 | o := &C.struct_RGBLedMatrixOptions{}
108 | o.rows = C.int(c.Rows)
109 | o.cols = C.int(c.Cols)
110 | o.chain_length = C.int(c.ChainLength)
111 | o.parallel = C.int(c.Parallel)
112 | o.pwm_bits = C.int(c.PWMBits)
113 | o.pwm_lsb_nanoseconds = C.int(c.PWMLSBNanoseconds)
114 | o.brightness = C.int(c.Brightness)
115 | o.scan_mode = C.int(c.ScanMode)
116 | o.hardware_mapping = C.CString(c.HardwareMapping)
117 |
118 | if c.ShowRefreshRate == true {
119 | C.set_show_refresh_rate(o, C.int(1))
120 | } else {
121 | C.set_show_refresh_rate(o, C.int(0))
122 | }
123 |
124 | if c.DisableHardwarePulsing == true {
125 | C.set_disable_hardware_pulsing(o, C.int(1))
126 | } else {
127 | C.set_disable_hardware_pulsing(o, C.int(0))
128 | }
129 |
130 | if c.InverseColors == true {
131 | C.set_inverse_colors(o, C.int(1))
132 | } else {
133 | C.set_inverse_colors(o, C.int(0))
134 | }
135 |
136 | return o
137 | }
138 |
139 | type ScanMode int8
140 |
141 | const (
142 | Progressive ScanMode = 0
143 | Interlaced ScanMode = 1
144 | )
145 |
146 | // RGBLedMatrix matrix representation for ws281x
147 | type RGBLedMatrix struct {
148 | Config *HardwareConfig
149 |
150 | height int
151 | width int
152 | matrix *C.struct_RGBLedMatrix
153 | buffer *C.struct_LedCanvas
154 | leds []C.uint32_t
155 | }
156 |
157 | const MatrixEmulatorENV = "MATRIX_EMULATOR"
158 |
159 | // NewRGBLedMatrix returns a new matrix using the given size and config
160 | func NewRGBLedMatrix(config *HardwareConfig) (c Matrix, err error) {
161 | defer func() {
162 | if r := recover(); r != nil {
163 | var ok bool
164 | err, ok = r.(error)
165 | if !ok {
166 | err = fmt.Errorf("error creating matrix: %v", r)
167 | }
168 | }
169 | }()
170 |
171 | if isMatrixEmulator() {
172 | return buildMatrixEmulator(config), nil
173 | }
174 |
175 | w, h := config.geometry()
176 | m := C.led_matrix_create_from_options(config.toC(), nil, nil)
177 | b := C.led_matrix_create_offscreen_canvas(m)
178 | c = &RGBLedMatrix{
179 | Config: config,
180 | width: w, height: h,
181 | matrix: m,
182 | buffer: b,
183 | leds: make([]C.uint32_t, w*h),
184 | }
185 | if m == nil {
186 | return nil, fmt.Errorf("unable to allocate memory")
187 | }
188 |
189 | return c, nil
190 | }
191 |
192 | func isMatrixEmulator() bool {
193 | if os.Getenv(MatrixEmulatorENV) == "1" {
194 | return true
195 | }
196 |
197 | return false
198 | }
199 |
200 | func buildMatrixEmulator(config *HardwareConfig) Matrix {
201 | w, h := config.geometry()
202 | return emulator.NewEmulator(w, h, emulator.DefaultPixelPitch, true)
203 | }
204 |
205 | // Initialize initialize library, must be called once before other functions are
206 | // called.
207 | func (c *RGBLedMatrix) Initialize() error {
208 | return nil
209 | }
210 |
211 | // Geometry returns the width and the height of the matrix
212 | func (c *RGBLedMatrix) Geometry() (width, height int) {
213 | return c.width, c.height
214 | }
215 |
216 | // Apply set all the pixels to the values contained in leds
217 | func (c *RGBLedMatrix) Apply(leds []color.Color) error {
218 | for position, l := range leds {
219 | c.Set(position, l)
220 | }
221 |
222 | return c.Render()
223 | }
224 |
225 | // Render update the display with the data from the LED buffer
226 | func (c *RGBLedMatrix) Render() error {
227 | w, h := c.Config.geometry()
228 |
229 | C.led_matrix_swap(
230 | c.matrix,
231 | c.buffer,
232 | C.int(w), C.int(h),
233 | (*C.uint32_t)(unsafe.Pointer(&c.leds[0])),
234 | )
235 |
236 | c.leds = make([]C.uint32_t, w*h)
237 | return nil
238 | }
239 |
240 | // At return an Color which allows access to the LED display data as
241 | // if it were a sequence of 24-bit RGB values.
242 | func (c *RGBLedMatrix) At(position int) color.Color {
243 | return uint32ToColor(c.leds[position])
244 | }
245 |
246 | // Set set LED at position x,y to the provided 24-bit color value.
247 | func (c *RGBLedMatrix) Set(position int, color color.Color) {
248 | c.leds[position] = C.uint32_t(colorToUint32(color))
249 | }
250 |
251 | // Close finalizes the ws281x interface
252 | func (c *RGBLedMatrix) Close() error {
253 | C.led_matrix_delete(c.matrix)
254 | return nil
255 | }
256 |
257 | func colorToUint32(c color.Color) uint32 {
258 | if c == nil {
259 | return 0
260 | }
261 |
262 | // A color's RGBA method returns values in the range [0, 65535]
263 | red, green, blue, _ := c.RGBA()
264 | return (red>>8)<<16 | (green>>8)<<8 | blue>>8
265 | }
266 |
267 | func uint32ToColor(u C.uint32_t) color.Color {
268 | return color.RGBA{
269 | uint8(u>>16) & 255,
270 | uint8(u>>8) & 255,
271 | uint8(u>>0) & 255,
272 | 0,
273 | }
274 | }
275 |
--------------------------------------------------------------------------------