diff --git a/devices/apa102/apa102.go b/devices/apa102/apa102.go index fabf38d..992204b 100644 --- a/devices/apa102/apa102.go +++ b/devices/apa102/apa102.go @@ -6,6 +6,7 @@ package apa102 import ( "errors" + "fmt" "image" "image/color" @@ -224,6 +225,10 @@ type Dev struct { pixels []byte } +func (d *Dev) String() string { + return fmt.Sprintf("APA102{I:%d, T:%dK, %dLEDs, %s}", d.Intensity, d.Temperature, d.numLights, d.s) +} + // ColorModel implements devices.Display. There's no surprise, it is // color.NRGBAModel. func (d *Dev) ColorModel() color.Model { @@ -262,7 +267,7 @@ func (d *Dev) Write(pixels []byte) (int, error) { } d.l.init(d.Intensity, d.Temperature) // Trying to write more pixels than defined? - if o := len(d.pixels) / 4; o < len(pixels)/3 { + if o := d.numLights; o < len(pixels)/3 { pixels = pixels[:o*3] } // Do not touch header and footer. @@ -271,6 +276,12 @@ func (d *Dev) Write(pixels []byte) (int, error) { return len(pixels), err } +// Halt turns off all the lights. +func (d *Dev) Halt() error { + _, err := d.Write(make([]byte, d.numLights*3)) + return err +} + // New returns a strip that communicates over SPI to APA102 LEDs. // // The SPI bus speed should be high, at least in the Mhz range, as @@ -309,3 +320,4 @@ func New(s spi.Conn, numLights int, intensity uint8, temperature uint16) (*Dev, var errLength = errors.New("apa102: invalid RGB stream length") var _ devices.Display = &Dev{} +var _ devices.Device = &Dev{} diff --git a/devices/apa102/apa102_test.go b/devices/apa102/apa102_test.go index cfb7f10..a611ad7 100644 --- a/devices/apa102/apa102_test.go +++ b/devices/apa102/apa102_test.go @@ -14,6 +14,7 @@ import ( "log" "testing" + "periph.io/x/periph/conn/conntest" "periph.io/x/periph/conn/spi" "periph.io/x/periph/conn/spi/spireg" "periph.io/x/periph/conn/spi/spitest" @@ -334,6 +335,9 @@ func TestDevEmpty(t *testing.T) { if expected := []byte{0x0, 0x0, 0x0, 0x0, 0xFF}; !bytes.Equal(expected, buf.Bytes()) { t.Fatalf("%#v != %#v", expected, buf.Bytes()) } + if s := d.String(); s != "APA102{I:255, T:6500K, 0LEDs, recordraw}" { + t.Fatal(s) + } } func TestDevParamsFail(t *testing.T) { @@ -547,6 +551,23 @@ func TestDrawRGBA(t *testing.T) { } } +func TestHalt(t *testing.T) { + bus := 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}}, + }, + }, + } + d, _ := New(&bus, 4, 250, 5000) + if err := d.Halt(); err != nil { + t.Fatal(err) + } + if err := bus.Close(); err != nil { + t.Fatal(err) + } +} + func TestInit(t *testing.T) { // Catch the "maxB == maxG" line. l := lut{} diff --git a/devices/bme280/bme280.go b/devices/bme280/bme280.go index edf3724..9cd1311 100644 --- a/devices/bme280/bme280.go +++ b/devices/bme280/bme280.go @@ -77,6 +77,10 @@ type Dev struct { c calibration } +func (d *Dev) String() string { + return fmt.Sprintf("BME280{%s}", d.d) +} + // Sense returns measurements as °C, kPa and % of relative humidity. func (d *Dev) Sense(env *devices.Environment) error { // All registers must be read in a single pass, as noted at page 21, section @@ -105,9 +109,10 @@ func (d *Dev) Sense(env *devices.Environment) error { return nil } -// Stop stops the bme280 from acquiring measurements. It is recommended to call -// to reduce idle power usage. -func (d *Dev) Stop() error { +// Halt stops the bme280 from acquiring measurements. +// +// It is recommended to call to reduce idle power usage. +func (d *Dev) Halt() error { // Page 27 (for register) and 12~13 section 3.3. return d.writeCommands([]byte{0xF4, byte(sleep)}) } @@ -397,3 +402,4 @@ func (c *calibration) compensateHumidityInt(raw, tFine int32) uint32 { } var _ devices.Environmental = &Dev{} +var _ devices.Device = &Dev{} diff --git a/devices/bme280/bme280_test.go b/devices/bme280/bme280_test.go index 69414ec..6f3d0f8 100644 --- a/devices/bme280/bme280_test.go +++ b/devices/bme280/bme280_test.go @@ -79,6 +79,9 @@ func TestSPISense_success(t *testing.T) { if err != nil { t.Fatal(err) } + if s := dev.String(); s != "BME280{playback}" { + t.Fatal(s) + } env := devices.Environment{} if err := dev.Sense(&env); err != nil { t.Fatal(err) @@ -149,7 +152,7 @@ func TestNewSPI_fail_chipid(t *testing.T) { } } -func TestNewI2C_fail(t *testing.T) { +func TestNewI2C_fail_io(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ // Chipd ID detection. @@ -167,7 +170,7 @@ func TestNewI2C_fail(t *testing.T) { } } -func TestNewI2C_chipid(t *testing.T) { +func TestNewI2C_fail_chipid(t *testing.T) { bus := i2ctest.Playback{ Ops: []i2ctest.IO{ // Chipd ID detection. @@ -305,7 +308,7 @@ func TestI2CSense_success(t *testing.T) { {Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xe0, 0xf4, 0x6f}, R: nil}, // Read. {Addr: 0x76, W: []byte{0xf7}, R: []byte{0x4a, 0x52, 0xc0, 0x80, 0x96, 0xc0, 0x7a, 0x76}}, - // Stop. + // Halt. {Addr: 0x76, W: []byte{0xf4, 0x0}}, }, } @@ -313,6 +316,9 @@ func TestI2CSense_success(t *testing.T) { if err != nil { t.Fatal(err) } + if s := dev.String(); s != "BME280{playback(118)}" { + t.Fatal(s) + } env := devices.Environment{} if err := dev.Sense(&env); err != nil { t.Fatal(err) @@ -326,7 +332,7 @@ func TestI2CSense_success(t *testing.T) { if env.Humidity != 6531 { t.Fatalf("humidity %d", env.Humidity) } - if err := dev.Stop(); err != nil { + if err := dev.Halt(); err != nil { t.Fatal(err) } if err := bus.Close(); err != nil { diff --git a/devices/bme280/bme280smoketest/bme280smoketest.go b/devices/bme280/bme280smoketest/bme280smoketest.go index 65da9f7..af9e6c8 100644 --- a/devices/bme280/bme280smoketest/bme280smoketest.go +++ b/devices/bme280/bme280smoketest/bme280smoketest.go @@ -133,7 +133,7 @@ func run(i2cBus i2c.Bus, spiBus spi.ConnCloser) (err error) { return err2 } defer func() { - if err2 := i2cDev.Stop(); err == nil { + if err2 := i2cDev.Halt(); err == nil { err = err2 } }() @@ -143,7 +143,7 @@ func run(i2cBus i2c.Bus, spiBus spi.ConnCloser) (err error) { return err2 } defer func() { - if err2 := spiDev.Stop(); err == nil { + if err2 := spiDev.Halt(); err == nil { err = err2 } }() diff --git a/devices/devices.go b/devices/devices.go index e27a953..cfebfb7 100644 --- a/devices/devices.go +++ b/devices/devices.go @@ -11,6 +11,18 @@ import ( "io" ) +// Device is a basic device. +type Device interface { + fmt.Stringer + // Halt stops the device. + // + // Unlike a connection, a device cannot be closed, only the port can be + // closed. On the other hand, a device can be halted. What halting entails + // depends on the actual device but it should stop motion, sensing or light + // emition. + Halt() error +} + // Display represents a pixel output device. It is a write-only interface. // // What Display represents can be as varied as a 1 bit OLED display or a strip diff --git a/devices/ds18b20/ds18b20.go b/devices/ds18b20/ds18b20.go index 375d90b..59fda10 100644 --- a/devices/ds18b20/ds18b20.go +++ b/devices/ds18b20/ds18b20.go @@ -23,6 +23,7 @@ package ds18b20 import ( "errors" + "fmt" "time" "periph.io/x/periph/conn/onewire" @@ -93,6 +94,15 @@ type Dev struct { resolution int // resolution in bits (9..12) } +func (d *Dev) String() string { + return fmt.Sprintf("DS18B20{%v}", d.onewire) +} + +// Halt implements devices.Device. +func (d *Dev) Halt() error { + return nil +} + // Temperature performs a conversion and returns the temperature. func (d *Dev) Temperature() (devices.Celsius, error) { if err := d.onewire.TxPower([]byte{0x44}, nil); err != nil { @@ -163,3 +173,5 @@ func (d *Dev) readScratchpad() ([]byte, error) { return spad[:8], nil } + +var _ devices.Device = &Dev{} diff --git a/devices/ds18b20/ds18b20_test.go b/devices/ds18b20/ds18b20_test.go index f41bb58..b036521 100644 --- a/devices/ds18b20/ds18b20_test.go +++ b/devices/ds18b20/ds18b20_test.go @@ -13,7 +13,7 @@ import ( "periph.io/x/periph/devices" ) -func TestNew_resolution(t *testing.T) { +func TestNew_fail_resolution(t *testing.T) { bus := &onewiretest.Playback{} var addr onewire.Address = 0x740000070e41ac28 if d, err := New(bus, addr, 1); d != nil || err == nil { @@ -21,7 +21,7 @@ func TestNew_resolution(t *testing.T) { } } -func TestNew_read(t *testing.T) { +func TestNew_fail_read(t *testing.T) { bus := &onewiretest.Playback{DontPanic: true} var addr onewire.Address = 0x740000070e41ac28 if d, err := New(bus, addr, 9); d != nil || err == nil { @@ -53,14 +53,16 @@ func TestTemperature(t *testing.T) { var addr onewire.Address = 0x740000070e41ac28 var temp devices.Celsius = 30000 // 30.000°C bus := onewiretest.Playback{Ops: ops} - // Init the ds18b20. - ds18b20, err := New(&bus, addr, 10) + dev, err := New(&bus, addr, 10) if err != nil { t.Fatal(err) } + if s := dev.String(); s != "DS18B20{{playback 8358680938703596584}}" { + t.Fatal(s) + } // Read the temperature. t0 := time.Now() - now, err := ds18b20.Temperature() + now, err := dev.Temperature() dt := time.Since(t0) if err != nil { t.Fatal(err) @@ -73,6 +75,9 @@ func TestTemperature(t *testing.T) { if dt < 188*time.Millisecond { t.Errorf("expected conversion to take >187ms, took %s", dt) } + if err := dev.Halt(); err != nil { + t.Fatal(err) + } if err := bus.Close(); err != nil { t.Fatal(err) } @@ -101,17 +106,17 @@ func TestConvertAll(t *testing.T) { } } -func TestConvertAll_resolution(t *testing.T) { +func TestConvertAll_fail_resolution(t *testing.T) { bus := &onewiretest.Playback{} if err := ConvertAll(bus, 1); err == nil { t.Fatal("invalid resolution") } } -func TestConvertAll_fail(t *testing.T) { +func TestConvertAll_fail_io(t *testing.T) { bus := &onewiretest.Playback{DontPanic: true} if err := ConvertAll(bus, 9); err == nil { - t.Fatal("invalid resolution") + t.Fatal("invalid io") } } diff --git a/devices/ds248x/dev.go b/devices/ds248x/dev.go index 926371d..b10ebf9 100644 --- a/devices/ds248x/dev.go +++ b/devices/ds248x/dev.go @@ -11,6 +11,7 @@ import ( "periph.io/x/periph/conn" "periph.io/x/periph/conn/onewire" + "periph.io/x/periph/devices" ) // Dev is a handle to a ds248x device and it implements the onewire.Bus @@ -36,7 +37,15 @@ type Dev struct { } func (d *Dev) String() string { - return "ds248x" + if d.isDS2483 { + return fmt.Sprintf("DS2483{%s}", d.i2c) + } + return fmt.Sprintf("DS2482-100{%s}", d.i2c) +} + +// Halt implements devices.Device. +func (d *Dev) Halt() error { + return nil } // Tx performs a bus transaction, sending and receiving bytes, and ending by @@ -187,3 +196,5 @@ type busError string func (e busError) Error() string { return string(e) } func (e busError) BusError() bool { return true } + +var _ devices.Device = &Dev{} diff --git a/devices/ds248x/ds248x_test.go b/devices/ds248x/ds248x_test.go index e24963c..a799774 100644 --- a/devices/ds248x/ds248x_test.go +++ b/devices/ds248x/ds248x_test.go @@ -54,9 +54,12 @@ func TestNew(t *testing.T) { if err != nil { t.Fatal(err) } - if s := d.String(); s != "ds248x" { + if s := d.String(); s != "DS2483{playback(24)}" { t.Fatal(s) } + if err := d.Halt(); err != nil { + t.Fatal(err) + } if err := bus.Close(); err != nil { t.Fatal(err) } diff --git a/devices/ssd1306/ssd1306.go b/devices/ssd1306/ssd1306.go index 1744be6..7f75073 100644 --- a/devices/ssd1306/ssd1306.go +++ b/devices/ssd1306/ssd1306.go @@ -488,3 +488,4 @@ const ( ) var _ devices.Display = &Dev{} +var _ devices.Device = &Dev{} diff --git a/devices/tm1637/tm1637.go b/devices/tm1637/tm1637.go index 1793523..7e94503 100644 --- a/devices/tm1637/tm1637.go +++ b/devices/tm1637/tm1637.go @@ -11,10 +11,12 @@ package tm1637 import ( "errors" + "fmt" "runtime" "time" "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/devices" "periph.io/x/periph/host/cpu" ) @@ -67,6 +69,10 @@ type Dev struct { data gpio.PinIO } +func (d *Dev) String() string { + return fmt.Sprintf("TM1637{clk:%s, data:%s}", d.clk, d.data) +} + // SetBrightness changes the brightness and/or turns the display on and off. func (d *Dev) SetBrightness(b Brightness) error { // This helps reduce jitter a little. @@ -113,6 +119,13 @@ func (d *Dev) Write(seg []byte) (int, error) { return len(seg), nil } +// Halt turns the display off. +func (d *Dev) Halt() error { + b := [6]byte{} + _, err := d.Write(b[:]) + return err +} + // New returns an object that communicates over two pins to a TM1637. func New(clk gpio.PinOut, data gpio.PinIO) (*Dev, error) { // Spec calls to idle at high. @@ -186,3 +199,5 @@ func (d *Dev) writeByte(b byte) (bool, error) { func (d *Dev) sleepHalfCycle() { cpu.Nanospin(clockHalfCycle) } + +var _ devices.Device = &Dev{} diff --git a/devices/tm1637/tm1637_test.go b/devices/tm1637/tm1637_test.go index f678519..bee033f 100644 --- a/devices/tm1637/tm1637_test.go +++ b/devices/tm1637/tm1637_test.go @@ -40,12 +40,18 @@ func TestNew(t *testing.T) { if err != nil { t.Fatalf("failed to initialize tm1637: %v", err) } + if s := dev.String(); s != "TM1637{clk:(0), data:(0)}" { + t.Fatal(s) + } if _, err := dev.Write(Clock(12, 00, true)); err != nil { log.Fatalf("failed to write to tm1637: %v", err) } if err := dev.SetBrightness(Brightness10); err != nil { log.Fatalf("failed to write to tm1637: %v", err) } + if err := dev.Halt(); err != nil { + t.Fatal(err) + } // TODO(maruel): Check the state of the pins. That's hard since it has to // emulate the quasi-I²C protocol. }