From 32e997346bb5766aefa6ef5f4caa189af128b498 Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Mon, 21 May 2018 09:54:00 +0100 Subject: Moved unicorn lib to it's own package --- unicorn/FakeUnicorn.go | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ unicorn/RealUnicorn.go | 66 +++++++++++++++++++++++++++++++++ unicorn/Unicorn.go | 73 ++++++++++++++++++++++++++++++++++++ unicorn/Unicorn_test.go | 76 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 313 insertions(+) create mode 100644 unicorn/FakeUnicorn.go create mode 100644 unicorn/RealUnicorn.go create mode 100644 unicorn/Unicorn.go create mode 100644 unicorn/Unicorn_test.go diff --git a/unicorn/FakeUnicorn.go b/unicorn/FakeUnicorn.go new file mode 100644 index 0000000..3236842 --- /dev/null +++ b/unicorn/FakeUnicorn.go @@ -0,0 +1,98 @@ +// +build linux,386 linux,amd64 + +package unicorn + +import ( + "github.com/veandco/go-sdl2/sdl" +) + +type FakeUnicorn struct { + BaseUnicorn + displayWidth int32 + displayHeight int32 + window *sdl.Window + renderer *sdl.Renderer +} + +// NewUnicorn ... +// Constructs a new fake unicorn out of paint and glue +func NewUnicorn() (*FakeUnicorn, error) { + width := uint8(16) + height := uint8(16) + if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil { + return nil, err + } + + unicorn := &FakeUnicorn{ + BaseUnicorn{ + pixels: makePixels(width, height), + }, + 300, + 300, + nil, + nil, + } + if err := unicorn.createWindow(); err != nil { + unicorn.Close() + return nil, err + } + if err := unicorn.createRenderer(); err != nil { + unicorn.Close() + return nil, err + } + return unicorn, nil +} + +func (f *FakeUnicorn) createWindow() error { + window, err := sdl.CreateWindow("Fake Unicorn", + sdl.WINDOWPOS_UNDEFINED, + sdl.WINDOWPOS_UNDEFINED, + f.displayWidth, + f.displayHeight, + sdl.WINDOW_SHOWN) + f.window = window + return err +} + +func (f *FakeUnicorn) createRenderer() error { + renderer, err := sdl.CreateRenderer(f.window, -1, sdl.RENDERER_ACCELERATED) + f.renderer = renderer + return err +} + +func (f *FakeUnicorn) Close() error { + if f.window != nil { + f.window.Destroy() + } + if f.renderer != nil { + f.renderer.Destroy() + } + return nil +} + +func (f *FakeUnicorn) Show() { + width, height := f.GetWidth(), f.GetHeight() + for x := uint8(0); x < width; x++ { + for y := uint8(0); y < height; y++ { + r, g, b := rgb(f.pixels[x][y]) + if err := f.renderer.SetDrawColor(r, g, b, uint8(255)); err != nil { + panic(err) + } + cellWidth := f.displayWidth / int32(width) + cellHeight := f.displayHeight / int32(height) + if err := f.renderer.FillRect(&sdl.Rect{ + X: cellWidth * int32(x), + Y: f.displayHeight - (cellHeight * int32(y)) - cellHeight, // SDL Y coordinate is from the top + W: cellWidth, + H: cellHeight, + }); err != nil { + panic(err) + } + } + } + f.renderer.Present() +} + +func (f *FakeUnicorn) Off() { + f.Close() +} diff --git a/unicorn/RealUnicorn.go b/unicorn/RealUnicorn.go new file mode 100644 index 0000000..4e5c376 --- /dev/null +++ b/unicorn/RealUnicorn.go @@ -0,0 +1,66 @@ +// +build linux,arm linux,arm64 + +package unicorn + +import ( + //"golang.org/x/exp/io/spi" + "log" + + "github.com/ecc1/spi" +) + +type RealUnicorn struct { + BaseUnicorn + device *spi.Device +} + +// NewUnicorn ... +// Constructs a new real unicorn from fairy dust and sprinkles +func NewUnicorn() (*RealUnicorn, error) { + dev, err := spi.Open("/dev/spidev0.0", 9000000, 0) + if err != nil { + return nil, err + } + + return &RealUnicorn{ + BaseUnicorn{ + pixels: makePixels(16, 16), + }, + dev, + }, nil +} + +func (u *RealUnicorn) Show() { + // Width * height * colours + leading bit + width := int(u.GetWidth()) + height := int(u.GetHeight()) + sz := (width * height * 3) + 1 + write := make([]byte, sz) + + // Add the leading bit + write[0] = 0x72 + // Add all the pixel values + ix := 1 + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + for j := 0; j < 3; j++ { + write[ix] = byte(u.pixels[x][y][j]) + ix++ + } + } + } + // Write to the device + //err := u.device.Tx(write, nil) + err := u.device.Transfer(write) + if err != nil { + log.Printf("Error writing to SPI device %v", err) + } +} + +func (u *RealUnicorn) Off() { + u.Close() +} + +func (u *RealUnicorn) Close() error { + return u.device.Close() +} diff --git a/unicorn/Unicorn.go b/unicorn/Unicorn.go new file mode 100644 index 0000000..09f712e --- /dev/null +++ b/unicorn/Unicorn.go @@ -0,0 +1,73 @@ +package unicorn + +// Unicorn ... +// Object representing the Unicorn HAT to be controlled +type Unicorn interface { + // Not all unicorns are the same size + GetWidth() uint8 + GetHeight() uint8 + + // Array of pixels, indexed x, then y, then color (rgb) + GetPixels() [][][]uint8 + + // Set an individual pixel + SetPixel(x, y, r, g, b uint8) + + // Flip the display buffer + Show() + + // Set all pixels back to black + Clear() + + // Turns off the LEDs + Off() +} + +// GetUnicorn ... +// Get a unicorn. Get's a real on on ARM hardware, +// get's a fake one on x86 +func GetUnicorn() (Unicorn, error) { + return NewUnicorn() +} + +type BaseUnicorn struct { + pixels [][][]uint8 +} + +func (f *BaseUnicorn) GetWidth() uint8 { + return uint8(len(f.pixels)) +} + +func (f *BaseUnicorn) GetHeight() uint8 { + if len(f.pixels) > 0 { + return uint8(len(f.pixels[0])) + } + return 0 +} + +func (f *BaseUnicorn) GetPixels() [][][]uint8 { + return f.pixels +} + +func (f *BaseUnicorn) SetPixel(x, y, r, g, b uint8) { + f.pixels[x][y] = []uint8{r, g, b} +} + +func (f *BaseUnicorn) Clear() { + f.pixels = makePixels(f.GetWidth(), f.GetHeight()) +} + +func makePixels(width, height uint8) [][][]uint8 { + pixels := make([][][]uint8, width) + for x := uint8(0); x < width; x++ { + pixels[x] = make([][]uint8, height) + for y := uint8(0); y < height; y++ { + pixels[x][y] = []uint8{0, 0, 0} + } + } + return pixels +} + +func rgb(pixel []uint8) (uint8, uint8, uint8) { + return pixel[0], pixel[1], pixel[2] +} diff --git a/unicorn/Unicorn_test.go b/unicorn/Unicorn_test.go new file mode 100644 index 0000000..358dc7b --- /dev/null +++ b/unicorn/Unicorn_test.go @@ -0,0 +1,76 @@ +package unicorn + +import ( + "reflect" + "testing" + "time" +) + +func TestFakeUnicorn(t *testing.T) { + unicorn, err := NewUnicorn() + if err != nil { + t.Errorf("Got an error making a fake unicorn, shouldn't happen") + } + defer unicorn.Close() + + // Check simple functions + if unicorn.GetHeight() != 16 { + t.Errorf("Height was wrong, expecting 16") + } + if unicorn.GetWidth() != 16 { + t.Errorf("Width was wrong, expecting 16") + } + // Pixels should be black to start with + pixels := unicorn.GetPixels() + for x := uint8(0); x < 16; x++ { + for y := uint8(0); y < 16; y++ { + if !reflect.DeepEqual(pixels[x][y], []uint8{0, 0, 0}) { + t.Errorf("Expecting black pixels to start with") + } + } + } + + // Should be able to set a pixel, no others should change + unicorn.SetPixel(0, 0, uint8(255), uint8(255), uint8(255)) + pixels = unicorn.GetPixels() + if !reflect.DeepEqual(pixels[0][0], []uint8{255, 255, 255}) { + t.Errorf("Pixel wasn't set when it should be") + } + for x := uint8(0); x < 16; x++ { + for y := uint8(0); y < 16; y++ { + if x == 0 && y == 0 { + continue + } + if !reflect.DeepEqual(pixels[x][y], []uint8{0, 0, 0}) { + t.Errorf("Expecting black pixels to start with") + } + } + } + + // Should be able to set a second pixel + unicorn.SetPixel(3, 4, uint8(4), uint8(5), uint8(6)) + pixels = unicorn.GetPixels() + for x := uint8(0); x < 16; x++ { + for y := uint8(0); y < 16; y++ { + checkcolor := []uint8{0, 0, 0} + if x == 0 && y == 0 { + checkcolor = []uint8{255, 255, 255} + } else if x == 3 && y == 4 { + checkcolor = []uint8{4, 5, 6} + } + if !reflect.DeepEqual(pixels[x][y], checkcolor) { + t.Errorf("Got incorrect pixel color at %d %d", x, y) + } + } + } + + unicorn.Show() + time.Sleep(time.Duration(500) * time.Millisecond) + unicorn.SetPixel(10, 10, uint8(255), uint8(255), uint8(0)) + unicorn.Show() + time.Sleep(time.Duration(500) * time.Millisecond) + + unicorn.SetPixel(0, 15, uint8(255), uint8(0), uint8(0)) + unicorn.Show() + time.Sleep(time.Duration(500) * time.Millisecond) +} -- cgit v1.2.3-ZIG