nrzled: merge SPIDev into Dev

- Remove Strip, now unnecessary.
- Rename New() to NewStream().
- Merge SPIDev into Dev.
- Rename all unit tests.
- Unexport ToRGB(), this was in a unit test.
- Increase test coverage to 93.8%.
pull/1/head
Marc-Antoine Ruel 8 years ago
parent b930b20a2d
commit 34b5f2bfcb

@ -1,192 +0,0 @@
// 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 nrzled
import (
"errors"
"fmt"
"image"
"image/color"
"image/draw"
"periph.io/x/periph/conn/display"
"periph.io/x/periph/conn/physic"
"periph.io/x/periph/conn/spi"
)
// SPIDev represents a strip of WS2812b LEDs as a strip connected over a SPI port.
// It accepts a stream of raw RGB pixels and converts it to a bit pattern consistent
// with the WS812b protocol.
// Includes intensity and temperature correction.
type SPIDev struct {
s spi.Conn //
numPixels int //
rawBuf []byte // Raw buffer sent over SPI. Cached to reduce heap fragmentation.
pixels []byte // Double buffer of pixels, to enable partial painting via Draw(). Effectively points inside rawBuf.
rect image.Rectangle // Device bounds
pixelMap map[image.Point]int
}
// NewSPI returns a strip that communicates over SPI to NRZ encoded LEDs.
//
// Due to the tight timing demands of these LEDs,
// the SPI port speed must be a reliable 2.5MHz
//
// Note that your SPI buffer should be at least 12*num_pixels+2 bytes long
func NewSPI(p spi.Port, o *Opts) (*SPIDev, error) {
c, err := p.Connect(2500*physic.KiloHertz, spi.Mode3|spi.NoCS, 8)
if err != nil {
return nil, err
}
rawBCt := 4 * (3 * o.NumPixels) //3 bytes per pixel, 4 symbol bytes per byte
buf := make([]byte, rawBCt+3) //3 bytes for latch. 24*400ns = 9600ns.
tail := buf[rawBCt:]
for i := range tail {
tail[i] = 0x00
}
return &SPIDev{
s: c,
numPixels: o.NumPixels,
rawBuf: buf,
pixels: buf[:rawBCt],
rect: image.Rect(0, 0, o.NumPixels, 1),
}, nil
}
func (d *SPIDev) String() string {
return fmt.Sprintf("nrzled: {%d, %s}", d.numPixels, d.s)
}
// ColorModel implements display.Drawer. There's no surprise, it is
// color.NRGBAModel.
func (d *SPIDev) ColorModel() color.Model {
return color.NRGBAModel
}
// Bounds implements display.Drawer. Min is guaranteed to be {0, 0}.
func (d *SPIDev) Bounds() image.Rectangle {
return d.rect
}
// Draw implements display.Drawer.
//
// Using something else than image.NRGBA is 10x slower. When using image.NRGBA,
// the alpha channel is ignored.
func (d *SPIDev) Draw(r image.Rectangle, src image.Image, sp image.Point) error {
if r = r.Intersect(d.rect); r.Empty() {
return nil
}
srcR := src.Bounds()
srcR.Min = srcR.Min.Add(sp)
if dX := r.Dx(); dX < srcR.Dx() {
srcR.Max.X = srcR.Min.X + dX
}
if dY := r.Dy(); dY < srcR.Dy() {
srcR.Max.Y = srcR.Min.Y + dY
}
if srcR.Empty() {
return nil
}
d.rasterImg(d.pixels, r, src, srcR)
return d.s.Tx(d.rawBuf, nil)
}
// Write accepts a stream of raw RGB pixels and sends it as WS2812b encoded
// stream.
func (d *SPIDev) Write(pixels []byte) (int, error) {
if len(pixels)%3 != 0 || len(pixels)/3 > d.numPixels {
return 0, errors.New("ws2812b: invalid RGB stream length")
}
// Do not touch footer.
d.raster(d.pixels, pixels, false)
err := d.s.Tx(d.rawBuf, nil)
return len(pixels), err
}
// Halt turns off all the lights.
func (d *SPIDev) Halt() error {
// Zap out the buffer.
for i := range d.pixels {
d.pixels[i] = 0x88
}
return d.s.Tx(d.rawBuf, nil)
}
// raster serializes a buffer of RGB bytes to the WS2812b SPI format.
//
// It is expected to be given the part where pixels are, not the header nor
// footer.
//
// dst is in WS2812b SPI 32 bits word format. src is in RGB 24 bits, or 32 bits
// word format when srcHasAlpha is true. The src alpha channel is ignored in
// this case.
//
// src cannot be longer in pixel count than dst.
func (d *SPIDev) raster(dst []byte, src []byte, srcHasAlpha bool) {
pBytes := 3
if srcHasAlpha {
pBytes = 4
}
length := len(src) / pBytes
if l := len(dst) / 4; l < length {
length = l
}
if length == 0 {
// Save ourself some unneeded processing.
return
}
stride := 4 //number of spi-bytes in color-byte
for i := 0; i < length; i++ {
sOff := pBytes * i
dOff := 3 * stride * i //3 channels * stride
r, g, b := src[sOff], src[sOff+1], src[sOff+2]
//grb color order, msb first
copy(dst[dOff+stride*0:dOff+stride*1], nrzMSB4[r][:])
copy(dst[dOff+stride*1:dOff+stride*2], nrzMSB4[g][:])
copy(dst[dOff+stride*2:dOff+stride*3], nrzMSB4[b][:])
}
}
// rasterImg is the generic version of raster that converts an image instead of raw RGB values.
//
// It has 'fast paths' for image.RGBA and image.NRGBA that extract and convert the RGB values
// directly. For other image types, it converts to image.RGBA and then does the same. In all
// cases, alpha values are ignored.
//
// rect specifies where into the output buffer to draw.
//
// srcR specifies what portion of the source image to use.
func (d *SPIDev) rasterImg(dst []byte, rect image.Rectangle, src image.Image, srcR image.Rectangle) {
// Render directly into the buffer for maximum performance and to keep
// untouched sections intact.
switch im := src.(type) {
case *image.RGBA:
start := im.PixOffset(srcR.Min.X, srcR.Min.Y)
// srcR.Min.Y since the output display has only a single column
end := im.PixOffset(srcR.Max.X, srcR.Min.Y)
// Offset into the output buffer using rect
d.raster(dst[4*rect.Min.X:], im.Pix[start:end], true)
case *image.NRGBA:
// Ignores alpha
start := im.PixOffset(srcR.Min.X, srcR.Min.Y)
// srcR.Min.Y since the output display has only a single column
end := im.PixOffset(srcR.Max.X, srcR.Min.Y)
// Offset into the output buffer using rect
d.raster(dst[4*rect.Min.X:], im.Pix[start:end], true)
default:
// Slow path. Convert to RGBA
b := im.Bounds()
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
draw.Draw(m, m.Bounds(), src, b.Min, draw.Src)
start := m.PixOffset(srcR.Min.X, srcR.Min.Y)
// srcR.Min.Y since the output display has only a single column
end := m.PixOffset(srcR.Max.X, srcR.Min.Y)
// Offset into the output buffer using rect
d.raster(dst[4*rect.Min.X:], m.Pix[start:end], true)
}
}
var _ display.Drawer = &Dev{}

@ -9,18 +9,15 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"image/draw"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/display" "periph.io/x/periph/conn/display"
"periph.io/x/periph/conn/gpio/gpiostream" "periph.io/x/periph/conn/gpio/gpiostream"
"periph.io/x/periph/conn/physic" "periph.io/x/periph/conn/physic"
"periph.io/x/periph/conn/spi"
) )
// Strip is deprecated and will soon be removed.
type Strip interface {
display.Drawer
Write(pixels []byte) (int, error)
}
// DefaultOpts is the recommended default options. // DefaultOpts is the recommended default options.
var DefaultOpts = Opts{ var DefaultOpts = Opts{
NumPixels: 150, // 150 LEDs is a common strip length. NumPixels: 150, // 150 LEDs is a common strip length.
@ -43,7 +40,7 @@ type Opts struct {
} }
// New opens a handle to a compatible LED strip. // New opens a handle to a compatible LED strip.
func New(p gpiostream.PinOut, opts *Opts) (*Dev, error) { func NewStream(p gpiostream.PinOut, opts *Opts) (*Dev, error) {
// Allow a wider range in case there's new devices with higher supported // Allow a wider range in case there's new devices with higher supported
// frequency. // frequency.
if opts.Freq < 10*physic.KiloHertz || opts.Freq > 100*physic.MegaHertz { if opts.Freq < 10*physic.KiloHertz || opts.Freq > 100*physic.MegaHertz {
@ -52,43 +49,96 @@ func New(p gpiostream.PinOut, opts *Opts) (*Dev, error) {
if opts.Channels != 3 && opts.Channels != 4 { if opts.Channels != 3 && opts.Channels != 4 {
return nil, errors.New("nrzled: specify valid number of channels (3 or 4)") return nil, errors.New("nrzled: specify valid number of channels (3 or 4)")
} }
// 3 symbol bytes per byte, 3/4 bytes per pixel.
streamLen := 3 * (opts.Channels * opts.NumPixels)
// 3 bytes for latch. TODO: duration.
bufSize := streamLen + 3
buf := make([]byte, bufSize)
return &Dev{ return &Dev{
name: "nrzled{" + p.Name() + "}",
p: p, p: p,
numPixels: opts.NumPixels, numPixels: opts.NumPixels,
channels: opts.Channels, channels: opts.Channels,
b: gpiostream.BitStream{ b: gpiostream.BitStream{Freq: opts.Freq, Bits: buf, LSBF: false},
Freq: opts.Freq, rawBuf: buf[:streamLen],
// Each bit is encoded on 3 bits. rect: image.Rect(0, 0, opts.NumPixels, 1),
Bits: make([]byte, opts.NumPixels*3*opts.Channels), }, nil
LSBF: false, }
},
// NewSPI returns a strip that communicates over SPI to NRZ encoded LEDs.
//
// Due to the tight timing demands of these LEDs, the SPI port speed must be a
// reliable 2.4~2.5MHz; this is 3x 800kHz.
//
// The driver's SPI buffer must be at least 12*num_pixels+3 bytes long.
func NewSPI(p spi.Port, opts *Opts) (*Dev, error) {
const spiFreq = 2500 * physic.KiloHertz
if opts.Freq != spiFreq {
return nil, errors.New("nrzled: expected Freq " + spiFreq.String())
}
if opts.Channels != 3 && opts.Channels != 4 {
return nil, errors.New("nrzled: specify valid number of channels (3 or 4)")
}
// 4 symbol bytes per byte, 3/4 bytes per pixel.
streamLen := 4 * (opts.Channels * opts.NumPixels)
// 3 bytes for latch. 24*400ns = 9600ns. In practice this could be skipped,
// as the overhead for SPI Tx() tear down and the next one is likely at least
// 10µs.
bufSize := streamLen + 3
if l, ok := p.(conn.Limits); ok {
if s := l.MaxTxSize(); s < bufSize {
return nil, errors.New("spi port buffer is too short for the specified number of pixels")
}
}
c, err := p.Connect(spiFreq, spi.Mode3|spi.NoCS, 8)
if err != nil {
return nil, err
}
buf := make([]byte, bufSize)
return &Dev{
name: "nrzled{" + c.String() + "}",
s: c,
numPixels: opts.NumPixels,
channels: opts.Channels,
b: gpiostream.BitStream{Freq: opts.Freq, Bits: buf, LSBF: false},
rawBuf: buf[:streamLen],
rect: image.Rect(0, 0, opts.NumPixels, 1), rect: image.Rect(0, 0, opts.NumPixels, 1),
}, nil }, nil
} }
// Dev is a handle to the LED strip. // Dev is a handle to the LED strip.
type Dev struct { type Dev struct {
// Immutable.
name string
s spi.Conn
p gpiostream.PinOut p gpiostream.PinOut
numPixels int numPixels int
channels int // Number of channels per pixel channels int // Number of channels per pixel
b gpiostream.BitStream // NRZ encoded bits; cached to reduce heap fragmentation
buf []byte // Double buffer of RGB/RGBW pixels; enables partial Draw()
rect image.Rectangle // Device bounds rect image.Rectangle // Device bounds
// Mutable.
b gpiostream.BitStream // NRZ encoded bits; cached to reduce heap fragmentation
rawBuf []byte // NRZ encoded bits; excluding the padding
pixels []byte // Double buffer of RGB/RGBW pixels; enables partial Draw()
} }
func (d *Dev) String() string { func (d *Dev) String() string {
return fmt.Sprintf("nrzled{%s}", d.p) return d.name
} }
// Halt turns the lights off. // Halt turns the lights off.
// //
// It doesn't affect the back buffer. // It doesn't affect the back buffer.
func (d *Dev) Halt() error { func (d *Dev) Halt() error {
zero := nrzMSB3[0] if d.s == nil {
for i := 0; i < d.channels*d.numPixels; i++ { // zero := nrzMSB3[0]
d.b.Bits[3*i+0] = zero[0] const a = 0x92
d.b.Bits[3*i+1] = zero[1] const b = 0x49
d.b.Bits[3*i+2] = zero[2] const c = 0x24
for i := 0; i < len(d.rawBuf); i += 3 {
d.rawBuf[i+0] = a
d.rawBuf[i+1] = b
d.rawBuf[i+2] = c
} }
if err := d.p.StreamOut(&d.b); err != nil { if err := d.p.StreamOut(&d.b); err != nil {
return fmt.Errorf("nrzled: %v", err) return fmt.Errorf("nrzled: %v", err)
@ -96,6 +146,16 @@ func (d *Dev) Halt() error {
return nil return nil
} }
// Zap out the buffer. 0x88 is '0'.
for i := range d.rawBuf {
d.rawBuf[i] = 0x88
}
if err := d.s.Tx(d.b.Bits, nil); err != nil {
return fmt.Errorf("nrzled: %v", err)
}
return nil
}
// ColorModel implements display.Drawer. // ColorModel implements display.Drawer.
// //
// It is color.NRGBAModel. // It is color.NRGBAModel.
@ -128,15 +188,22 @@ func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) error {
if dY := r.Dy(); dY < srcR.Dy() { if dY := r.Dy(); dY < srcR.Dy() {
srcR.Max.Y = srcR.Min.Y + dY srcR.Max.Y = srcR.Min.Y + dY
} }
if d.buf == nil { if srcR.Empty() {
// Allocate d.buf on first Draw() call, in case the user only wants to use return nil
// .Write(). }
d.buf = make([]byte, d.numPixels*d.channels) if d.s != nil {
d.rasterSPIImg(d.rawBuf, r, src, srcR)
return d.s.Tx(d.b.Bits, nil)
}
if d.pixels == nil {
// Allocate d.pixels on first Draw() call, in case the user only wants to
// use .Write().
d.pixels = make([]byte, d.numPixels*d.channels)
} }
if img, ok := src.(*image.NRGBA); ok { if img, ok := src.(*image.NRGBA); ok {
// Fast path for image.NRGBA. // Fast path for image.NRGBA.
base := srcR.Min.Y * img.Stride base := srcR.Min.Y * img.Stride
raster(d.b.Bits, img.Pix[base+4*srcR.Min.X:base+4*srcR.Max.X], d.channels, 4) rasterBits(d.b.Bits, img.Pix[base+4*srcR.Min.X:base+4*srcR.Max.X], d.channels, 4)
} else { } else {
// Generic version. // Generic version.
m := srcR.Max.X - srcR.Min.X m := srcR.Max.X - srcR.Min.X
@ -170,23 +237,27 @@ func (d *Dev) Write(pixels []byte) (int, error) {
if len(pixels)%d.channels != 0 || len(pixels) > d.numPixels*d.channels { if len(pixels)%d.channels != 0 || len(pixels) > d.numPixels*d.channels {
return 0, errors.New("nrzled: invalid RGB stream length") return 0, errors.New("nrzled: invalid RGB stream length")
} }
raster(d.b.Bits, pixels, d.channels, d.channels) if d.s == nil {
rasterBits(d.b.Bits, pixels, d.channels, d.channels)
if err := d.p.StreamOut(&d.b); err != nil { if err := d.p.StreamOut(&d.b); err != nil {
return 0, fmt.Errorf("nrzled: %v", err) return 0, fmt.Errorf("nrzled: %v", err)
} }
return len(pixels), nil return len(pixels), nil
} }
d.rasterSPI(d.rawBuf, pixels, false)
return len(pixels), d.s.Tx(d.b.Bits, nil)
}
// // Bits
// raster converts a RGB/RGBW input stream into a MSB binary output stream as it // rasterBits converts a RGB/RGBW input stream into a MSB binary output stream
// must be sent over the GPIO pin. // as it must be sent over the GPIO pin.
// //
// `in` is RGB 24 bits or RGBW 32 bits. Each bit is encoded over 3 bits so the // `in` is RGB 24 bits or RGBW 32 bits. Each bit is encoded over 3 bits so the
// length of `out` must be 3x as large as `in`. // length of `out` must be 3x as large as `in`.
// //
// Encoded output format is GRB as 72 bits (24 * 3) or 96 bits (32 * 3). // Encoded output format is GRB as 72 bits (24 * 3) or 96 bits (32 * 3).
func raster(out, in []byte, outChannels, inChannels int) { func rasterBits(out, in []byte, outChannels, inChannels int) {
pixels := len(in) / inChannels pixels := len(in) / inChannels
if outChannels == 3 { if outChannels == 3 {
for i := 0; i < pixels; i++ { for i := 0; i < pixels; i++ {
@ -214,4 +285,74 @@ func putNRZMSB3(out []byte, v byte) {
copy(out, nrzMSB3[v][:]) copy(out, nrzMSB3[v][:])
} }
// SPI
// rasterSPI serializes a buffer of RGB bytes to the WS2812b SPI format.
//
// It is expected to be given the part where pixels are, not the header nor
// footer.
//
// dst is in WS2812b SPI 32 bits word format. src is in RGB 24 bits, or 32 bits
// word format when srcHasAlpha is true. The src alpha channel is ignored in
// this case.
//
// src cannot be longer in pixel count than dst.
func (d *Dev) rasterSPI(dst []byte, src []byte, srcHasAlpha bool) {
pBytes := 3
if srcHasAlpha {
pBytes = 4
}
length := len(src) / pBytes
stride := 4 //number of spi-bytes in color-byte
for i := 0; i < length; i++ {
sOff := pBytes * i
dOff := 3 * stride * i //3 channels * stride
r, g, b := src[sOff], src[sOff+1], src[sOff+2]
//grb color order, msb first
copy(dst[dOff+stride*0:dOff+stride*1], nrzMSB4[r][:])
copy(dst[dOff+stride*1:dOff+stride*2], nrzMSB4[g][:])
copy(dst[dOff+stride*2:dOff+stride*3], nrzMSB4[b][:])
}
}
// rasterSPIImg is the generic version of raster that converts an image instead
// of raw RGB values.
//
// It has 'fast paths' for image.RGBA and image.NRGBA that extract and convert
// the RGB values directly. For other image types, it converts to image.RGBA
// and then does the same. In all cases, alpha values are ignored.
//
// rect specifies where into the output buffer to draw.
//
// srcR specifies what portion of the source image to use.
func (d *Dev) rasterSPIImg(dst []byte, rect image.Rectangle, src image.Image, srcR image.Rectangle) {
// Render directly into the buffer for maximum performance and to keep
// untouched sections intact.
switch im := src.(type) {
case *image.RGBA:
start := im.PixOffset(srcR.Min.X, srcR.Min.Y)
// srcR.Min.Y since the output display has only a single column
end := im.PixOffset(srcR.Max.X, srcR.Min.Y)
// Offset into the output buffer using rect
d.rasterSPI(dst[4*rect.Min.X:], im.Pix[start:end], true)
case *image.NRGBA:
// Ignores alpha
start := im.PixOffset(srcR.Min.X, srcR.Min.Y)
// srcR.Min.Y since the output display has only a single column
end := im.PixOffset(srcR.Max.X, srcR.Min.Y)
// Offset into the output buffer using rect
d.rasterSPI(dst[4*rect.Min.X:], im.Pix[start:end], true)
default:
// Slow path. Convert to RGBA
b := im.Bounds()
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
draw.Draw(m, m.Bounds(), src, b.Min, draw.Src)
start := m.PixOffset(srcR.Min.X, srcR.Min.Y)
// srcR.Min.Y since the output display has only a single column
end := m.PixOffset(srcR.Max.X, srcR.Min.Y)
// Offset into the output buffer using rect
d.rasterSPI(dst[4*rect.Min.X:], m.Pix[start:end], true)
}
}
var _ display.Drawer = &Dev{} var _ display.Drawer = &Dev{}

@ -19,10 +19,10 @@ import (
"periph.io/x/periph/conn/spi/spitest" "periph.io/x/periph/conn/spi/spitest"
) )
// ToRGB converts a slice of color.NRGBA to a byte stream of RGB pixels. // toRGB converts a slice of color.NRGBA to a byte stream of RGB pixels.
// //
// Ignores alpha. // Ignores alpha.
func ToRGB(p []color.NRGBA) []byte { func toRGB(p []color.NRGBA) []byte {
b := make([]byte, 0, len(p)*3) b := make([]byte, 0, len(p)*3)
for _, c := range p { for _, c := range p {
b = append(b, c.R, c.G, c.B) b = append(b, c.R, c.G, c.B)
@ -30,33 +30,61 @@ func ToRGB(p []color.NRGBA) []byte {
return b return b
} }
func TestDevEmpty(t *testing.T) { func TestSPI_Empty(t *testing.T) {
buf := bytes.Buffer{} buf := bytes.Buffer{}
o := Opts{NumPixels: 150} o := Opts{NumPixels: 0, Channels: 3, Freq: 2500 * physic.KiloHertz}
o.NumPixels = 0 s := spitest.Playback{
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &o) Playback: conntest.Playback{
Count: 1,
Ops: []conntest.IO{{W: []byte{0x00, 0x00, 0x00}}},
},
}
d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
if err != nil {
t.Fatal(err)
}
if got, expected := d.String(), "nrzled{recordraw}"; got != expected {
t.Fatalf("\nGot: %s\nWant: %s\n", got, expected)
}
if n, err := d.Write([]byte{}); n != 0 || err != nil { if n, err := d.Write([]byte{}); n != 0 || err != nil {
t.Fatalf("%d %v", n, err) t.Fatalf("%d %v", n, err)
} }
if expected := []byte{0x0, 0x0, 0x0}; !bytes.Equal(expected, buf.Bytes()) { if err := s.Close(); err != nil {
t.Fatalf("\nGot: %#02v\nWant: %#02v\n", buf.Bytes(), expected) t.Fatal(err)
} }
if got, expected := d.String(), "nrzled: {0, recordraw}"; got != expected {
t.Fatalf("\nGot: %s\nWant: %s\n", got, expected)
} }
func TestSPI_fail(t *testing.T) {
buf := bytes.Buffer{}
o := Opts{NumPixels: 1, Channels: 3, Freq: 1 * physic.KiloHertz}
if _, err := NewSPI(spitest.NewRecordRaw(&buf), &o); err == nil {
t.Fatal("invalid Freq")
} }
func TestConnectFail(t *testing.T) { o = Opts{NumPixels: 1, Channels: 0, Freq: 2500 * physic.KiloHertz}
if d, err := NewSPI(&configFail{}, &Opts{NumPixels: 150}); d != nil || err == nil { if _, err := NewSPI(spitest.NewRecordRaw(&buf), &o); err == nil {
t.Fatal("invalid Channels")
}
o = Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
if d, err := NewSPI(&configFail{}, &o); d != nil || err == nil {
t.Fatal("Connect() call have failed") t.Fatal("Connect() call have failed")
} }
o = Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
if d, err := NewSPI(&limitLow{}, &o); d != nil || err == nil {
t.Fatal("MaxTxSize() is too small")
}
} }
func TestDevLen(t *testing.T) { func TestSPI_Len(t *testing.T) {
buf := bytes.Buffer{} buf := bytes.Buffer{}
o := Opts{NumPixels: 150} o := Opts{NumPixels: 1, Channels: 3, Freq: 2500 * physic.KiloHertz}
o.NumPixels = 1 d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &o) if err != nil {
t.Fatal(err)
}
if n, err := d.Write([]byte{0}); n != 0 || err == nil { if n, err := d.Write([]byte{0}); n != 0 || err == nil {
t.Fatalf("%d %v", n, err) t.Fatalf("%d %v", n, err)
} }
@ -73,7 +101,7 @@ var writeTests = []struct {
}{ }{
{ {
name: "1 pixel to #FFFFFF", name: "1 pixel to #FFFFFF",
pixels: ToRGB([]color.NRGBA{ pixels: toRGB([]color.NRGBA{
{0xFF, 0xFF, 0xFF, 0x00}, {0xFF, 0xFF, 0xFF, 0x00},
}), }),
want: []byte{ want: []byte{
@ -82,11 +110,13 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 1, NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
name: "1 pixel to #FEFEFE", name: "1 pixel to #FEFEFE",
pixels: ToRGB([]color.NRGBA{ pixels: toRGB([]color.NRGBA{
{0xFE, 0xFE, 0xFE, 0x00}, {0xFE, 0xFE, 0xFE, 0x00},
}), }),
want: []byte{ want: []byte{
@ -95,11 +125,13 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 1, NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
name: "1 pixel to #F0F0F0", name: "1 pixel to #F0F0F0",
pixels: ToRGB([]color.NRGBA{ pixels: toRGB([]color.NRGBA{
{0xF0, 0xF0, 0xF0, 0x00}, {0xF0, 0xF0, 0xF0, 0x00},
}), }),
want: []byte{ want: []byte{
@ -108,11 +140,13 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 1, NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
name: "1 pixel to #808080", name: "1 pixel to #808080",
pixels: ToRGB([]color.NRGBA{ pixels: toRGB([]color.NRGBA{
{0x80, 0x80, 0x80, 0x00}, {0x80, 0x80, 0x80, 0x00},
}), }),
want: []byte{ want: []byte{
@ -121,11 +155,13 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 1, NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
name: "1 pixel to #80FF00", name: "1 pixel to #80FF00",
pixels: ToRGB([]color.NRGBA{ pixels: toRGB([]color.NRGBA{
{0x80, 0xFF, 0x00, 0x00}, {0x80, 0xFF, 0x00, 0x00},
}), }),
want: []byte{ want: []byte{
@ -134,11 +170,13 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 1, NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
name: "1 pixel to #800000", name: "1 pixel to #800000",
pixels: ToRGB([]color.NRGBA{ pixels: toRGB([]color.NRGBA{
{0x80, 0x00, 0x00, 0x00}, {0x80, 0x00, 0x00, 0x00},
}), }),
want: []byte{ want: []byte{
@ -147,11 +185,13 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 1, NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
name: "1 pixel to #008000", name: "1 pixel to #008000",
pixels: ToRGB([]color.NRGBA{ pixels: toRGB([]color.NRGBA{
{0x00, 0x80, 0x00, 0x00}, {0x00, 0x80, 0x00, 0x00},
}), }),
want: []byte{ want: []byte{
@ -160,11 +200,13 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 1, NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
name: "1 pixel to #000080", name: "1 pixel to #000080",
pixels: ToRGB([]color.NRGBA{ pixels: toRGB([]color.NRGBA{
{0x00, 0x00, 0x80, 0x00}, {0x00, 0x00, 0x80, 0x00},
}), }),
want: []byte{ want: []byte{
@ -173,11 +215,13 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 1, NumPixels: 1,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
name: "All at once", name: "All at once",
pixels: ToRGB([]color.NRGBA{ pixels: toRGB([]color.NRGBA{
{0xFF, 0xFF, 0xFF, 0x00}, {0xFF, 0xFF, 0xFF, 0x00},
{0xFE, 0xFE, 0xFE, 0x00}, {0xFE, 0xFE, 0xFE, 0x00},
{0xF0, 0xF0, 0xF0, 0x00}, {0xF0, 0xF0, 0xF0, 0x00},
@ -208,15 +252,20 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 10, NumPixels: 10,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
} }
func TestWrites(t *testing.T) { func TestSPI_Writes(t *testing.T) {
for _, tt := range writeTests { for _, tt := range writeTests {
buf := bytes.Buffer{} buf := bytes.Buffer{}
tt.opts.NumPixels = len(tt.pixels) / 3 tt.opts.NumPixels = len(tt.pixels) / 3
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &tt.opts) d, err := NewSPI(spitest.NewRecordRaw(&buf), &tt.opts)
if err != nil {
t.Fatal(err)
}
n, err := d.Write(tt.pixels) n, err := d.Write(tt.pixels)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -236,19 +285,21 @@ func TestWrites(t *testing.T) {
} }
} }
func TestDevColor(t *testing.T) { func TestSPI_Color(t *testing.T) {
if c := (&Dev{}).ColorModel(); c != color.NRGBAModel { if c := (&Dev{}).ColorModel(); c != color.NRGBAModel {
t.Fatal(c) t.Fatal(c)
} }
} }
func TestDevLong(t *testing.T) { func TestSPI_Long(t *testing.T) {
buf := bytes.Buffer{} buf := bytes.Buffer{}
colors := make([]color.NRGBA, 256) colors := make([]color.NRGBA, 256)
o := Opts{NumPixels: 150} o := Opts{NumPixels: len(colors), Channels: 3, Freq: 2500 * physic.KiloHertz}
o.NumPixels = len(colors) d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &o) if err != nil {
if n, err := d.Write(ToRGB(colors)); n != len(colors)*3 || err != nil { t.Fatal(err)
}
if n, err := d.Write(toRGB(colors)); n != len(colors)*3 || err != nil {
t.Fatalf("%d %v", n, err) t.Fatalf("%d %v", n, err)
} }
expected := make([]byte, 12*o.NumPixels+3) expected := make([]byte, 12*o.NumPixels+3)
@ -267,11 +318,13 @@ func TestDevLong(t *testing.T) {
} }
} }
func TestDevWrite_Long(t *testing.T) { func TestSPI_Write_Long(t *testing.T) {
buf := bytes.Buffer{} buf := bytes.Buffer{}
o := Opts{NumPixels: 150} o := Opts{NumPixels: 1, Channels: 3, Freq: 2500 * physic.KiloHertz}
o.NumPixels = 1 d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &o) if err != nil {
t.Fatal(err)
}
if n, err := d.Write([]byte{0, 0, 0, 1, 1, 1}); n != 0 || err == nil { if n, err := d.Write([]byte{0, 0, 0, 1, 1, 1}); n != 0 || err == nil {
t.Fatal(n, err) t.Fatal(n, err)
} }
@ -309,6 +362,8 @@ var drawTests = []struct {
}(), }(),
opts: Opts{ opts: Opts{
NumPixels: 4, NumPixels: 4,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
@ -323,14 +378,19 @@ var drawTests = []struct {
}(), }(),
opts: Opts{ opts: Opts{
NumPixels: 4, NumPixels: 4,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
} }
func TestDraws(t *testing.T) { func TestSPI_Draws(t *testing.T) {
for _, tt := range drawTests { for _, tt := range drawTests {
buf := bytes.Buffer{} buf := bytes.Buffer{}
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &tt.opts) d, err := NewSPI(spitest.NewRecordRaw(&buf), &tt.opts)
if err != nil {
t.Fatal(err)
}
if err := d.Draw(d.Bounds(), tt.img, image.Point{}); err != nil { if err := d.Draw(d.Bounds(), tt.img, image.Point{}); err != nil {
t.Fatalf("%s: %v", tt.name, err) t.Fatalf("%s: %v", tt.name, err)
} }
@ -347,6 +407,19 @@ func TestDraws(t *testing.T) {
} }
} }
func TestSPI_Draw_DstEmpty(t *testing.T) {
buf := bytes.Buffer{}
o := Opts{NumPixels: 4, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(spitest.NewRecordRaw(&buf), &o)
if err != nil {
t.Fatal(err)
}
img := image.NewNRGBA(image.Rect(0, 0, 1, 1))
if err := d.Draw(image.Rect(0, 0, 0, 0), img, image.Point{}); err != nil {
t.Fatal(err)
}
}
var offsetDrawWant = []byte{ var offsetDrawWant = []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE1, 0x89, 0x79, 0x6B, 0xE1, 0x89, 0x79, 0x6B,
@ -392,6 +465,8 @@ var offsetDrawTests = []struct {
want: offsetDrawWant, want: offsetDrawWant,
opts: Opts{ opts: Opts{
NumPixels: 15, NumPixels: 15,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
{ {
@ -431,14 +506,15 @@ var offsetDrawTests = []struct {
}, },
opts: Opts{ opts: Opts{
NumPixels: 17, NumPixels: 17,
Channels: 3,
Freq: 2500 * physic.KiloHertz,
}, },
}, },
} }
func TestHalt(t *testing.T) { func TestSPI_Halt(t *testing.T) {
s := spitest.Playback{ s := spitest.Playback{
Playback: conntest.Playback{ Playback: conntest.Playback{
DontPanic: false,
Count: 1, Count: 1,
Ops: []conntest.IO{ Ops: []conntest.IO{
{}, {},
@ -453,9 +529,11 @@ func TestHalt(t *testing.T) {
}, },
}, },
} }
o := Opts{NumPixels: 150} o := Opts{NumPixels: 4, Channels: 3, Freq: 2500 * physic.KiloHertz}
o.NumPixels = 4 d, err := NewSPI(&s, &o)
d, _ := NewSPI(&s, &o) if err != nil {
t.Fatal(err)
}
if err := d.Halt(); err != nil { if err := d.Halt(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -464,9 +542,24 @@ func TestHalt(t *testing.T) {
} }
} }
func TestSPI_Halt_fail(t *testing.T) {
s := spitest.Playback{Playback: conntest.Playback{DontPanic: true}}
o := Opts{NumPixels: 4, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(&s, &o)
if err != nil {
t.Fatal(err)
}
if d.Halt() == nil {
t.Fatal("expected failure")
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
}
type genColor func(int) [3]byte type genColor func(int) [3]byte
func benchmarkWrite(b *testing.B, o Opts, length int, f genColor) { func benchmarkSPIWrite(b *testing.B, o Opts, length int, f genColor) {
var pixels []byte var pixels []byte
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
c := f(i) c := f(i)
@ -474,27 +567,34 @@ func benchmarkWrite(b *testing.B, o Opts, length int, f genColor) {
} }
o.NumPixels = length o.NumPixels = length
b.ReportAllocs() b.ReportAllocs()
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o) d, err := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
_, _ = d.Write(pixels[:]) if err != nil {
b.Fatal(err)
}
if _, err := d.Write(pixels[:]); err != nil {
b.Fatal(err)
}
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = d.Write(pixels[:]) if _, err = d.Write(pixels[:]); err != nil {
b.Fatal(err)
}
} }
} }
func BenchmarkWriteWhite(b *testing.B) { func BenchmarkSPI_WriteWhite(b *testing.B) {
o := Opts{NumPixels: 150} o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0xFF, 0xFF, 0xFF} }) benchmarkSPIWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0xFF, 0xFF, 0xFF} })
} }
func BenchmarkWriteDim(b *testing.B) { func BenchmarkSPI_WriteDim(b *testing.B) {
o := Opts{NumPixels: 150} o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x01, 0x01, 0x01} }) benchmarkSPIWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x01, 0x01, 0x01} })
} }
func BenchmarkWriteBlack(b *testing.B) { func BenchmarkSPI_WriteBlack(b *testing.B) {
o := Opts{NumPixels: 150} o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x0, 0x0, 0x0} }) benchmarkSPIWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x0, 0x0, 0x0} })
} }
func genColorfulPixel(x int) [3]byte { func genColorfulPixel(x int) [3]byte {
@ -505,36 +605,40 @@ func genColorfulPixel(x int) [3]byte {
} }
} }
func BenchmarkWriteColorful(b *testing.B) { func BenchmarkSPI_WriteColorful(b *testing.B) {
o := Opts{NumPixels: 150} o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkWrite(b, o, 150, genColorfulPixel) benchmarkSPIWrite(b, o, 150, genColorfulPixel)
} }
func BenchmarkWriteColorfulPassThru(b *testing.B) { func BenchmarkSPI_WriteColorfulPassThru(b *testing.B) {
o := Opts{ o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
NumPixels: 150, benchmarkSPIWrite(b, o, 150, genColorfulPixel)
}
benchmarkWrite(b, o, 150, genColorfulPixel)
} }
func BenchmarkWriteColorfulVariation(b *testing.B) { func BenchmarkSPI_WriteColorfulVariation(b *testing.B) {
// Continuously vary the lookup tables. // Continuously vary the lookup tables.
b.ReportAllocs() b.ReportAllocs()
pixels := [256 * 3]byte{} pixels := [256 * 3]byte{}
for i := range pixels { for i := range pixels {
pixels[i] = uint8(i) + uint8(i>>8) pixels[i] = uint8(i) + uint8(i>>8)
} }
o := Opts{NumPixels: 150} o := Opts{NumPixels: len(pixels) / 3, Channels: 3, Freq: 2500 * physic.KiloHertz}
o.NumPixels = len(pixels) / 3 d, err := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o) if err != nil {
_, _ = d.Write(pixels[:]) b.Fatal(err)
}
if _, err = d.Write(pixels[:]); err != nil {
b.Fatal(err)
}
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = d.Write(pixels[:]) if _, err = d.Write(pixels[:]); err != nil {
b.Fatal(err)
}
} }
} }
func benchmarkDraw(b *testing.B, o Opts, img draw.Image, f genColor) { func benchmarkSPIDraw(b *testing.B, o Opts, img draw.Image, f genColor) {
for x := 0; x < img.Bounds().Dx(); x++ { for x := 0; x < img.Bounds().Dx(); x++ {
for y := 0; y < img.Bounds().Dy(); y++ { for y := 0; y < img.Bounds().Dy(); y++ {
pix := f(x) pix := f(x)
@ -558,22 +662,22 @@ func benchmarkDraw(b *testing.B, o Opts, img draw.Image, f genColor) {
} }
} }
func BenchmarkDrawNRGBAColorful(b *testing.B) { func BenchmarkSPI_DrawNRGBAColorful(b *testing.B) {
o := Opts{NumPixels: 150} o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel) benchmarkSPIDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel)
} }
func BenchmarkDrawNRGBAWhite(b *testing.B) { func BenchmarkSPI_DrawNRGBAWhite(b *testing.B) {
o := Opts{NumPixels: 150} o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), func(i int) [3]byte { return [3]byte{0xFF, 0xFF, 0xFF} }) benchmarkSPIDraw(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) { func BenchmarkDrawRGBAColorful(b *testing.B) {
o := Opts{NumPixels: 150} o := Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
benchmarkDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel) benchmarkSPIDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel)
} }
func BenchmarkDrawSlowpath(b *testing.B) { func BenchmarkSPI_DrawSlowpath(b *testing.B) {
// Should be an image type that doesn't have a fast path // Should be an image type that doesn't have a fast path
img := image.NewGray(image.Rect(0, 0, 150, 1)) img := image.NewGray(image.Rect(0, 0, 150, 1))
for x := 0; x < img.Bounds().Dx(); x++ { for x := 0; x < img.Bounds().Dx(); x++ {
@ -582,10 +686,12 @@ func BenchmarkDrawSlowpath(b *testing.B) {
img.Set(x, y, color.Gray{pix[0]}) img.Set(x, y, color.Gray{pix[0]})
} }
} }
o := Opts{NumPixels: 150} o := Opts{NumPixels: img.Bounds().Max.X, Channels: 3, Freq: 2500 * physic.KiloHertz}
o.NumPixels = img.Bounds().Max.X
b.ReportAllocs() b.ReportAllocs()
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o) d, err := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
if err != nil {
b.Fatal(err)
}
r := d.Bounds() r := d.Bounds()
p := image.Point{} p := image.Point{}
if err := d.Draw(r, img, p); err != nil { if err := d.Draw(r, img, p); err != nil {
@ -599,9 +705,13 @@ func BenchmarkDrawSlowpath(b *testing.B) {
} }
} }
func BenchmarkHalt(b *testing.B) { func BenchmarkSPI_Halt(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &Opts{NumPixels: 150}) o := &Opts{NumPixels: 150, Channels: 3, Freq: 2500 * physic.KiloHertz}
d, err := NewSPI(spitest.NewRecordRaw(ioutil.Discard), o)
if err != nil {
b.Fatal(err)
}
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if err := d.Halt(); err != nil { if err := d.Halt(); err != nil {
@ -620,6 +730,14 @@ func (c *configFail) Connect(f physic.Frequency, mode spi.Mode, bits int) (spi.C
return nil, errors.New("injected error") return nil, errors.New("injected error")
} }
type limitLow struct {
spitest.Record
}
func (c *limitLow) MaxTxSize() int {
return 1
}
func equalUint16(a, b []uint16) bool { func equalUint16(a, b []uint16) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false

@ -15,7 +15,7 @@ import (
"periph.io/x/periph/conn/physic" "periph.io/x/periph/conn/physic"
) )
func TestNew_3(t *testing.T) { func TestStream_NewBits_3(t *testing.T) {
g := gpiostreamtest.PinOutPlayback{ g := gpiostreamtest.PinOutPlayback{
N: "Yo", N: "Yo",
Ops: []gpiostream.Stream{ Ops: []gpiostream.Stream{
@ -27,6 +27,7 @@ func TestNew_3(t *testing.T) {
0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92,
0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49,
0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24,
0x00, 0x00, 0x00,
}, },
Freq: 800 * physic.KiloHertz, Freq: 800 * physic.KiloHertz,
LSBF: false, LSBF: false,
@ -35,7 +36,7 @@ func TestNew_3(t *testing.T) {
} }
opts := DefaultOpts opts := DefaultOpts
opts.NumPixels = 10 opts.NumPixels = 10
d, err := New(&g, &opts) d, err := NewStream(&g, &opts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -56,21 +57,21 @@ func TestNew_3(t *testing.T) {
} }
} }
func TestNew_fail(t *testing.T) { func TestStream_New_fail(t *testing.T) {
g := gpiostreamtest.PinOutPlayback{} g := gpiostreamtest.PinOutPlayback{}
opts := DefaultOpts opts := DefaultOpts
opts.Freq = 0 opts.Freq = 0
if _, err := New(&g, &opts); err == nil { if _, err := NewStream(&g, &opts); err == nil {
t.Fatal("hz == 0") t.Fatal("hz == 0")
} }
opts = DefaultOpts opts = DefaultOpts
opts.Channels = 2 opts.Channels = 2
if _, err := New(&g, &opts); err == nil { if _, err := NewStream(&g, &opts); err == nil {
t.Fatal("channels == 2") t.Fatal("channels == 2")
} }
} }
func TestDraw_NRGBA_3(t *testing.T) { func TestStream_Draw_NRGBA_3(t *testing.T) {
g := gpiostreamtest.PinOutPlayback{ g := gpiostreamtest.PinOutPlayback{
Ops: []gpiostream.Stream{ Ops: []gpiostream.Stream{
&gpiostream.BitStream{ &gpiostream.BitStream{
@ -81,6 +82,7 @@ func TestDraw_NRGBA_3(t *testing.T) {
0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb,
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d,
0xb6, 0x92, 0x49, 0xb6, 0x92, 0x49, 0xb4, 0x92, 0x4d, 0x24, 0xb6, 0x92, 0x49, 0xb6, 0x92, 0x49, 0xb4, 0x92, 0x4d, 0x24,
0x00, 0x00, 0x00,
}, },
Freq: 800 * physic.KiloHertz, Freq: 800 * physic.KiloHertz,
LSBF: false, LSBF: false,
@ -89,7 +91,10 @@ func TestDraw_NRGBA_3(t *testing.T) {
} }
opts := DefaultOpts opts := DefaultOpts
opts.NumPixels = 10 opts.NumPixels = 10
d, _ := New(&g, &opts) d, err := NewStream(&g, &opts)
if err != nil {
t.Fatal(err)
}
img := image.NewNRGBA(d.Bounds()) img := image.NewNRGBA(d.Bounds())
copy(img.Pix, getRGBW()) copy(img.Pix, getRGBW())
if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil {
@ -100,7 +105,7 @@ func TestDraw_NRGBA_3(t *testing.T) {
} }
} }
func TestDraw_RGBA_3(t *testing.T) { func TestStream_Draw_RGBA_3(t *testing.T) {
g := gpiostreamtest.PinOutPlayback{ g := gpiostreamtest.PinOutPlayback{
Ops: []gpiostream.Stream{ Ops: []gpiostream.Stream{
&gpiostream.BitStream{ &gpiostream.BitStream{
@ -111,6 +116,7 @@ func TestDraw_RGBA_3(t *testing.T) {
0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb,
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d,
0xa6, 0xda, 0x49, 0xb6, 0xd3, 0x4d, 0x34, 0xdb, 0x49, 0x36, 0xa6, 0xda, 0x49, 0xb6, 0xd3, 0x4d, 0x34, 0xdb, 0x49, 0x36,
0x00, 0x00, 0x00,
}, },
Freq: 800 * physic.KiloHertz, Freq: 800 * physic.KiloHertz,
LSBF: false, LSBF: false,
@ -119,7 +125,10 @@ func TestDraw_RGBA_3(t *testing.T) {
} }
opts := DefaultOpts opts := DefaultOpts
opts.NumPixels = 10 opts.NumPixels = 10
d, _ := New(&g, &opts) d, err := NewStream(&g, &opts)
if err != nil {
t.Fatal(err)
}
img := image.NewRGBA(d.Bounds()) img := image.NewRGBA(d.Bounds())
copy(img.Pix, getRGBW()) copy(img.Pix, getRGBW())
if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil {
@ -130,7 +139,7 @@ func TestDraw_RGBA_3(t *testing.T) {
} }
} }
func TestDraw_RGBA_4(t *testing.T) { func TestStream_Draw_RGBA_4(t *testing.T) {
g := gpiostreamtest.PinOutPlayback{ g := gpiostreamtest.PinOutPlayback{
Ops: []gpiostream.Stream{ Ops: []gpiostream.Stream{
&gpiostream.BitStream{ &gpiostream.BitStream{
@ -143,6 +152,7 @@ func TestDraw_RGBA_4(t *testing.T) {
0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6,
0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xd2, 0x49, 0x24, 0xda, 0x49, 0xb6, 0xd3, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xd2, 0x49, 0x24, 0xda, 0x49, 0xb6, 0xd3,
0x4d, 0x34, 0xdb, 0x49, 0x36, 0x92, 0x4d, 0x26, 0x4d, 0x34, 0xdb, 0x49, 0x36, 0x92, 0x4d, 0x26,
0x00, 0x00, 0x00,
}, },
Freq: 800 * physic.KiloHertz, Freq: 800 * physic.KiloHertz,
LSBF: false, LSBF: false,
@ -152,7 +162,10 @@ func TestDraw_RGBA_4(t *testing.T) {
opts := DefaultOpts opts := DefaultOpts
opts.NumPixels = 10 opts.NumPixels = 10
opts.Channels = 4 opts.Channels = 4
d, _ := New(&g, &opts) d, err := NewStream(&g, &opts)
if err != nil {
t.Fatal(err)
}
img := image.NewRGBA(d.Bounds()) img := image.NewRGBA(d.Bounds())
copy(img.Pix, getRGBW()) copy(img.Pix, getRGBW())
if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil {
@ -163,7 +176,7 @@ func TestDraw_RGBA_4(t *testing.T) {
} }
} }
func TestDraw_Limits(t *testing.T) { func TestStream_Draw_Limits(t *testing.T) {
g := gpiostreamtest.PinOutPlayback{ g := gpiostreamtest.PinOutPlayback{
Ops: []gpiostream.Stream{ Ops: []gpiostream.Stream{
&gpiostream.BitStream{ &gpiostream.BitStream{
@ -174,6 +187,7 @@ func TestDraw_Limits(t *testing.T) {
0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb,
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d,
0xa6, 0xda, 0x49, 0xb6, 0xd3, 0x4d, 0x34, 0xdb, 0x49, 0x36, 0xa6, 0xda, 0x49, 0xb6, 0xd3, 0x4d, 0x34, 0xdb, 0x49, 0x36,
0x00, 0x00, 0x00,
}, },
Freq: 800 * physic.KiloHertz, Freq: 800 * physic.KiloHertz,
LSBF: false, LSBF: false,
@ -182,7 +196,10 @@ func TestDraw_Limits(t *testing.T) {
} }
opts := DefaultOpts opts := DefaultOpts
opts.NumPixels = 10 opts.NumPixels = 10
d, _ := New(&g, &opts) d, err := NewStream(&g, &opts)
if err != nil {
t.Fatal(err)
}
img := image.NewRGBA(image.Rect(-1, -1, 20, 20)) img := image.NewRGBA(image.Rect(-1, -1, 20, 20))
copy(img.Pix, getRGBW()) copy(img.Pix, getRGBW())
if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil {
@ -193,7 +210,7 @@ func TestDraw_Limits(t *testing.T) {
} }
} }
func TestWrite_3(t *testing.T) { func TestStream_Write_3(t *testing.T) {
g := gpiostreamtest.PinOutPlayback{ g := gpiostreamtest.PinOutPlayback{
Ops: []gpiostream.Stream{ Ops: []gpiostream.Stream{
&gpiostream.BitStream{ &gpiostream.BitStream{
@ -204,6 +221,7 @@ func TestWrite_3(t *testing.T) {
0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb,
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0xa4, 0x92, 0x49, 0x36, 0x92, 0x49, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0xa4, 0x92, 0x49, 0x36, 0x92, 0x49,
0xa6, 0x92, 0x49, 0xb6, 0x92, 0x49, 0xb4, 0x92, 0x4d, 0x24, 0xa6, 0x92, 0x49, 0xb6, 0x92, 0x49, 0xb4, 0x92, 0x4d, 0x24,
0x00, 0x00, 0x00,
}, },
Freq: 800 * physic.KiloHertz, Freq: 800 * physic.KiloHertz,
LSBF: false, LSBF: false,
@ -212,7 +230,10 @@ func TestWrite_3(t *testing.T) {
} }
opts := DefaultOpts opts := DefaultOpts
opts.NumPixels = 10 opts.NumPixels = 10
d, _ := New(&g, &opts) d, err := NewStream(&g, &opts)
if err != nil {
t.Fatal(err)
}
if n, err := d.Write(getRGB()); n != 30 || err != nil { if n, err := d.Write(getRGB()); n != 30 || err != nil {
t.Fatal(n, err) t.Fatal(n, err)
} }
@ -221,11 +242,14 @@ func TestWrite_3(t *testing.T) {
} }
} }
func TestWrite_fail(t *testing.T) { func TestStream_Write_fail(t *testing.T) {
g := gpiostreamtest.PinOutPlayback{DontPanic: true} g := gpiostreamtest.PinOutPlayback{DontPanic: true}
opts := DefaultOpts opts := DefaultOpts
opts.NumPixels = 10 opts.NumPixels = 10
d, _ := New(&g, &opts) d, err := NewStream(&g, &opts)
if err != nil {
t.Fatal(err)
}
if n, err := d.Write([]byte{1}); n != 0 || err == nil { if n, err := d.Write([]byte{1}); n != 0 || err == nil {
t.Fatal(n, err) t.Fatal(n, err)
} }
@ -237,11 +261,14 @@ func TestWrite_fail(t *testing.T) {
} }
} }
func TestHalt_fail(t *testing.T) { func TestStream_Halt_fail(t *testing.T) {
g := gpiostreamtest.PinOutPlayback{DontPanic: true} g := gpiostreamtest.PinOutPlayback{DontPanic: true}
opts := DefaultOpts opts := DefaultOpts
opts.NumPixels = 10 opts.NumPixels = 10
d, _ := New(&g, &opts) d, err := NewStream(&g, &opts)
if err != nil {
t.Fatal(err)
}
if d.Halt() == nil { if d.Halt() == nil {
t.Fatal("expected failure") t.Fatal("expected failure")
} }
@ -250,7 +277,7 @@ func TestHalt_fail(t *testing.T) {
} }
} }
func TestRaster_3_3(t *testing.T) { func TestStream_Raster_3_3(t *testing.T) {
data := []byte{ data := []byte{
// 24 bits per pixel in RGB // 24 bits per pixel in RGB
0, 1, 2, 0, 1, 2,
@ -262,13 +289,13 @@ func TestRaster_3_3(t *testing.T) {
0xdb, 0x6d, 0xb4, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb4, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xb6,
} }
actual := make([]byte, len(expected)) actual := make([]byte, len(expected))
raster(actual, data, 3, 3) rasterBits(actual, data, 3, 3)
if !bytes.Equal(expected, actual) { if !bytes.Equal(expected, actual) {
t.Fatalf("\nexpected %#v\n actual %#v", expected, actual) t.Fatalf("\nexpected %#v\n actual %#v", expected, actual)
} }
} }
func TestRaster_4_4(t *testing.T) { func TestStream_Raster_4_4(t *testing.T) {
data := []byte{ data := []byte{
// 32 bits per pixel in RGBW // 32 bits per pixel in RGBW
0, 1, 2, 3, 0, 1, 2, 3,
@ -280,7 +307,7 @@ func TestRaster_4_4(t *testing.T) {
0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa4, 0xdb, 0x6d, 0xb4, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa4, 0xdb, 0x6d, 0xb4, 0xdb, 0x6d, 0xb6,
} }
actual := make([]byte, len(expected)) actual := make([]byte, len(expected))
raster(actual, data, 4, 4) rasterBits(actual, data, 4, 4)
if !bytes.Equal(expected, actual) { if !bytes.Equal(expected, actual) {
t.Fatalf("\nexpected %#v\n actual %#v", expected, actual) t.Fatalf("\nexpected %#v\n actual %#v", expected, actual)
} }
Loading…
Cancel
Save