cap1188 experimental device (#192)

This driver was tested against a adafruit version of the cap1188
https://learn.adafruit.com/adafruit-cap1188-breakout/overview and the
pimoroni drumhat:
https://shop.pimoroni.com/collections/raspberry-pi/products/drum-hat
(which has its LED reversed and uses a different i2c address).
pull/1/head
Matt Aimonetti 9 years ago committed by M-A
parent 11522179a3
commit 68f531c751

@ -4,6 +4,7 @@
# some cases, their employer may be the copyright holder. To see the full list
# of contributors, see the revision history in source control.
Google Inc.
Matt Aimonetti <mattaimonetti@gmail.com>
Max Ekman <max@looplab.se>
Stephan Sperber
Thorsten von Eicken <tve@voneicken.com>

@ -28,6 +28,7 @@
Hidetoshi Shimokawa <smkwhdts@gmail.com>
Marc-Antoine Ruel <maruel@chromium.org> <maruel@gmail.com>
Matt Aimonetti <mattaimonetti@gmail.com>
Max Ekman <max@looplab.se>
Matias Insaurralde <matias@insaurral.de>
Stephan Sperber <sperberstephan@googlemail.com>

@ -0,0 +1,522 @@
// Copyright 2017 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 cap1188 controls a Microchip cap1188 device over I²C.
// The device is a 8 Channel Capacitive Touch Sensor with 8 LED Drivers
//
// Datasheet
//
// The official data sheet can be found here:
//
// http://ww1.microchip.com/downloads/en/DeviceDoc/CAP1188.pdf
//
package cap1188
import (
"encoding/binary"
"errors"
"fmt"
"log"
"time"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/mmr"
"periph.io/x/periph/conn/spi"
"periph.io/x/periph/devices"
)
// TouchStatus is the status of an input sensor
type TouchStatus int8
func (t TouchStatus) String() string {
switch t {
case OffStatus:
return strOffStatus
case PressedStatus:
return strPressedStatus
case HeldStatus:
return strHeldStatus
case ReleasedStatus:
return strReleasedStatus
default:
return "Unknown"
}
}
const (
// OffStatus indicates that the input sensor isn't being activated
OffStatus TouchStatus = iota
// PressedStatus indicates that the input sensor is currently pressed
PressedStatus
// HeldStatus indicates that the input sensor was pressed and is still held pressed
HeldStatus
// ReleasedStatus indicates that the input sensor was pressed and is being released
ReleasedStatus
)
const (
nbrOfLEDs = 8
strOffStatus = "Off"
strPressedStatus = "Pressed"
strHeldStatus = "Held"
strReleasedStatus = "Released"
)
const (
// reg_LEDLinking - The Sensor Input LED Linking register controls whether a
// capacitive touch sensor input is linked to an LED output. If the
// corresponding bit is set, then the appropriate LED output will change
// states defined by the LED Behavior controls in response to the capacitive
// touch sensor input.
reg_LEDLinking = 0x72
// reg_LEDOutputControl - The LED Output Control Register controls the output
// state of the LED pins that are not linked to sensor inputs.
reg_LEDOutputControl = 0x74
)
// Dev is a handle to a cap1188.
type Dev struct {
Opts
d conn.Conn
regWrapper mmr.Dev8
isSPI bool
inputStatuses []TouchStatus
resetAt time.Time
}
func (d *Dev) String() string {
return fmt.Sprintf("cap1188{%s}", d.regWrapper.Conn)
}
// Halt is a noop for the cap1188.
func (d *Dev) Halt() error {
return nil
}
// InputStatus reads and returns the status of the 8 inputs as an array where
// each entry indicates a touch event or not.
func (d *Dev) InputStatus() ([]TouchStatus, error) {
// first check that we are ready
now := time.Now()
readyAt := d.resetAt.Add(200 * time.Millisecond)
if now.Before(readyAt) {
time.Sleep(readyAt.Sub(now))
}
// read inputs
status, err := d.regWrapper.ReadUint8(0x3)
if err != nil {
return d.inputStatuses, wrap(fmt.Errorf("failed to read the input values - %s", err))
}
// read deltas (in two's complement, capped at -128 to 127)
deltasB := [nbrOfLEDs]byte{}
if err = d.regWrapper.ReadStruct(0x10, &deltasB); err != nil {
return d.inputStatuses, wrap(fmt.Errorf("failed to read the delta values - %s", err))
}
deltas := [nbrOfLEDs]int{}
for i, b := range deltasB {
deltas[i] = int(int8(b))
}
// read threshold
thresholds := [nbrOfLEDs]byte{}
if err = d.regWrapper.ReadStruct(0x30, &thresholds); err != nil {
return d.inputStatuses, wrap(fmt.Errorf("failed to read the threshold values - %s", err))
}
// convert the data into a sensor state
var touched bool
for i := uint8(0); i < uint8(len(d.inputStatuses)); i++ {
// check if the bit is set.
touched = (status>>(7-i))&1 == 1
// TODO(mattetti): check if the event is passed the threshold:
// deltas[i] > int(thresholds[i])
if touched {
if d.inputStatuses[i] == PressedStatus {
if d.RetriggerOnHold {
d.inputStatuses[i] = HeldStatus
}
continue
}
d.inputStatuses[i] = PressedStatus
} else {
d.inputStatuses[i] = OffStatus
}
}
return d.inputStatuses, nil
}
// LinkLEDs links the behavior of the LEDs to the touch sensors.
// Doing so, disabled the option for the host to set specific LEDs on/off.
func (d *Dev) LinkLEDs(on bool) error {
if on {
if err := d.regWrapper.WriteUint8(reg_LEDLinking, 0xff); err != nil {
return wrap(fmt.Errorf("failed to link LEDs - %s", err))
}
} else {
if err := d.regWrapper.WriteUint8(reg_LEDLinking, 0x00); err != nil {
return wrap(fmt.Errorf("failed to unlink LEDs - %s", err))
}
}
d.LinkedLEDs = on
return nil
}
// AllLEDs turns all the LEDs on or off.
//
// This is quite more efficient than looping through each led and turn them on/off.
func (d *Dev) AllLEDs(on bool) error {
if d.LinkedLEDs {
return wrap(fmt.Errorf("can't manually set LEDs when they are linked to sensors"))
}
if on {
return d.regWrapper.WriteUint8(reg_LEDOutputControl, 0xff)
}
return d.regWrapper.WriteUint8(reg_LEDOutputControl, 0x00)
}
// SetLED sets the state of a LED as on or off
// Only works if the LEDs are not linked to the sensors
func (d *Dev) SetLED(idx int, state bool) error {
if d.LinkedLEDs {
return wrap(fmt.Errorf("can't manually set LEDs when they are linked to sensors"))
}
if idx > 7 || idx < 0 {
return wrap(fmt.Errorf("invalid led idx %d", idx))
}
if d.Debug {
log.Printf("Set LED state %d - %t\n", idx, state)
}
if state {
return d.setBit(reg_LEDOutputControl, idx)
}
return d.clearBit(reg_LEDOutputControl, idx)
}
// Reset issues a soft reset to the device using the reset pin
// if available.
func (d *Dev) Reset() (err error) {
d.ClearInterrupt()
if d != nil && d.ResetPin != nil {
if d.Debug {
log.Println("cap1188: Resetting the device using the reset pin")
}
if err = d.ResetPin.Out(gpio.Low); err != nil {
return err
}
time.Sleep(1 * time.Microsecond)
if err = d.ResetPin.Out(gpio.High); err != nil {
return err
}
time.Sleep(10 * time.Millisecond)
if err = d.ResetPin.Out(gpio.Low); err != nil {
return err
}
}
// track the reset time since the device won't be ready for up to 15ms
// and won't be ready for first conversion for up to 200ms
d.resetAt = time.Now()
return nil
}
// ClearInterrupt resets the interrupt flag
func (d *Dev) ClearInterrupt() error {
// clear the main control bit
return d.clearBit(0x0, 0)
}
// NewI2C returns a new device that communicates over I²C to cap1188.
func NewI2C(b i2c.Bus, opts *Opts) (*Dev, error) {
addr := uint16(0x28) // default address
if opts != nil {
switch opts.Address {
case 0x28, 0x29, 0x2a, 0x2b, 0x2c:
addr = opts.Address
case 0x00:
// do not do anything
default:
return nil, wrap(errors.New("given address not supported by device"))
}
}
d := &Dev{d: &i2c.Dev{Bus: b, Addr: addr}, isSPI: false}
if d.Debug {
log.Printf("cap1188: Connecting via I2C address: %#X\n", addr)
}
d.inputStatuses = make([]TouchStatus, nbrOfLEDs)
if err := d.makeDev(opts); err != nil {
return nil, err
}
// time to communications is 15ms
now := time.Now()
readyAt := d.resetAt.Add(15 * time.Millisecond)
if now.Before(readyAt) {
time.Sleep(readyAt.Sub(now))
}
return d, nil
}
// NewSPI returns an object that communicates over SPI to cap1188 environmental
// sensor.
func NewSPI(p spi.Port, opts *Opts) (*Dev, error) {
return nil, fmt.Errorf("not implemented")
}
func (d *Dev) makeDev(opts *Opts) error {
// Use default options if none are passed.
if opts == nil {
opts = DefaultOpts()
}
d.Opts = *opts
d.regWrapper = mmr.Dev8{Conn: d.d, Order: binary.LittleEndian}
var productID byte
var err error
// Read the product id to confirm it matches our expectations.
if productID, err = d.regWrapper.ReadUint8(0xFD); err != nil {
return fmt.Errorf("failed to read product id - %s", err)
}
if productID != 0x50 {
return fmt.Errorf("cap1188: unexpected chip id %x; is this a cap1188?", productID)
}
// manufacturer ID on 0xFE, should be 0x5D
// revision ID on 0xFF, should be 0x83
// reset the device
if err = d.Reset(); err != nil {
return fmt.Errorf("failed to reset the device - %s", err)
}
var recalFlag byte
if opts.EnableRecalibration {
recalFlag = 1
}
var intOnRel byte
if !opts.InterruptOnRelease {
intOnRel = 1 // 0 = trigger on release
}
// enable all inputs
if err = d.regWrapper.WriteUint8(0x21, 0xff); err != nil {
return wrap(fmt.Errorf("failed to enable all inputs - %s", err))
}
// enable interrupts
if err = d.regWrapper.WriteUint8(0x27, 0xff); err != nil {
return wrap(fmt.Errorf("failed to enable interrupts - %s", err))
}
// enable/disable repeats
// TODO(mattetti): make it an option
if err = d.regWrapper.WriteUint8(0x28, 0xff); err != nil {
return fmt.Errorf("failed to disable repeats - %s", err)
}
// enable multitouch
multitouchConfig := (
// Enables the multiple button blocking circuitry.
// „ 0 - The multiple touch circuitry is disabled. The device will not
// block multiple touches.
// „ 1 (default) - The multiple touch circuitry is enabled. The device
// will flag the number of touches equal to programmed multiple touch
// threshold and block all others. It will remember which sensor inputs
// are valid and block all others until that sensor pad has been
// released. Once a sensor pad has been released, the N detected touches
// (determined via the cycle order of CS1 - CS8) will be flagged and all
// others blocked.
byte(0)<<7 |
byte(0)<<6 | byte(0)<<5 | byte(0)<<4 |
// Determines the number of simultaneous touches on all sensor pads
// before a Multiple Touch Event is detected and sensor inputs are blocked.
// set to 2
byte(0)<<3 | byte(1)<<2 |
byte(0)<<1 | byte(0)<<0)
if err = d.regWrapper.WriteUint8(0x2a, multitouchConfig); err != nil {
return wrap(fmt.Errorf("failed to enable multitouch - %s", err))
}
// Averaging and Sampling Config
samplingConfig := (byte(0)<<7 |
// number of samples taken per measurement
// TODO(mattetti): use opts.SamplesPerMeasurement
byte(0)<<6 |
byte(0)<<5 |
byte(0)<<4 |
// sample time
// TODO(mattetti): use opts.SamplingTime
byte(1)<<3 |
byte(0)<<2 |
// overall cycle time
// TODO(mattetti): use opts.CycleTime
byte(0)<<1 |
byte(0)<<0)
if d.Debug {
log.Printf("cap1188: Sampling config mask: %08b\n", samplingConfig)
}
if err = d.regWrapper.WriteUint8(0x24, samplingConfig); err != nil {
return fmt.Errorf("failed to enable multitouch - %s", err)
}
// customize sensitivity (TODO)
sensitivity := (byte(0)<<7 |
// Controls the sensitivity of a touch detection. The sensitivity settings act
// to scale the relative delta count value higher or lower based on the system parameters. A setting of
// 000b is the most sensitive while a setting of 111b is the least sensitive. At the more sensitive settings,
// touches are detected for a smaller delta capacitance corresponding to a “lighter” touch. These settings
// are more sensitive to noise, however, and a noisy environment may flag more false touches with higher
// sensitivity levels.
// Set to 4x
// TODO(mattetti): make that configurable.
byte(1)<<6 | byte(0)<<5 | byte(1)<<4 |
byte(0)<<3 | byte(0)<<2 | byte(0)<<1 | byte(0)<<0)
if d.Debug {
log.Printf("cap1188: Sensitivity mask: %08b\n", sensitivity)
}
if err = d.regWrapper.WriteUint8(0x1F, sensitivity); err != nil {
return fmt.Errorf("failed to set sensitivity - %s", err)
}
if opts.LinkedLEDs {
if err = d.LinkLEDs(true); err != nil {
return err
}
}
if opts.RetriggerOnHold {
if err = d.regWrapper.WriteUint8(0x28, 0xff); err != nil {
return fmt.Errorf("failed to set retrigger on hold - %s", err)
}
} else {
if err = d.regWrapper.WriteUint8(0x28, 0x00); err != nil {
return fmt.Errorf("failed to turn off retrigger on hold - %s", err)
}
}
// page 47 - configuration registers
config := (
// Timeout: Enables the timeout and idle functionality of the SMBus protocol.
// default 0: The SMBus timeout and idle functionality are disabled. The
// SMBus interface will not time out if the clock line is held low.
// Likewise, it will not reset if both data and clock lines are held
// high for longer than 200us
byte(0)<<7 |
// Configures the operation of the WAKE pin.
// default 0: The WAKE pin is not asserted when a touch is detected while the
// device is in Standby. It will still be used to wake the device from
// Deep Sleep when driven high.
byte(0)<<6 |
// digital noise threshold
// Determines whether the digital noise threshold is used by the device.
// default 1: The noise threshold is disabled. Any delta count that
// is less than the touch threshold is used for the automatic
// re-calibration routine.
byte(1)<<5 |
// analog noise filter
// default 0: Determines whether the analog noise filter is enabled. Setting this
// bit disables the feature.
byte(1)<<4 |
// maximum duration recalibration
// Determines whether the maximum duration recalibration is enabled.
//
// if 0, the maximum duration recalibration functionality is
// disabled. A touch may be held indefinitely and no re-calibration
// will be performed on any sensor input.
//
// if 1, The maximum duration recalibration functionality is
// enabled. If a touch is held for longer than the
// opts.MaxTouchDuration, then the re-calibration routine will be
// restarted
recalFlag<<3 |
byte(0)<<2 |
byte(0)<<1 |
byte(0)<<0)
if d.Debug {
log.Printf("cap1188: Config mask: %08b\n", config)
}
if err = d.regWrapper.WriteUint8(0x20, config); err != nil {
return fmt.Errorf("failed to set the device configuration - %s", err)
}
config2 := (
// Linked LED Transition controls
// default 0: The Linked LED Transition controls set the min duty
// cycle equal to the max duty cycle
byte(0)<<7 |
// Determines the ALERT# pin polarity and behavior.
// default 1: the ALERT# pin is active low and open drain.
byte(1)<<6 |
// Determines whether the device will reduce power consumption
// while waiting between conversion time completion and the end of
// the polling cycle.
// default 0: The device will always power down as much as possible
// during the time between the end of the last conversion and the
// end of the polling cycle.
byte(1)<<5 |
// Determines whether the LED Mirror Control register bits are
// linked to the LED Polarity bits. Setting this bit blocks the
// normal behavior which is to automatically set and clear the LED
// Mirror Control bits when the LED Polarity bits are set or
// cleared.
// default 0: When the LED Polarity controls are set, the
// corresponding LED Mirror control is automatically set. Likewise,
// when the LED Polarity controls are cleared, the corresponding
// LED Mirror control is also cleared.
byte(0)<<4 |
// Determines whether the Noise Status bits will show RF Noise as
// the only input source.
// default 0: The Noise Status registers will show both RF noise and
// low frequency EMI noise if either is detected on a capacitive
// touch sensor input.
byte(0)<<3 |
// Determines whether the RF noise filter is enabled.
// default 0: If RF noise is detected by the analog block, the
// delta count on the corresponding channel is set to 0. Note that
// this does not require that Noise Status bits be set.
byte(0)<<2 |
byte(0)<<1 |
// Controls the interrupt behavior when a release is detected on a button.
// when 0: An interrupt is generated when a press is detected and
// again when a release is detected and at the repeat rate (if enabled)
// when 1: An interrupt is generated when a press is detected and
// at the repeat rate but not when a release is detected.
intOnRel<<0)
if d.Debug {
log.Printf("cap1188: Config2 mask: %08b\n", config2)
}
if err = d.regWrapper.WriteUint8(0x44, config2); err != nil {
return fmt.Errorf("failed to set the device configuration 2 - %s", err)
}
return nil
}
// setBit sets a specific bit on a register
// TODO(mattetti): avoid reading before writing, keep states in memory
func (d *Dev) setBit(regID uint8, idx int) error {
v, err := d.regWrapper.ReadUint8(regID)
if err != nil {
return err
}
v |= (1 << uint8(idx))
return d.regWrapper.WriteUint8(regID, v)
}
// clearBit clears a specific bit on a register
// TODO(mattetti): avoid reading before writing, keep states in memory
func (d *Dev) clearBit(regID uint8, idx int) error {
v, err := d.regWrapper.ReadUint8(regID)
if err != nil {
return err
}
v &= ^(1 << uint8(idx))
return d.regWrapper.WriteUint8(regID, v)
}
func wrap(err error) error {
return fmt.Errorf("cap1188: %v", err)
}
var _ devices.Device = &Dev{}

@ -0,0 +1,144 @@
// Copyright 2017 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 cap1188
import "periph.io/x/periph/conn/gpio"
// SamplingTime determines the time to take a single sample
type SamplingTime uint8
// Possible sampling time values (written as 2 bits)
const (
S320us SamplingTime = 0
S640us SamplingTime = 1
// S1_28ms represents 1.28ms sampling time, which is the default.
S1_28ms SamplingTime = 2 // default
S2_56ms SamplingTime = 3
)
// AvgSampling set the number of samples per measurement that get
// averaged
type AvgSampling uint8
// possible average sampling values. (written as 3 bits)
const (
// Avg1 means that 1 sample is taken per measurement
Avg1 AvgSampling = iota // 0
Avg2 // 1
Avg4 // 2
Avg8 // 3 default
Avg16 // 4
Avg32 // 5
Avg64 // 6
Avg128 // 7
)
// CycleTime determines the overall cycle time for all measured channels during
// normal operation.
type CycleTime uint8
// possible cycle time values. (written as 2 bits)
const (
C35ms CycleTime = iota // 0
C70ms // default
C105ms
C140ms
)
// MaxDur is the maximum duration of a touch event before it triggers a
// recalibration.
type MaxDur uint8
// possible touch duration values. (written as 4 bits)
const (
MaxDur560ms MaxDur = iota
MaxDur840ms
MaxDur1120ms
MaxDur1400ms
MaxDur1680ms
MaxDur2240ms
MaxDur2800ms
MaxDur3360ms
MaxDur3920ms
MaxDur44800ms
MaxDur5600ms // default
MaxDur6720ms
MaxDur7840ms
MaxDur8906ms
MaxDur10080ms
MaxDur11200ms
)
// Opts is optional options to pass to the constructor.
//
// Address is only used on creation of an I²C-device. Its default value is 0x28.
// It can be set to other values (0x29, 0x2a, 0x2b, 0x2c) depending on the HW
// configuration of the ADDR_COMM pin. This has no effect with NewSPI()
type Opts struct {
// Address is the I2C slave address to use
Address uint16
// Debug turns on extra logging capabilities
Debug bool
// LinkedLEDs indicates if the LEDs should be activated automatically
// when their sensors detect a touch event.
LinkedLEDs bool
// MaxTouchDuration sets the touch duration threshold. It is possible that a
// “stuck button” occurs when something is placed on a button which causes a
// touch to be detected for a long period. By setting this value,
// a recalibration can be forced when a touch is held on a button for longer
// than the duration specified.
MaxTouchDuration MaxDur
// EnableRecalibration is used to force the recalibration if a touch event lasts
// longer than MaxTouchDuration.
EnableRecalibration bool
// AlertPin is the pin receiving the interrupt when a touch event is detected
// and optionally if a release event is detected.
AlertPin gpio.PinIn
// ResetPin is the pin used to reset the device.
ResetPin gpio.PinOut
// InterruptOnRelease indicates if the device should also trigger an
// interrupt on the AlertPin when a release event is detected.
InterruptOnRelease bool
// RetriggerOnHold forces a retrigger of the interrupt when a sensor is pressed
// for longer than MaxTouchDuration
RetriggerOnHold bool
// Averaging and Sampling Configuration Register
// SamplesPerMeasurement is the number of samples taken per measurement. All
// samples are taken consecutively on the same channel before the next
// channel is sampled and the result is averaged over the number of samples
// measured before updating the measured results.
// Available options: 1, 2, 4, 8 (default), 16, 32, 64, 128
SamplesPerMeasurement AvgSampling
// SamplingTime Determines the time to take a single sample as shown
SamplingTime SamplingTime
// CycleTime determines the overall cycle time for all measured channels
// during normal operation. All measured channels are sampled at the
// beginning of the cycle time. If additional time is remaining, then the
// device is placed into a lower power state for the remaining duration of
// the cycle.
CycleTime CycleTime
}
// DefaultOpts returns a pointer to a new Opts with the default option values.
func DefaultOpts() *Opts {
return &Opts{
LinkedLEDs: true,
MaxTouchDuration: MaxDur5600ms,
RetriggerOnHold: false,
EnableRecalibration: false,
InterruptOnRelease: false,
SamplesPerMeasurement: Avg1,
SamplingTime: S1_28ms,
CycleTime: C35ms,
}
}

@ -0,0 +1,348 @@
// Copyright 2017 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 cap1188_test
import (
"fmt"
"log"
"reflect"
"testing"
"time"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpioreg"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/i2c/i2creg"
"periph.io/x/periph/conn/i2c/i2ctest"
"periph.io/x/periph/experimental/devices/cap1188"
)
func Example() {
// Open the I²C bus to which the cap1188 is connected.
i2cBus, err := i2creg.Open("")
if err != nil {
log.Fatal(err)
}
defer i2cBus.Close()
// We will configure the cap1188 by setting some options, we can start by the defaults.
opts := cap1188.DefaultOpts()
// We need to set an alert ping that will let us know when a touch event
// occurs. The alert pin is the pin connected to the IRQ/interrupt pin.
alertPin := gpioreg.ByName("GPIO25")
if alertPin == nil {
log.Fatal("invalid alert GPIO pin number")
}
// We set the alert pin to monitor for interrupts
if err := alertPin.In(gpio.PullUp, gpio.BothEdges); err != nil {
log.Fatalf("Can't monitor the alert pin")
}
// Optionally but highly recommended, we can also set a reset pin to
// start/leave things in a clean state.
resetPin := gpioreg.ByName("GPIO21")
if resetPin == nil {
log.Fatal("invalid reset GPIO pin number")
}
opts.AlertPin = alertPin
opts.ResetPin = resetPin
// open the device so we can detect touch events
dev, err := cap1188.NewI2C(i2cBus, opts)
if err != nil {
log.Fatalf("couldn't open cap1188 - %s", err)
}
time.Sleep(200 * time.Millisecond)
fmt.Println("Monitoring for touch events")
maxTouches := 42 // stop the program after 42 touches
for maxTouches > 0 {
if alertPin.WaitForEdge(-1) {
maxTouches--
statuses, err := dev.InputStatus()
if err != nil {
fmt.Printf("Error reading inputs: %s\n", err)
continue
}
// print the status of each sensor
for i, st := range statuses {
fmt.Printf("#%d: %s\t", i, st)
}
fmt.Println()
// we need to clear the interrupt so it can be triggered again
if err := dev.ClearInterrupt(); err != nil {
fmt.Println(err, "while clearing the interrupt")
}
}
}
fmt.Print("\n")
}
func TestNewI2C(t *testing.T) {
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// chip ID
{Addr: 40, W: []byte{0xfd}, R: []byte{0x50}},
// clear interrupt
{Addr: 40, W: []byte{0x0}, R: []byte{0x0}},
{Addr: 40, W: []byte{0x0, 0x0}, R: nil},
// enable all inputs
{Addr: 40, W: []byte{0x21, 0xff}, R: nil},
// enable interrupts
{Addr: 40, W: []byte{0x27, 0xff}, R: nil},
// enable/disable repeats
{Addr: 40, W: []byte{0x28, 0xff}, R: nil},
// multitouch
{Addr: 40, W: []byte{0x2a, 0x4}, R: nil},
// sampling
{Addr: 40, W: []byte{0x24, 0x8}, R: nil},
// sensitivity
{Addr: 40, W: []byte{0x1f, 0x50}, R: nil},
// don't retrigger on hold
{Addr: 40, W: []byte{0x28, 0x0}, R: nil},
// config
{Addr: 40, W: []byte{0x20, 0x30}, R: nil},
// config 2
{Addr: 40, W: []byte{0x44, 0x61}, R: nil},
},
}
d, err := cap1188.NewI2C(&bus, &cap1188.Opts{Debug: true})
if err != nil {
t.Fatal(err)
}
if s := d.String(); s != "cap1188{playback(40)}" {
t.Fatal(s)
}
if err := d.Halt(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestDev_InputStatus(t *testing.T) {
tests := []struct {
name string
bus i2c.Bus
opts *cap1188.Opts
want []cap1188.TouchStatus
}{
{name: "all off",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
// status
{Addr: 40, W: []byte{0x3}, R: []byte{0x0}},
// deltas
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
// thresholds
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus},
},
{name: "all pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{0xff}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus},
},
{name: "first pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 7}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus},
},
{name: "second pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 6}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus},
},
{name: "third pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 5}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus},
},
{name: "forth pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 4}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus},
},
{name: "fifth pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 3}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus},
},
{name: "sixth pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 2}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus},
},
{name: "seventh pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 1}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus},
},
{name: "eighth pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 0}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus},
},
{name: "3 pressed",
bus: &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{(1 << 7) ^ (1 << 4) ^ (1 << 0)}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
},
opts: cap1188.DefaultOpts(),
want: []cap1188.TouchStatus{cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d, err := cap1188.NewI2C(tt.bus, tt.opts)
if err != nil {
t.Fatal(err)
}
got, err := d.InputStatus()
if err != nil {
t.Fatalf("Dev.InputStatus() error = %v", err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Dev.InputStatus() = %v, want %v", got, tt.want)
}
})
}
// test hold
t.Run("held touch sensors", func(t *testing.T) {
bus := &i2ctest.Playback{
Ops: append(setupPlaybackIO(), []i2ctest.IO{
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 7}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
// repeat call to get status (still pressed)
{Addr: 40, W: []byte{0x3}, R: []byte{1 << 7}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
// finall call
{Addr: 40, W: []byte{0x3}, R: []byte{0x0}},
{Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
{Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}...),
}
// set the recorded response to have the retrigger option on
bus.Ops[10] = i2ctest.IO{Addr: 40, W: []byte{0x28, 0xff}, R: nil}
opts := cap1188.DefaultOpts()
// following option needs to be true so we can get the held status
opts.RetriggerOnHold = true
d, err := cap1188.NewI2C(bus, opts)
if err != nil {
t.Fatal(err)
}
// first check
got, err := d.InputStatus()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, []cap1188.TouchStatus{cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}) {
t.Fatalf("expected to have the first sensor touched but instead got %v", got)
}
// 2nd check
got, err = d.InputStatus()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, []cap1188.TouchStatus{cap1188.HeldStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}) {
t.Fatalf("expected to have the first sensor touched but instead got %v", got)
}
})
}
func setupPlaybackIO() []i2ctest.IO {
return []i2ctest.IO{
// chip ID
{Addr: 40, W: []byte{0xfd}, R: []byte{0x50}},
// clear interrupt
{Addr: 40, W: []byte{0x0}, R: []byte{0x0}},
{Addr: 40, W: []byte{0x0, 0x0}, R: nil},
// enable all inputs
{Addr: 40, W: []byte{0x21, 0xff}, R: nil},
// enable interrupts
{Addr: 40, W: []byte{0x27, 0xff}, R: nil},
// enable/disable repeats
{Addr: 40, W: []byte{0x28, 0xff}, R: nil},
// multitouch
{Addr: 40, W: []byte{0x2a, 0x4}, R: nil},
// sampling
{Addr: 40, W: []byte{0x24, 0x8}, R: nil},
// sensitivity
{Addr: 40, W: []byte{0x1f, 0x50}, R: nil},
// linked leds
{Addr: 40, W: []byte{0x72, 0xff}, R: nil},
// don't retrigger on hold
{Addr: 40, W: []byte{0x28, 0x0}, R: nil},
// config
{Addr: 40, W: []byte{0x20, 0x30}, R: nil},
// config 2
{Addr: 40, W: []byte{0x44, 0x61}, R: nil},
}
}
Loading…
Cancel
Save