aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--unicorn/FakeUnicorn.go98
-rw-r--r--unicorn/RealUnicorn.go66
-rw-r--r--unicorn/Unicorn.go73
-rw-r--r--unicorn/Unicorn_test.go76
4 files changed, 313 insertions, 0 deletions
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)
+}