├── .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 [![GoDoc](https://godoc.org/github.com/mcuadros/go-rpi-rgb-led-matrix?status.svg)](https://godoc.org/github.com/mcuadros/go-rpi-rgb-led-matrix) [![Build Status](https://travis-ci.org/mcuadros/go-rpi-rgb-led-matrix.svg?branch=master)](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 | --------------------------------------------------------------------------------