mirror of https://github.com/periph/devices
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1111 lines
25 KiB
Go
1111 lines
25 KiB
Go
// Copyright 2016 The Periph Authors. All rights reserved.
|
|
// Use of this source code is governed under the Apache License, Version 2.0
|
|
// that can be found in the LICENSE file.
|
|
|
|
package apa102
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"io/ioutil"
|
|
"testing"
|
|
|
|
"periph.io/x/conn/v3/conntest"
|
|
"periph.io/x/conn/v3/physic"
|
|
"periph.io/x/conn/v3/spi"
|
|
"periph.io/x/conn/v3/spi/spitest"
|
|
)
|
|
|
|
func TestRamp(t *testing.T) {
|
|
// Tests a few known values.
|
|
data := []struct {
|
|
input uint8
|
|
expected uint16
|
|
}{
|
|
{0x00, 0x0000},
|
|
{0x01, 0x0001},
|
|
{0x02, 0x0002},
|
|
{0x03, 0x0003},
|
|
{0x04, 0x0004},
|
|
{0x05, 0x0005},
|
|
{0x06, 0x0006},
|
|
{0x07, 0x0007},
|
|
{0x08, 0x0008},
|
|
{0x09, 0x0009},
|
|
{0x0A, 0x000A},
|
|
{0x0B, 0x000B},
|
|
{0x0C, 0x000C},
|
|
{0x0D, 0x000D},
|
|
{0x0E, 0x000E},
|
|
{0x0F, 0x000F},
|
|
{0x10, 0x0010},
|
|
{0x11, 0x0011},
|
|
{0x12, 0x0012},
|
|
{0x13, 0x0013},
|
|
{0x14, 0x0014},
|
|
{0x15, 0x0015},
|
|
{0x16, 0x0016},
|
|
{0x17, 0x0017},
|
|
{0x18, 0x0018},
|
|
{0x19, 0x0019},
|
|
{0x1A, 0x001A},
|
|
{0x1B, 0x001B},
|
|
{0x1C, 0x001C},
|
|
{0x1D, 0x001D},
|
|
{0x1E, 0x001E},
|
|
{0x1F, 0x001F},
|
|
{0x20, 0x0020},
|
|
{0x21, 0x0021},
|
|
{0x22, 0x0022},
|
|
{0x23, 0x0023},
|
|
{0x24, 0x0024},
|
|
{0x25, 0x0025},
|
|
{0x26, 0x0026},
|
|
{0x27, 0x0027},
|
|
{0x28, 0x0028},
|
|
{0x29, 0x0029},
|
|
{0x2A, 0x002A},
|
|
{0x2B, 0x002B},
|
|
{0x2C, 0x002C},
|
|
{0x2D, 0x002D},
|
|
{0x2E, 0x002E},
|
|
{0x2F, 0x002F},
|
|
{0x30, 0x0030},
|
|
{0x31, 0x0031},
|
|
{0x32, 0x0032},
|
|
{0x33, 0x0033},
|
|
{0x34, 0x0034},
|
|
{0x35, 0x0035},
|
|
{0x36, 0x0036},
|
|
{0x37, 0x0037},
|
|
{0x38, 0x0038},
|
|
{0x39, 0x0039},
|
|
{0x3A, 0x003A},
|
|
{0x3B, 0x003B},
|
|
{0x3C, 0x003C},
|
|
{0x3D, 0x003D},
|
|
{0x3E, 0x003E},
|
|
{0x3F, 0x003F},
|
|
{0x40, 0x0040},
|
|
{0x41, 0x0041},
|
|
{0x42, 0x0042},
|
|
{0x43, 0x0043},
|
|
{0x44, 0x0044},
|
|
{0x45, 0x0045},
|
|
{0x46, 0x0046},
|
|
{0x47, 0x0047},
|
|
{0x48, 0x0048},
|
|
{0x49, 0x0049},
|
|
{0x4A, 0x004A},
|
|
{0x4B, 0x004B},
|
|
{0x4C, 0x004C},
|
|
{0x4D, 0x004D},
|
|
{0x4E, 0x004E},
|
|
{0x4F, 0x004F},
|
|
{0x50, 0x004F},
|
|
{0x51, 0x004F},
|
|
{0x52, 0x004F},
|
|
{0x53, 0x004F},
|
|
{0x54, 0x004F},
|
|
{0x55, 0x004F},
|
|
{0x56, 0x004F},
|
|
{0x57, 0x0050},
|
|
{0x58, 0x0050},
|
|
{0x59, 0x0050},
|
|
{0x5A, 0x0051},
|
|
{0x5B, 0x0051},
|
|
{0x5C, 0x0052},
|
|
{0x5D, 0x0053},
|
|
{0x5E, 0x0054},
|
|
{0x5F, 0x0055},
|
|
{0x60, 0x0056},
|
|
{0x61, 0x0057},
|
|
{0x62, 0x0059},
|
|
{0x63, 0x005A},
|
|
{0x64, 0x005C},
|
|
{0x65, 0x005E},
|
|
{0x66, 0x0060},
|
|
{0x67, 0x0063},
|
|
{0x68, 0x0065},
|
|
{0x69, 0x0068},
|
|
{0x6A, 0x006B},
|
|
{0x6B, 0x006E},
|
|
{0x6C, 0x0072},
|
|
{0x6D, 0x0075},
|
|
{0x6E, 0x0079},
|
|
{0x6F, 0x007E},
|
|
{0x70, 0x0082},
|
|
{0x71, 0x0087},
|
|
{0x72, 0x008C},
|
|
{0x73, 0x0092},
|
|
{0x74, 0x0098},
|
|
{0x75, 0x009E},
|
|
{0x76, 0x00A4},
|
|
{0x77, 0x00AB},
|
|
{0x78, 0x00B2},
|
|
{0x79, 0x00B9},
|
|
{0x7A, 0x00C1},
|
|
{0x7B, 0x00C9},
|
|
{0x7C, 0x00D2},
|
|
{0x7D, 0x00DA},
|
|
{0x7E, 0x00E4},
|
|
{0x7F, 0x00ED},
|
|
{0x80, 0x00F8},
|
|
{0x81, 0x0102},
|
|
{0x82, 0x010D},
|
|
{0x83, 0x0119},
|
|
{0x84, 0x0124},
|
|
{0x85, 0x0131},
|
|
{0x86, 0x013E},
|
|
{0x87, 0x014B},
|
|
{0x88, 0x0159},
|
|
{0x89, 0x0167},
|
|
{0x8A, 0x0176},
|
|
{0x8B, 0x0185},
|
|
{0x8C, 0x0195},
|
|
{0x8D, 0x01A5},
|
|
{0x8E, 0x01B6},
|
|
{0x8F, 0x01C7},
|
|
{0x90, 0x01D9},
|
|
{0x91, 0x01EC},
|
|
{0x92, 0x01FF},
|
|
{0x93, 0x0212},
|
|
{0x94, 0x0226},
|
|
{0x95, 0x023B},
|
|
{0x96, 0x0251},
|
|
{0x97, 0x0267},
|
|
{0x98, 0x027D},
|
|
{0x99, 0x0294},
|
|
{0x9A, 0x02AC},
|
|
{0x9B, 0x02C5},
|
|
{0x9C, 0x02DE},
|
|
{0x9D, 0x02F8},
|
|
{0x9E, 0x0312},
|
|
{0x9F, 0x032E},
|
|
{0xA0, 0x034A},
|
|
{0xA1, 0x0366},
|
|
{0xA2, 0x0384},
|
|
{0xA3, 0x03A2},
|
|
{0xA4, 0x03C0},
|
|
{0xA5, 0x03E0},
|
|
{0xA6, 0x0400},
|
|
{0xA7, 0x0421},
|
|
{0xA8, 0x0443},
|
|
{0xA9, 0x0465},
|
|
{0xAA, 0x0489},
|
|
{0xAB, 0x04AC},
|
|
{0xAC, 0x04D1},
|
|
{0xAD, 0x04F7},
|
|
{0xAE, 0x051D},
|
|
{0xAF, 0x0545},
|
|
{0xB0, 0x056D},
|
|
{0xB1, 0x0596},
|
|
{0xB2, 0x05C0},
|
|
{0xB3, 0x05EA},
|
|
{0xB4, 0x0616},
|
|
{0xB5, 0x0642},
|
|
{0xB6, 0x066F},
|
|
{0xB7, 0x069D},
|
|
{0xB8, 0x06CC},
|
|
{0xB9, 0x06FC},
|
|
{0xBA, 0x072D},
|
|
{0xBB, 0x075F},
|
|
{0xBC, 0x0792},
|
|
{0xBD, 0x07C6},
|
|
{0xBE, 0x07FA},
|
|
{0xBF, 0x0830},
|
|
{0xC0, 0x0866},
|
|
{0xC1, 0x089E},
|
|
{0xC2, 0x08D6},
|
|
{0xC3, 0x090F},
|
|
{0xC4, 0x094A},
|
|
{0xC5, 0x0985},
|
|
{0xC6, 0x09C2},
|
|
{0xC7, 0x09FF},
|
|
{0xC8, 0x0A3E},
|
|
{0xC9, 0x0A7D},
|
|
{0xCA, 0x0ABE},
|
|
{0xCB, 0x0B00},
|
|
{0xCC, 0x0B42},
|
|
{0xCD, 0x0B86},
|
|
{0xCE, 0x0BCB},
|
|
{0xCF, 0x0C11},
|
|
{0xD0, 0x0C58},
|
|
{0xD1, 0x0CA1},
|
|
{0xD2, 0x0CEA},
|
|
{0xD3, 0x0D34},
|
|
{0xD4, 0x0D80},
|
|
{0xD5, 0x0DCD},
|
|
{0xD6, 0x0E1B},
|
|
{0xD7, 0x0E6A},
|
|
{0xD8, 0x0EBA},
|
|
{0xD9, 0x0F0B},
|
|
{0xDA, 0x0F5E},
|
|
{0xDB, 0x0FB2},
|
|
{0xDC, 0x1007},
|
|
{0xDD, 0x105D},
|
|
{0xDE, 0x10B4},
|
|
{0xDF, 0x110D},
|
|
{0xE0, 0x1167},
|
|
{0xE1, 0x11C2},
|
|
{0xE2, 0x121F},
|
|
{0xE3, 0x127C},
|
|
{0xE4, 0x12DB},
|
|
{0xE5, 0x133C},
|
|
{0xE6, 0x139D},
|
|
{0xE7, 0x1400},
|
|
{0xE8, 0x1464},
|
|
{0xE9, 0x14CA},
|
|
{0xEA, 0x1530},
|
|
{0xEB, 0x1599},
|
|
{0xEC, 0x1602},
|
|
{0xED, 0x166D},
|
|
{0xEE, 0x16D9},
|
|
{0xEF, 0x1747},
|
|
{0xF0, 0x17B6},
|
|
{0xF1, 0x1826},
|
|
{0xF2, 0x1898},
|
|
{0xF3, 0x190B},
|
|
{0xF4, 0x197F},
|
|
{0xF5, 0x19F5},
|
|
{0xF6, 0x1A6D},
|
|
{0xF7, 0x1AE5},
|
|
{0xF8, 0x1B60},
|
|
{0xF9, 0x1BDB},
|
|
{0xFA, 0x1C58},
|
|
{0xFB, 0x1CD7},
|
|
{0xFC, 0x1D57},
|
|
{0xFD, 0x1DD9},
|
|
{0xFE, 0x1E5C},
|
|
{0xFF, 0x1EE1},
|
|
}
|
|
if false {
|
|
for i := 0; i <= 255; i++ {
|
|
fmt.Printf("{0x%02X, 0x%04X},\n", i, ramp(uint8(i), maxOut))
|
|
}
|
|
}
|
|
for i, line := range data {
|
|
if i != int(line.input) {
|
|
t.Fatalf("expected ordered inputs; %d != %d", i, line.input)
|
|
}
|
|
if actual := ramp(line.input, maxOut); actual != line.expected {
|
|
t.Fatalf("%d: %v != %v", i, line.expected, actual)
|
|
}
|
|
}
|
|
if v := ramp(0x00, 0xFF); v != 0x00 {
|
|
t.Fatal(v)
|
|
}
|
|
if v := ramp(0x7F, 0xFF); v != 0x21 {
|
|
t.Fatal(v)
|
|
}
|
|
if v := ramp(0xFF, 0xFF); v != 0xFF {
|
|
t.Fatal(v)
|
|
}
|
|
}
|
|
|
|
func TestRampMonotonic(t *testing.T) {
|
|
// Ensures the ramp is 100% monotonically increasing and without bumps.
|
|
lastValue := uint16(0)
|
|
lastDelta := uint16(0)
|
|
for in := uint32(0); in <= 255; in++ {
|
|
out := ramp(uint8(in), maxOut)
|
|
if out < lastValue {
|
|
t.Fatalf("f(%d) = %d; f(%d) = %d", in-1, lastValue, in, out)
|
|
}
|
|
if out > maxOut {
|
|
t.Fatalf("f(%d) = %d", in, out)
|
|
}
|
|
|
|
if out-lastValue+1 < lastDelta {
|
|
t.Errorf("f(%d)=%d f(%d)=%d f(%d)=%d Deltas: '%d+1 < %d' but should be '>='",
|
|
in-2, ramp(uint8(in-2), maxOut), in-1, ramp(uint8(in-1), maxOut), in, ramp(uint8(in), maxOut), out-lastValue, lastDelta)
|
|
}
|
|
lastDelta = out - lastValue
|
|
lastValue = out
|
|
}
|
|
}
|
|
|
|
func TestDevEmpty(t *testing.T) {
|
|
buf := bytes.Buffer{}
|
|
o := DefaultOpts
|
|
o.NumPixels = 0
|
|
d, _ := New(spitest.NewRecordRaw(&buf), &o, spi.Mode3)
|
|
if n, err := d.Write([]byte{}); n != 0 || err != nil {
|
|
t.Fatalf("%d %v", n, err)
|
|
}
|
|
if expected := []byte{0x0, 0x0, 0x0, 0x0, 0xFF}; !bytes.Equal(expected, buf.Bytes()) {
|
|
t.Fatalf("\ngot: %#02v\nwant: %#02v\n", buf.Bytes(), expected)
|
|
}
|
|
if s := d.String(); s != "APA102{I:255, T:5000K, GPWM:true, 0LEDs, recordraw}" {
|
|
t.Fatal(s)
|
|
}
|
|
}
|
|
|
|
func TestConnectFail(t *testing.T) {
|
|
if d, err := New(&configFail{}, &DefaultOpts, spi.Mode3); d != nil || err == nil {
|
|
t.Fatal("Connect() call have failed")
|
|
}
|
|
}
|
|
|
|
func TestDevLen(t *testing.T) {
|
|
buf := bytes.Buffer{}
|
|
o := DefaultOpts
|
|
o.NumPixels = 1
|
|
d, _ := New(spitest.NewRecordRaw(&buf), &o, spi.Mode3)
|
|
if n, err := d.Write([]byte{0}); n != 0 || err == nil {
|
|
t.Fatalf("%d %v", n, err)
|
|
}
|
|
if expected := []byte{}; !bytes.Equal(expected, buf.Bytes()) {
|
|
t.Fatalf("\ngot: %#02v\nwant: %#02v\n", buf.Bytes(), expected)
|
|
}
|
|
}
|
|
|
|
var writeTests = []struct {
|
|
name string
|
|
pixels []byte
|
|
want []byte
|
|
opts Opts
|
|
}{
|
|
{
|
|
name: "Temperature",
|
|
pixels: ToRGB([]color.NRGBA{
|
|
{0xFF, 0xFF, 0xFF, 0x00},
|
|
{0xFE, 0xFE, 0xFE, 0x00},
|
|
{0xF0, 0xF0, 0xF0, 0x00},
|
|
{0x80, 0x80, 0x80, 0x00},
|
|
{0x80, 0x00, 0x00, 0x00},
|
|
{0x00, 0x80, 0x00, 0x00},
|
|
{0x00, 0x00, 0x80, 0x00},
|
|
{0x00, 0x00, 0x10, 0x00},
|
|
{0x00, 0x00, 0x01, 0x00},
|
|
{0x00, 0x00, 0x00, 0x00},
|
|
}),
|
|
want: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFB, 0xFB, 0xFB,
|
|
0xFF, 0xC4, 0xC4, 0xC4,
|
|
0xE1, 0xF8, 0xF8, 0xF8,
|
|
0xE1, 0x00, 0x00, 0xF8,
|
|
0xE1, 0x00, 0xF8, 0x00,
|
|
0xE1, 0xF8, 0x00, 0x00,
|
|
0xE1, 0x10, 0x00, 0x00,
|
|
0xE1, 0x01, 0x00, 0x00,
|
|
0xE1, 0x00, 0x00, 0x00,
|
|
0xFF,
|
|
},
|
|
opts: Opts{
|
|
Intensity: 255,
|
|
Temperature: NeutralTemp,
|
|
},
|
|
},
|
|
{
|
|
name: "Intensity",
|
|
pixels: ToRGB([]color.NRGBA{
|
|
{0xFF, 0xFF, 0xFF, 0x00},
|
|
{0xFE, 0xFE, 0xFE, 0x00},
|
|
{0xF0, 0xF0, 0xF0, 0x00},
|
|
{0x80, 0x80, 0x80, 0x00},
|
|
{0x80, 0x00, 0x00, 0x00},
|
|
{0x00, 0x80, 0x00, 0x00},
|
|
{0x00, 0x00, 0x80, 0x00},
|
|
{0x00, 0x00, 0x10, 0x00},
|
|
{0x00, 0x00, 0x01, 0x00},
|
|
{0x00, 0x00, 0x00, 0x00},
|
|
}),
|
|
want: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0x7F, 0x7F, 0x7F,
|
|
0xFF, 0x7D, 0x7D, 0x7D,
|
|
0xFF, 0x67, 0x67, 0x67,
|
|
0xE2, 0x9B, 0x9B, 0x9B,
|
|
0xE2, 0x00, 0x00, 0x9B,
|
|
0xE2, 0x00, 0x9B, 0x00,
|
|
0xE2, 0x9B, 0x00, 0x00,
|
|
0xE1, 0x10, 0x00, 0x00,
|
|
0xE1, 0x01, 0x00, 0x00,
|
|
0xE1, 0x00, 0x00, 0x00,
|
|
0xFF,
|
|
},
|
|
opts: Opts{
|
|
Intensity: 127,
|
|
Temperature: NeutralTemp,
|
|
},
|
|
},
|
|
{
|
|
name: "PassThru",
|
|
pixels: ToRGB([]color.NRGBA{
|
|
{0xFF, 0xFF, 0xFF, 0x00},
|
|
{0xFE, 0xFE, 0xFE, 0x00},
|
|
{0xF0, 0xF0, 0xF0, 0x00},
|
|
{0x80, 0x80, 0x80, 0x00},
|
|
{0x80, 0x00, 0x00, 0x00},
|
|
{0x00, 0x80, 0x00, 0x00},
|
|
{0x00, 0x00, 0x80, 0x00},
|
|
{0x00, 0x00, 0x10, 0x00},
|
|
{0x00, 0x00, 0x01, 0x00},
|
|
{0x00, 0x00, 0x00, 0x00},
|
|
}),
|
|
want: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFE, 0xFE, 0xFE,
|
|
0xFF, 0xF0, 0xF0, 0xF0,
|
|
0xFF, 0x80, 0x80, 0x80,
|
|
0xFF, 0x00, 0x00, 0x80,
|
|
0xFF, 0x00, 0x80, 0x00,
|
|
0xFF, 0x80, 0x00, 0x00,
|
|
0xFF, 0x10, 0x00, 0x00,
|
|
0xFF, 0x01, 0x00, 0x00,
|
|
0xFF, 0x00, 0x00, 0x00,
|
|
0xFF,
|
|
},
|
|
opts: PassThruOpts,
|
|
},
|
|
{
|
|
name: "DisableGlobalPWM - Intensity",
|
|
pixels: ToRGB([]color.NRGBA{
|
|
{0xFF, 0xFF, 0xFF, 0x00},
|
|
{0xFE, 0xFE, 0xFE, 0x00},
|
|
{0xF0, 0xF0, 0xF0, 0x00},
|
|
{0x80, 0x80, 0x80, 0x00},
|
|
{0x80, 0x00, 0x00, 0x00},
|
|
{0x00, 0x80, 0x00, 0x00},
|
|
{0x00, 0x00, 0x80, 0x00},
|
|
{0x00, 0x00, 0x10, 0x00},
|
|
{0x00, 0x00, 0x01, 0x00},
|
|
{0x00, 0x00, 0x00, 0x00},
|
|
}),
|
|
want: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0xff, 0x40, 0x40, 0x40,
|
|
0xff, 0x40, 0x40, 0x40,
|
|
0xff, 0x3c, 0x3c, 0x3c,
|
|
0xff, 0x20, 0x20, 0x20,
|
|
0xff, 0x00, 0x00, 0x20,
|
|
0xff, 0x00, 0x20, 0x00,
|
|
0xff, 0x20, 0x00, 0x00,
|
|
0xff, 0x04, 0x00, 0x00,
|
|
0xff, 0x00, 0x00, 0x00,
|
|
0xff, 0x00, 0x00, 0x00,
|
|
0xff,
|
|
},
|
|
opts: Opts{
|
|
Intensity: 64,
|
|
Temperature: NeutralTemp,
|
|
DisableGlobalPWM: true,
|
|
},
|
|
},
|
|
{
|
|
name: "DisableGlobalPWM - Temp",
|
|
pixels: ToRGB([]color.NRGBA{
|
|
{0xFF, 0xFF, 0xFF, 0x00},
|
|
{0xFE, 0xFE, 0xFE, 0x00},
|
|
{0xF0, 0xF0, 0xF0, 0x00},
|
|
{0x80, 0x80, 0x80, 0x00},
|
|
{0x80, 0x00, 0x00, 0x00},
|
|
{0x00, 0x80, 0x00, 0x00},
|
|
{0x00, 0x00, 0x80, 0x00},
|
|
{0x00, 0x00, 0x10, 0x00},
|
|
{0x00, 0x00, 0x01, 0x00},
|
|
{0x00, 0x00, 0x00, 0x00},
|
|
}),
|
|
want: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0xff, 0xd5, 0xe8, 0xff,
|
|
0xff, 0xd4, 0xe7, 0xfe,
|
|
0xff, 0xc8, 0xda, 0xf0,
|
|
0xff, 0x6b, 0x74, 0x80,
|
|
0xff, 0x00, 0x00, 0x80,
|
|
0xff, 0x00, 0x74, 0x00,
|
|
0xff, 0x6b, 0x00, 0x00,
|
|
0xff, 0x0d, 0x00, 0x00,
|
|
0xff, 0x01, 0x00, 0x00,
|
|
0xff, 0x00, 0x00, 0x00,
|
|
0xff,
|
|
},
|
|
opts: Opts{
|
|
Intensity: 255,
|
|
Temperature: 5000,
|
|
DisableGlobalPWM: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Intensity and temperature",
|
|
pixels: func() []byte {
|
|
var p []byte
|
|
for i := 0; i < 16*3; i++ {
|
|
p = append(p, uint8(i<<2))
|
|
}
|
|
return p
|
|
}(),
|
|
want: expectedi250t5000,
|
|
opts: Opts{
|
|
Intensity: 250,
|
|
Temperature: 5000,
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestWrites(t *testing.T) {
|
|
for _, tt := range writeTests {
|
|
buf := bytes.Buffer{}
|
|
tt.opts.NumPixels = len(tt.pixels) / 3
|
|
d, _ := New(spitest.NewRecordRaw(&buf), &tt.opts, spi.Mode3)
|
|
n, err := d.Write(tt.pixels)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != len(tt.pixels) {
|
|
t.Fatalf("%s: Got %d bytes result, want %d", tt.name, n, len(tt.pixels)*3)
|
|
}
|
|
if !bytes.Equal(buf.Bytes(), tt.want) {
|
|
t.Fatalf("%s:\ngot: %#02v\nwant: %#02v\n", tt.name, buf.Bytes(), tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDevColor(t *testing.T) {
|
|
if c := (&Dev{}).ColorModel(); c != color.NRGBAModel {
|
|
t.Fatal(c)
|
|
}
|
|
}
|
|
|
|
func TestDevLong(t *testing.T) {
|
|
buf := bytes.Buffer{}
|
|
colors := make([]color.NRGBA, 256)
|
|
o := DefaultOpts
|
|
o.NumPixels = len(colors)
|
|
d, _ := New(spitest.NewRecordRaw(&buf), &o, spi.Mode3)
|
|
if n, err := d.Write(ToRGB(colors)); n != len(colors)*3 || err != nil {
|
|
t.Fatalf("%d %v", n, err)
|
|
}
|
|
expected := make([]byte, 4*(256+1)+17)
|
|
for i := 0; i < 256; i++ {
|
|
expected[4+4*i] = 0xE1
|
|
}
|
|
trailer := expected[4*257:]
|
|
for i := range trailer {
|
|
trailer[i] = 0xFF
|
|
}
|
|
if !bytes.Equal(expected, buf.Bytes()) {
|
|
t.Fatalf("\ngot: %#02v\nwant: %#02v\n", buf.Bytes(), expected)
|
|
}
|
|
}
|
|
|
|
func TestDevWrite_Long(t *testing.T) {
|
|
buf := bytes.Buffer{}
|
|
o := DefaultOpts
|
|
o.NumPixels = 1
|
|
d, _ := New(spitest.NewRecordRaw(&buf), &o, spi.Mode3)
|
|
if n, err := d.Write([]byte{0, 0, 0, 1, 1, 1}); n != 0 || err == nil {
|
|
t.Fatal(n, err)
|
|
}
|
|
}
|
|
|
|
// expectedi250t5000 is the expected output for multiple test cases. Each test case
|
|
// use a completely different code path so make sure each code path results in
|
|
// the exact same output.
|
|
var expectedi250t5000 = []byte{
|
|
0x00, 0x00, 0x00, 0x00, 0xE1, 0x08, 0x04, 0x00, 0xE1, 0x14, 0x10, 0xC, 0xE1,
|
|
0x20, 0x1C, 0x18, 0xE1, 0x2C, 0x28, 0x24, 0xE1, 0x38, 0x34, 0x30, 0xE1, 0x41,
|
|
0x40, 0x3C, 0xE1, 0x44, 0x47, 0x48, 0xE1, 0x53, 0x4C, 0x4E, 0xE1, 0x78, 0x62,
|
|
0x56, 0xE1, 0xBD, 0x94, 0x73, 0xE2, 0x95, 0x77, 0x5A, 0xE2, 0xE5, 0xBD, 0x94,
|
|
0xE4, 0xAA, 0x92, 0x77, 0xE4, 0xF3, 0xD7, 0xB8, 0xFF, 0x2B, 0x28, 0x23, 0xFF,
|
|
0x3A, 0x36, 0x32, 0xFF, 0xFF,
|
|
}
|
|
|
|
// expectedi250t6500 is the default color temperature.
|
|
var expectedi250t6500 = []byte{
|
|
0x00, 0x00, 0x00, 0x00, 0xE1, 0x08, 0x04, 0x00, 0xE1, 0x14, 0x10, 0x0C, 0xE1,
|
|
0x20, 0x1C, 0x18, 0xE1, 0x2C, 0x28, 0x24, 0xE1, 0x38, 0x34, 0x30, 0xE1, 0x44,
|
|
0x40, 0x3C, 0xE1, 0x4E, 0x4C, 0x48, 0xE1, 0x52, 0x4F, 0x4E, 0xE1, 0x66, 0x5C,
|
|
0x56, 0xE1, 0x9A, 0x84, 0x73, 0xE1, 0xFB, 0xD4, 0xB4, 0xE2, 0xCB, 0xAE, 0x94,
|
|
0xE4, 0xA0, 0x8A, 0x77, 0xE4, 0xF0, 0xD2, 0xB8, 0xFF, 0x2D, 0x28, 0x23, 0xFF,
|
|
0x3E, 0x38, 0x32, 0xFF, 0xFF,
|
|
}
|
|
|
|
// expectedi250raw is using DisableGlobalPWM = true.
|
|
var expectedi250raw = []byte{
|
|
0x00, 0x00, 0x00, 0x00, 0xFF, 0x08, 0x04, 0x00, 0xFF, 0x14, 0x10, 0x0C, 0xFF,
|
|
0x1F, 0x1B, 0x18, 0xFF, 0x2B, 0x27, 0x23, 0xFF, 0x37, 0x33, 0x2F, 0xFF, 0x43,
|
|
0x3F, 0x3B, 0xFF, 0x4E, 0x4B, 0x47, 0xFF, 0x5A, 0x56, 0x52, 0xFF, 0x66, 0x62,
|
|
0x5E, 0xFF, 0x72, 0x6E, 0x6A, 0xFF, 0x7D, 0x7A, 0x76, 0xFF, 0x89, 0x85, 0x81,
|
|
0xFF, 0x95, 0x91, 0x8D, 0xFF, 0xA1, 0x9D, 0x99, 0xFF, 0xAD, 0xA9, 0xA5, 0xFF,
|
|
0xB8, 0xB4, 0xB0, 0xFF, 0xFF,
|
|
}
|
|
|
|
var drawTests = []struct {
|
|
name string
|
|
img image.Image
|
|
want []byte
|
|
opts Opts
|
|
}{
|
|
{
|
|
name: "Draw NRGBA",
|
|
img: func() image.Image {
|
|
im := image.NewNRGBA(image.Rect(0, 0, 16, 1))
|
|
for i := 0; i < 16; i++ {
|
|
// Test all intensity code paths. Confirm that alpha is ignored.
|
|
im.Pix[4*i] = uint8((3 * i) << 2)
|
|
im.Pix[4*i+1] = uint8((3*i + 1) << 2)
|
|
im.Pix[4*i+2] = uint8((3*i + 2) << 2)
|
|
im.Pix[4*i+3] = 0
|
|
}
|
|
return im
|
|
}(),
|
|
want: expectedi250t5000,
|
|
opts: Opts{
|
|
NumPixels: 16,
|
|
Intensity: 250,
|
|
Temperature: 5000,
|
|
},
|
|
},
|
|
{
|
|
name: "Draw NRGBA Wide",
|
|
img: func() image.Image {
|
|
im := image.NewNRGBA(image.Rect(0, 0, 17, 2))
|
|
for x := 0; x < 16; x++ {
|
|
// Test all intensity code paths. Confirm that alpha is ignored.
|
|
im.SetNRGBA(x, 0, color.NRGBA{uint8((3 * x) << 2), uint8((3*x + 1) << 2), uint8((3*x + 2) << 2), 0})
|
|
}
|
|
return im
|
|
}(),
|
|
want: expectedi250t6500,
|
|
opts: Opts{
|
|
NumPixels: 16,
|
|
Intensity: 250,
|
|
Temperature: NeutralTemp,
|
|
},
|
|
},
|
|
{
|
|
name: "Draw NRGBA no global PWM",
|
|
img: func() image.Image {
|
|
im := image.NewNRGBA(image.Rect(0, 0, 16, 1))
|
|
for i := 0; i < 16; i++ {
|
|
// Test all intensity code paths. Confirm that alpha is ignored.
|
|
im.Pix[4*i] = uint8((3 * i) << 2)
|
|
im.Pix[4*i+1] = uint8((3*i + 1) << 2)
|
|
im.Pix[4*i+2] = uint8((3*i + 2) << 2)
|
|
im.Pix[4*i+3] = 0
|
|
}
|
|
return im
|
|
}(),
|
|
want: expectedi250raw,
|
|
opts: Opts{
|
|
NumPixels: 16,
|
|
Temperature: NeutralTemp,
|
|
Intensity: 250,
|
|
DisableGlobalPWM: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Draw RGBA",
|
|
img: func() image.Image {
|
|
im := image.NewRGBA(image.Rect(0, 0, 16, 1))
|
|
for i := 0; i < 16; i++ {
|
|
im.Pix[4*i] = uint8((3 * i) << 2)
|
|
im.Pix[4*i+1] = uint8((3*i + 1) << 2)
|
|
im.Pix[4*i+2] = uint8((3*i + 2) << 2)
|
|
im.Pix[4*i+3] = 0xFF
|
|
}
|
|
return im
|
|
}(),
|
|
want: expectedi250t5000,
|
|
opts: Opts{
|
|
NumPixels: 16,
|
|
Intensity: 250,
|
|
Temperature: 5000,
|
|
},
|
|
},
|
|
{
|
|
// Just something that doesn't have a fast path
|
|
name: "Draw NRGBA64",
|
|
img: func() image.Image {
|
|
im := image.NewNRGBA64(image.Rect(0, 0, 16, 1))
|
|
for i := 0; i < 16; i++ {
|
|
im.Set(i, 0, color.NRGBA64{
|
|
R: uint16(((3 * i) << 10)),
|
|
G: uint16(((3*i + 1) << 10)),
|
|
B: uint16(((3*i + 2) << 10)),
|
|
A: 0xFFFF,
|
|
})
|
|
}
|
|
return im
|
|
}(),
|
|
want: expectedi250t5000,
|
|
opts: Opts{
|
|
NumPixels: 16,
|
|
Intensity: 250,
|
|
Temperature: 5000,
|
|
},
|
|
},
|
|
{
|
|
name: "Draw RGBA no global PWM",
|
|
img: func() image.Image {
|
|
im := image.NewRGBA(image.Rect(0, 0, 16, 1))
|
|
for i := 0; i < 16; i++ {
|
|
im.Pix[4*i] = uint8((3 * i) << 2)
|
|
im.Pix[4*i+1] = uint8((3*i + 1) << 2)
|
|
im.Pix[4*i+2] = uint8((3*i + 2) << 2)
|
|
im.Pix[4*i+3] = 0xFF
|
|
}
|
|
return im
|
|
}(),
|
|
want: expectedi250raw,
|
|
opts: Opts{
|
|
NumPixels: 16,
|
|
Temperature: NeutralTemp,
|
|
Intensity: 250,
|
|
DisableGlobalPWM: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestDraws(t *testing.T) {
|
|
for _, tt := range drawTests {
|
|
buf := bytes.Buffer{}
|
|
d, _ := New(spitest.NewRecordRaw(&buf), &tt.opts, spi.Mode3)
|
|
if err := d.Draw(d.Bounds(), tt.img, image.Point{}); err != nil {
|
|
t.Fatalf("%s: %v", tt.name, err)
|
|
}
|
|
if !bytes.Equal(buf.Bytes(), tt.want) {
|
|
t.Fatalf("%s:\ngot: %#02v\nwant: %#02v\n", tt.name, buf.Bytes(), tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
var offsetDrawWant = []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0xE1, 0x89, 0x79, 0x6B,
|
|
0xE1, 0x9A, 0x88, 0x75,
|
|
0xE1, 0xAD, 0x98, 0x82,
|
|
0xE1, 0xC2, 0xAB, 0x92,
|
|
0xE1, 0xDA, 0xC0, 0xA4,
|
|
0xE1, 0xF5, 0xD9, 0xB9,
|
|
0xE2, 0x89, 0x7A, 0x69,
|
|
0xE2, 0x9A, 0x8A, 0x76,
|
|
0xE2, 0xAC, 0x9B, 0x86,
|
|
0xE2, 0xC0, 0xAE, 0x98,
|
|
0xE2, 0xD5, 0xC3, 0xAC,
|
|
0xE2, 0xED, 0xDA, 0xC2,
|
|
0xE4, 0x83, 0x7A, 0x6E,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0xFF,
|
|
}
|
|
|
|
var offsetDrawTests = []struct {
|
|
name string
|
|
img image.Image
|
|
point image.Point
|
|
offset image.Rectangle
|
|
want []byte
|
|
opts Opts
|
|
}{
|
|
{
|
|
name: "Offset Draw NRGBA",
|
|
img: func() image.Image {
|
|
im := image.NewNRGBA(image.Rect(0, 0, 16, 4))
|
|
for x := 0; x < 16; x++ {
|
|
for y := 0; y < 4; y++ {
|
|
i := (y*16 + x) * 3
|
|
im.Set(x, y, color.RGBA{R: uint8(i + 1), G: uint8(i + 2), B: uint8(i + 3), A: 0xFF})
|
|
}
|
|
}
|
|
return im
|
|
}(),
|
|
point: image.Point{X: 3, Y: 2},
|
|
offset: image.Rect(0, 0, 16, 1),
|
|
want: offsetDrawWant,
|
|
opts: Opts{
|
|
NumPixels: 15,
|
|
Intensity: 255,
|
|
Temperature: 5000,
|
|
},
|
|
},
|
|
{
|
|
name: "Both Offset Draw NRGBA",
|
|
img: func() image.Image {
|
|
im := image.NewNRGBA(image.Rect(0, 0, 16, 4))
|
|
for x := 0; x < 16; x++ {
|
|
for y := 0; y < 4; y++ {
|
|
i := (y*16 + x) * 3
|
|
im.Set(x, y, color.RGBA{R: uint8(i + 1), G: uint8(i + 2), B: uint8(i + 3), A: 0xFF})
|
|
}
|
|
}
|
|
return im
|
|
}(),
|
|
point: image.Point{X: 3, Y: 2},
|
|
offset: image.Rect(2, 0, 16, 1),
|
|
want: []byte{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0xE1, 0x89, 0x79, 0x6B,
|
|
0xE1, 0x9A, 0x88, 0x75,
|
|
0xE1, 0xAD, 0x98, 0x82,
|
|
0xE1, 0xC2, 0xAB, 0x92,
|
|
0xE1, 0xDA, 0xC0, 0xA4,
|
|
0xE1, 0xF5, 0xD9, 0xB9,
|
|
0xE2, 0x89, 0x7A, 0x69,
|
|
0xE2, 0x9A, 0x8A, 0x76,
|
|
0xE2, 0xAC, 0x9B, 0x86,
|
|
0xE2, 0xC0, 0xAE, 0x98,
|
|
0xE2, 0xD5, 0xC3, 0xAC,
|
|
0xE2, 0xED, 0xDA, 0xC2,
|
|
0xE4, 0x83, 0x7A, 0x6E,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0xFF,
|
|
},
|
|
opts: Opts{
|
|
NumPixels: 17,
|
|
Intensity: 255,
|
|
Temperature: 5000,
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestOffsetDraws(t *testing.T) {
|
|
for _, tt := range offsetDrawTests {
|
|
buf := bytes.Buffer{}
|
|
d, _ := New(spitest.NewRecordRaw(&buf), &tt.opts, spi.Mode3)
|
|
if err := d.Draw(tt.offset, tt.img, tt.point); err != nil {
|
|
t.Fatalf("%s: %v", tt.name, err)
|
|
}
|
|
if !bytes.Equal(buf.Bytes(), tt.want) {
|
|
t.Fatalf("%s:\ngot: %#02v\nwant: %#02v\n", tt.name, buf.Bytes(), tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHalt(t *testing.T) {
|
|
s := spitest.Playback{
|
|
Playback: conntest.Playback{
|
|
Ops: []conntest.IO{
|
|
{W: []byte{0x0, 0x0, 0x0, 0x0, 0xe1, 0x0, 0x0, 0x0, 0xe1, 0x0, 0x0, 0x0, 0xe1, 0x0, 0x0, 0x0, 0xe1, 0x0, 0x0, 0x0, 0xff}},
|
|
},
|
|
},
|
|
}
|
|
o := DefaultOpts
|
|
o.NumPixels = 4
|
|
o.Temperature = 5000
|
|
d, _ := New(&s, &o, spi.Mode3)
|
|
if err := d.Halt(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestInit(t *testing.T) {
|
|
// Catch the "maxB == maxG" line.
|
|
l := lut{}
|
|
l.init(255, 6000, true)
|
|
if equalUint16(l.r[:], l.g[:]) || !equalUint16(l.g[:], l.b[:]) {
|
|
t.Fatal("test case is for only when maxG == maxB but maxR != maxG")
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
type genColor func(int) [3]byte
|
|
|
|
func benchmarkWrite(b *testing.B, o Opts, length int, f genColor) {
|
|
var pixels []byte
|
|
for i := 0; i < length; i++ {
|
|
c := f(i)
|
|
pixels = append(pixels, c[:]...)
|
|
}
|
|
o.NumPixels = length
|
|
b.ReportAllocs()
|
|
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), &o, spi.Mode3)
|
|
_, _ = d.Write(pixels[:])
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = d.Write(pixels[:])
|
|
}
|
|
}
|
|
|
|
func BenchmarkWriteWhite(b *testing.B) {
|
|
o := DefaultOpts
|
|
o.Intensity = 250
|
|
benchmarkWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0xFF, 0xFF, 0xFF} })
|
|
}
|
|
|
|
func BenchmarkWriteDim(b *testing.B) {
|
|
o := DefaultOpts
|
|
o.Intensity = 250
|
|
benchmarkWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x01, 0x01, 0x01} })
|
|
}
|
|
|
|
func BenchmarkWriteBlack(b *testing.B) {
|
|
o := DefaultOpts
|
|
o.Intensity = 250
|
|
benchmarkWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x0, 0x0, 0x0} })
|
|
}
|
|
|
|
func genColorfulPixel(x int) [3]byte {
|
|
i := x * 3
|
|
return [3]byte{uint8(i) + uint8(i>>8),
|
|
uint8(i+1) + uint8(i+1>>8),
|
|
uint8(i+2) + uint8(i+2>>8),
|
|
}
|
|
}
|
|
|
|
func BenchmarkWriteColorful(b *testing.B) {
|
|
o := DefaultOpts
|
|
o.Intensity = 250
|
|
o.Temperature = 5000
|
|
benchmarkWrite(b, o, 150, genColorfulPixel)
|
|
}
|
|
|
|
func BenchmarkWriteColorfulPassThru(b *testing.B) {
|
|
o := PassThruOpts
|
|
o.Intensity = 250
|
|
benchmarkWrite(b, o, 150, genColorfulPixel)
|
|
}
|
|
|
|
func BenchmarkWriteColorfulVariation(b *testing.B) {
|
|
// Continuously vary the lookup tables.
|
|
b.ReportAllocs()
|
|
pixels := [256 * 3]byte{}
|
|
for i := range pixels {
|
|
pixels[i] = uint8(i) + uint8(i>>8)
|
|
}
|
|
o := DefaultOpts
|
|
o.NumPixels = len(pixels) / 3
|
|
o.Intensity = 250
|
|
o.Temperature = 5000
|
|
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), &o, spi.Mode3)
|
|
_, _ = d.Write(pixels[:])
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
d.Intensity = uint8(i)
|
|
d.Temperature = uint16((3000 + i) & 0x1FFF)
|
|
_, _ = d.Write(pixels[:])
|
|
}
|
|
}
|
|
|
|
func benchmarkDraw(b *testing.B, o Opts, img draw.Image, f genColor) {
|
|
for x := 0; x < img.Bounds().Dx(); x++ {
|
|
for y := 0; y < img.Bounds().Dy(); y++ {
|
|
pix := f(x)
|
|
c := color.NRGBA{R: pix[0], G: pix[1], B: pix[2], A: 255}
|
|
img.Set(x, y, c)
|
|
}
|
|
}
|
|
o.NumPixels = img.Bounds().Max.X
|
|
b.ReportAllocs()
|
|
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), &o, spi.Mode3)
|
|
r := d.Bounds()
|
|
p := image.Point{}
|
|
if err := d.Draw(r, img, p); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if err := d.Draw(r, img, p); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkDrawNRGBAColorful(b *testing.B) {
|
|
o := DefaultOpts
|
|
o.Intensity = 250
|
|
o.Temperature = 5000
|
|
benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel)
|
|
}
|
|
|
|
func BenchmarkDrawNRGBAColorfulPassThru(b *testing.B) {
|
|
o := PassThruOpts
|
|
o.Intensity = 250
|
|
benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel)
|
|
}
|
|
|
|
func BenchmarkDrawNRGBAWhite(b *testing.B) {
|
|
o := DefaultOpts
|
|
o.Intensity = 250
|
|
o.Temperature = 5000
|
|
benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), func(i int) [3]byte { return [3]byte{0xFF, 0xFF, 0xFF} })
|
|
}
|
|
|
|
func BenchmarkDrawRGBAColorful(b *testing.B) {
|
|
o := DefaultOpts
|
|
o.Intensity = 250
|
|
o.Temperature = 5000
|
|
benchmarkDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel)
|
|
}
|
|
|
|
func BenchmarkDrawRGBAColorfulPassThru(b *testing.B) {
|
|
o := PassThruOpts
|
|
o.Intensity = 250
|
|
benchmarkDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel)
|
|
}
|
|
|
|
func BenchmarkDrawSlowpath(b *testing.B) {
|
|
// Should be an image type that doesn't have a fast path
|
|
img := image.NewGray(image.Rect(0, 0, 150, 1))
|
|
for x := 0; x < img.Bounds().Dx(); x++ {
|
|
for y := 0; y < img.Bounds().Dy(); y++ {
|
|
pix := genColorfulPixel(x)
|
|
img.Set(x, y, color.Gray{pix[0]})
|
|
}
|
|
}
|
|
o := DefaultOpts
|
|
o.NumPixels = img.Bounds().Max.X
|
|
b.ReportAllocs()
|
|
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), &o, spi.Mode3)
|
|
r := d.Bounds()
|
|
p := image.Point{}
|
|
if err := d.Draw(r, img, p); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if err := d.Draw(r, img, p); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkHalt(b *testing.B) {
|
|
b.ReportAllocs()
|
|
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), &DefaultOpts, spi.Mode3)
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if err := d.Halt(); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
type configFail struct {
|
|
spitest.Record
|
|
}
|
|
|
|
func (c *configFail) Connect(f physic.Frequency, mode spi.Mode, bits int) (spi.Conn, error) {
|
|
return nil, errors.New("injected error")
|
|
}
|
|
|
|
func equalUint16(a, b []uint16) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|