diff --git a/drivers/i2c/adxl345_driver.go b/drivers/i2c/adxl345_driver.go index 82b3ccc7..dae15904 100644 --- a/drivers/i2c/adxl345_driver.go +++ b/drivers/i2c/adxl345_driver.go @@ -2,69 +2,79 @@ package i2c import ( "encoding/binary" + "log" "github.com/pkg/errors" - "gobot.io/x/gobot" ) -const ADXL345AddressLow = 0x53 -const ADXL345AddressHigh = 0x1D +const adxl345Debug = false + +// ADXL345 supports 2 addresses, which can be changed by the address pin, there is no internal pull-up/down resistor! +// pin to GND: 0x53, pin to VDD: 0x1D +const ( + ADXL345AddressPullUp = 0x1D // can be used by WithAddress() + adxl345DefaultAddress = 0x53 +) + +type ADXL345RateConfig uint8 +type ADXL345FsRangeConfig uint8 const ( - // Data rate - ADXL345_RATE_3200HZ = 0x0F // 3200 Hz - ADXL345_RATE_1600HZ = 0x0E // 1600 Hz - ADXL345_RATE_800HZ = 0x0D // 800 Hz - ADXL345_RATE_400HZ = 0x0C // 400 Hz - ADXL345_RATE_200HZ = 0x0B // 200 Hz - ADXL345_RATE_100HZ = 0x0A // 100 Hz - ADXL345_RATE_50HZ = 0x09 // 50 Hz - ADXL345_RATE_25HZ = 0x08 // 25 Hz - ADXL345_RATE_12_5HZ = 0x07 // 12.5 Hz - ADXL345_RATE_6_25HZ = 0x06 // 6.25 Hz - ADXL345_RATE_3_13HZ = 0x05 // 3.13 Hz - ADXL345_RATE_1_56HZ = 0x04 // 1.56 Hz - ADXL345_RATE_0_78HZ = 0x03 // 0.78 Hz - ADXL345_RATE_0_39HZ = 0x02 // 0.39 Hz - ADXL345_RATE_0_20HZ = 0x01 // 0.20 Hz - ADXL345_RATE_0_10HZ = 0x00 // 0.10 Hz + // registers are named according to the datasheet + adxl345Reg_DEVID = 0x00 // R, 11100101, Device ID + adxl345Reg_THRESH_TAP = 0x1D // R/W, 00000000, Tap threshold + adxl345Reg_OFSX = 0x1E // R/W, 00000000, X-axis offset + adxl345Reg_OFSY = 0x1F // R/W, 00000000, Y-axis offset + adxl345Reg_OFSZ = 0x20 // R/W, 00000000, Z-axis offset + adxl345Reg_DUR = 0x21 // R/W, 00000000, Tap duration + adxl345Reg_LATENT = 0x22 // R/W, 00000000, Tap latency + adxl345Reg_WINDOW = 0x23 // R/W, 00000000, Tap window + adxl345Reg_THRESH_ACT = 0x24 // R/W, 00000000, Activity threshold + adxl345Reg_THRESH_INACT = 0x25 // R/W, 00000000, Inactivity threshold + adxl345Reg_TIME_INACT = 0x26 // R/W, 00000000, Inactivity time + adxl345Reg_ACT_INACT_CTL = 0x27 // R/W, 00000000, Axis enable control for activity and inactivity detection + adxl345Reg_THRESH_FF = 0x28 // R/W, 00000000, Free-fall threshold + adxl345Reg_TIME_FF = 0x29 // R/W, 00000000, Free-fall time + adxl345Reg_TAP_AXES = 0x2A // R/W, 00000000, Axis control for single tap/double tap + adxl345Reg_ACT_TAP_STATUS = 0x2B // R, 00000000, Source of single tap/double tap + adxl345Reg_BW_RATE = 0x2C // R/W, 00001010, Data rate and power mode control + adxl345Reg_POWER_CTL = 0x2D // R/W, 00000000, Power-saving features control + adxl345Reg_INT_ENABLE = 0x2E // R/W, 00000000, Interrupt enable control + adxl345Reg_INT_MAP = 0x2F // R/W, 00000000, Interrupt mapping control + adxl345Reg_INT_SOUCE = 0x30 // R, 00000010, Source of interrupts + adxl345Reg_DATA_FORMAT = 0x31 // R/W, 00000000, Data format control (FS range, justify, full resolution) + adxl345Reg_DATAX0 = 0x32 // R, 00000000, X-Axis Data 0 (LSByte) + adxl345Reg_DATAX1 = 0x33 // R, 00000000, X-Axis Data 1 (MSByte) + adxl345Reg_DATAY0 = 0x34 // R, 00000000, Y-Axis Data 0 + adxl345Reg_DATAY1 = 0x35 // R, 00000000, Y-Axis Data 1 + adxl345Reg_DATAZ0 = 0x36 // R, 00000000, Z-Axis Data 0 + adxl345Reg_DATAZ1 = 0x37 // R, 00000000, Z-Axis Data 1 + adxl345Reg_FIFO_CTL = 0x38 // R/W, 00000000, FIFO control + adxl345Reg_FIFO_STATUS = 0x39 // R, 00000000, FIFO status - // Data range - ADXL345_RANGE_2G = 0x00 // +-2 g - ADXL345_RANGE_4G = 0x01 // +-4 g - ADXL345_RANGE_8G = 0x02 // +-8 g - ADXL345_RANGE_16G = 0x03 // +-16 g) + adxl345Rate_LowPowerBit = 0x10 // set the device to low power, but increase the noise by ~2.5x - ADXL345_REG_DEVID = 0x00 // R, 11100101, Device ID - ADXL345_REG_THRESH_TAP = 0x1D // R/W, 00000000, Tap threshold - ADXL345_REG_OFSX = 0x1E // R/W, 00000000, X-axis offset - ADXL345_REG_OFSY = 0x1F // R/W, 00000000, Y-axis offset - ADXL345_REG_OFSZ = 0x20 // R/W, 00000000, Z-axis offset - ADXL345_REG_DUR = 0x21 // R/W, 00000000, Tap duration - ADXL345_REG_LATENT = 0x22 // R/W, 00000000, Tap latency - ADXL345_REG_WINDOW = 0x23 // R/W, 00000000, Tap window - ADXL345_REG_THRESH_ACT = 0x24 // R/W, 00000000, Activity threshold - ADXL345_REG_THRESH_INACT = 0x25 // R/W, 00000000, Inactivity threshold - ADXL345_REG_TIME_INACT = 0x26 // R/W, 00000000, Inactivity time - ADXL345_REG_ACT_INACT_CTL = 0x27 // R/W, 00000000, Axis enable control for activity and inactiv ity detection - ADXL345_REG_THRESH_FF = 0x28 // R/W, 00000000, Free-fall threshold - ADXL345_REG_TIME_FF = 0x29 // R/W, 00000000, Free-fall time - ADXL345_REG_TAP_AXES = 0x2A // R/W, 00000000, Axis control for single tap/double tap - ADXL345_REG_ACT_TAP_STATUS = 0x2B // R, 00000000, Source of single tap/double tap - ADXL345_REG_BW_RATE = 0x2C // R/W, 00001010, Data rate and power mode control - ADXL345_REG_POWER_CTL = 0x2D // R/W, 00000000, Power-saving features control - ADXL345_REG_INT_ENABLE = 0x2E // R/W, 00000000, Interrupt enable control - ADXL345_REG_INT_MAP = 0x2F // R/W, 00000000, Interrupt mapping control - ADXL345_REG_INT_SOUCE = 0x30 // R, 00000010, Source of interrupts - ADXL345_REG_DATA_FORMAT = 0x31 // R/W, 00000000, Data format control - ADXL345_REG_DATAX0 = 0x32 // R, 00000000, X-Axis Data 0 - ADXL345_REG_DATAX1 = 0x33 // R, 00000000, X-Axis Data 1 - ADXL345_REG_DATAY0 = 0x34 // R, 00000000, Y-Axis Data 0 - ADXL345_REG_DATAY1 = 0x35 // R, 00000000, Y-Axis Data 1 - ADXL345_REG_DATAZ0 = 0x36 // R, 00000000, Z-Axis Data 0 - ADXL345_REG_DATAZ1 = 0x37 // R, 00000000, Z-Axis Data 1 - ADXL345_REG_FIFO_CTL = 0x38 // R/W, 00000000, FIFO control - ADXL345_REG_FIFO_STATUS = 0x39 // R, 00000000, FIFO status + ADXL345Rate_100mHZ ADXL345RateConfig = 0x00 // 0.10 Hz + ADXL345Rate_200mHZ ADXL345RateConfig = 0x01 // 0.20 Hz + ADXL345Rate_390mHZ ADXL345RateConfig = 0x02 // 0.39 Hz + ADXL345Rate_780mHZ ADXL345RateConfig = 0x03 // 0.78 Hz + ADXL345Rate_1560mHZ ADXL345RateConfig = 0x04 // 1.56 Hz + ADXL345Rate_3130mHZ ADXL345RateConfig = 0x05 // 3.13 Hz + ADXL345Rate_6250mHZ ADXL345RateConfig = 0x06 // 6.25 Hz + ADXL345Rate_12500mHZ ADXL345RateConfig = 0x07 // 12.5 Hz + ADXL345Rate_25HZ ADXL345RateConfig = 0x08 // 25 Hz + ADXL345Rate_50HZ ADXL345RateConfig = 0x09 // 50 Hz + ADXL345Rate_100HZ ADXL345RateConfig = 0x0A // 100 Hz + ADXL345Rate_200HZ ADXL345RateConfig = 0x0B // 200 Hz + ADXL345Rate_400HZ ADXL345RateConfig = 0x0C // 400 Hz + ADXL345Rate_800HZ ADXL345RateConfig = 0x0D // 800 Hz + ADXL345Rate_1600HZ ADXL345RateConfig = 0x0E // 1600 Hz + ADXL345Rate_3200HZ ADXL345RateConfig = 0x0F // 3200 Hz + + ADXL345FsRange_2G ADXL345FsRangeConfig = 0x00 // +-2 g + ADXL345FsRange_4G ADXL345FsRangeConfig = 0x01 // +-4 g + ADXL345FsRange_8G ADXL345FsRangeConfig = 0x02 // +-8 g + ADXL345FsRange_16G ADXL345FsRangeConfig = 0x03 // +-16 g) ) // ADXL345Driver is the gobot driver for the digital accelerometer ADXL345 @@ -74,18 +84,10 @@ const ( // // Ported from the Arduino driver https://github.com/jakalada/Arduino-ADXL345 type ADXL345Driver struct { - name string - connector Connector - connection Connection - + *Driver powerCtl adxl345PowerCtl dataFormat adxl345DataFormat bwRate adxl345BwRate - - x, y, z float64 - rawX, rawY, rawZ int16 - - Config } // Internal structure for the power configuration @@ -99,194 +101,189 @@ type adxl345PowerCtl struct { // Internal structure for the sensor's data format configuration type adxl345DataFormat struct { - selfTest uint8 - spi uint8 - intInvert uint8 - fullRes uint8 - justify uint8 - sensorRange uint8 + selfTest uint8 + spi uint8 + intInvert uint8 + fullRes uint8 + justify uint8 + fullScaleRange ADXL345FsRangeConfig } // Internal structure for the sampling rate configuration type adxl345BwRate struct { - lowPower uint8 - rate uint8 + lowPower bool + rate ADXL345RateConfig } // NewADXL345Driver creates a new driver with specified i2c interface // Params: -// conn Connector - the Adaptor to use with this Driver +// c Connector - the Adaptor to use with this Driver // // Optional params: // i2c.WithBus(int): bus to use with this driver // i2c.WithAddress(int): address to use with this driver // -func NewADXL345Driver(a Connector, options ...func(Config)) *ADXL345Driver { - m := &ADXL345Driver{ - name: gobot.DefaultName("ADXL345"), - connector: a, +func NewADXL345Driver(c Connector, options ...func(Config)) *ADXL345Driver { + d := &ADXL345Driver{ + Driver: NewDriver(c, "ADXL345", adxl345DefaultAddress), powerCtl: adxl345PowerCtl{ measure: 1, }, dataFormat: adxl345DataFormat{ - sensorRange: ADXL345_RANGE_2G, + fullScaleRange: ADXL345FsRange_2G, }, bwRate: adxl345BwRate{ - lowPower: 1, - rate: ADXL345_RATE_100HZ, + lowPower: true, + rate: ADXL345Rate_100HZ, }, - Config: NewConfig(), } + d.afterStart = d.initialize + d.beforeHalt = d.shutdown for _, option := range options { - option(m) + option(d) } // TODO: add commands for API - return m + return d } -// Name returns the Name for the Driver -func (h *ADXL345Driver) Name() string { return h.name } +// WithADXL345LowPowerMode option modifies the low power mode. +func WithADXL345LowPowerMode(val bool) func(Config) { + return func(c Config) { + if d, ok := c.(*ADXL345Driver); ok { + d.bwRate.lowPower = val + } else if adxl345Debug { + log.Printf("Trying to modify low power mode for non-ADXL345Driver %v", c) + } + } +} -// SetName sets the Name for the Driver -func (h *ADXL345Driver) SetName(n string) { h.name = n } +// WithADXL345DataOutputRate option sets the data output rate. +// Valid settings are of type "ADXL345RateConfig" +func WithADXL345DataOutputRate(val ADXL345RateConfig) func(Config) { + return func(c Config) { + if d, ok := c.(*ADXL345Driver); ok { + d.bwRate.rate = val + } else if adxl345Debug { + log.Printf("Trying to set data output rate for non-ADXL345Driver %v", c) + } + } +} -// Connection returns the connection for the Driver -func (h *ADXL345Driver) Connection() gobot.Connection { return h.connector.(gobot.Connection) } +// WithADXL345FullScaleRange option sets the full scale range. +// Valid settings are of type "ADXL345FsRangeConfig" +func WithADXL345FullScaleRange(val ADXL345FsRangeConfig) func(Config) { + return func(c Config) { + if d, ok := c.(*ADXL345Driver); ok { + d.dataFormat.fullScaleRange = val + } else if adxl345Debug { + log.Printf("Trying to set full scale range for non-ADXL345Driver %v", c) + } + } +} -// Start initialized the adxl345 -func (h *ADXL345Driver) Start() (err error) { - bus := h.GetBusOrDefault(h.connector.GetDefaultBus()) - address := h.GetAddressOrDefault(ADXL345AddressLow) +// UseLowPower change the current rate of the sensor +func (d *ADXL345Driver) UseLowPower(lowPower bool) (err error) { + d.mutex.Lock() + defer d.mutex.Unlock() - h.connection, err = h.connector.GetConnection(address, bus) + d.bwRate.lowPower = lowPower + if err := d.connection.WriteByteData(adxl345Reg_BW_RATE, d.bwRate.toByte()); err != nil { + return err + } + return +} + +// SetRate change the current rate of the sensor immediately +func (d *ADXL345Driver) SetRate(rate ADXL345RateConfig) (err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + + d.bwRate.rate = rate + if err := d.connection.WriteByteData(adxl345Reg_BW_RATE, d.bwRate.toByte()); err != nil { + return err + } + return +} + +// SetRange change the current range of the sensor immediately +func (d *ADXL345Driver) SetRange(fullScaleRange ADXL345FsRangeConfig) (err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + + d.dataFormat.fullScaleRange = fullScaleRange + if err := d.connection.WriteByteData(adxl345Reg_DATA_FORMAT, d.dataFormat.toByte()); err != nil { + return err + } + return +} + +// XYZ returns the adjusted x, y and z axis, unit [g] +func (d *ADXL345Driver) XYZ() (float64, float64, float64, error) { + d.mutex.Lock() + defer d.mutex.Unlock() + + xr, yr, zr, err := d.readRawData() if err != nil { - return err + return 0, 0, 0, err } - if _, err := h.connection.Write([]byte{ADXL345_REG_BW_RATE, h.bwRate.toByte()}); err != nil { - return err - } - - if _, err := h.connection.Write([]byte{ADXL345_REG_POWER_CTL, h.powerCtl.toByte()}); err != nil { - return err - } - - if _, err := h.connection.Write([]byte{ADXL345_REG_DATA_FORMAT, h.dataFormat.toByte()}); err != nil { - return err - } - - return + return d.dataFormat.convertToG(xr), d.dataFormat.convertToG(yr), d.dataFormat.convertToG(zr), nil } -// Stop adxl345 -func (h *ADXL345Driver) Stop() (err error) { - h.powerCtl.measure = 0 - if h.connection == nil { - return errors.New("connection not available") - } - if _, err := h.connection.Write([]byte{ADXL345_REG_POWER_CTL, h.powerCtl.toByte()}); err != nil { - return err - } +// XYZ returns the raw x,y and z axis +func (d *ADXL345Driver) RawXYZ() (int16, int16, int16, error) { + d.mutex.Lock() + defer d.mutex.Unlock() - return + return d.readRawData() } -// Halt returns true if devices is halted successfully -func (h *ADXL345Driver) Halt() (err error) { - h.Stop() - return -} - -// XYZ returns the adjusted x, y and z axis from the adxl345 -func (h *ADXL345Driver) XYZ() (float64, float64, float64, error) { - err := h.update() - return h.x, h.y, h.z, err -} - -// XYZ returns the raw x,y and z axis from the adxl345 -func (h *ADXL345Driver) RawXYZ() (int16, int16, int16, error) { - err := h.update() - return h.rawX, h.rawY, h.rawZ, err -} - -// update the cached values for the axis to avoid errors if the connection is not available (polling too frequently) -func (h *ADXL345Driver) update() (err error) { - - if h.connection == nil { - return errors.New("connection not available") - } - - h.connection.Write([]byte{ADXL345_REG_DATAX0}) +func (d *ADXL345Driver) readRawData() (int16, int16, int16, error) { buf := []byte{0, 0, 0, 0, 0, 0} - - _, err = h.connection.Read(buf) - if err != nil { - return + if err := d.connection.ReadBlockData(adxl345Reg_DATAX0, buf); err != nil { + return 0, 0, 0, err } - h.rawX = int16(binary.LittleEndian.Uint16(buf[0:2])) - h.rawY = int16(binary.LittleEndian.Uint16(buf[2:4])) - h.rawZ = int16(binary.LittleEndian.Uint16(buf[4:6])) - - h.x = h.dataFormat.ConvertToSI(h.rawX) - h.y = h.dataFormat.ConvertToSI(h.rawY) - h.z = h.dataFormat.ConvertToSI(h.rawZ) - - return + rx := int16(binary.LittleEndian.Uint16(buf[0:2])) + ry := int16(binary.LittleEndian.Uint16(buf[2:4])) + rz := int16(binary.LittleEndian.Uint16(buf[4:6])) + return rx, ry, rz, nil } -// SetRate change the current rate of the sensor -func (h *ADXL345Driver) UseLowPower(power bool) (err error) { - if power { - h.bwRate.lowPower = 1 - } else { - h.bwRate.lowPower = 0 - } - if _, err := h.connection.Write([]byte{ADXL345_REG_BW_RATE, h.bwRate.toByte()}); err != nil { +func (d *ADXL345Driver) initialize() error { + if err := d.connection.WriteByteData(adxl345Reg_BW_RATE, d.bwRate.toByte()); err != nil { return err } - return -} - -// SetRate change the current rate of the sensor -func (h *ADXL345Driver) SetRate(rate byte) (err error) { - if rate <= ADXL345_RATE_3200HZ { - return errors.New("not a valid rate") - } - h.bwRate.rate = rate & 0x0F - if _, err := h.connection.Write([]byte{ADXL345_REG_BW_RATE, h.bwRate.toByte()}); err != nil { + if err := d.connection.WriteByteData(adxl345Reg_POWER_CTL, d.powerCtl.toByte()); err != nil { return err } - return -} - -// SetRange change the current range of the sensor -func (h *ADXL345Driver) SetRange(sensorRange byte) (err error) { - if sensorRange != ADXL345_RANGE_2G && - sensorRange != ADXL345_RANGE_4G && - sensorRange != ADXL345_RANGE_8G && - sensorRange != ADXL345_RANGE_16G { - return errors.New("not a valid range") - } - h.dataFormat.sensorRange = sensorRange & 0x03 - if _, err := h.connection.Write([]byte{ADXL345_REG_DATA_FORMAT, h.dataFormat.toByte()}); err != nil { + if err := d.connection.WriteByteData(adxl345Reg_DATA_FORMAT, d.dataFormat.toByte()); err != nil { return err } - return + + return nil } -// ConvertToSI adjusts the raw values from the adxl345 with the range configuration -func (d *adxl345DataFormat) ConvertToSI(rawValue int16) float64 { - switch d.sensorRange { - case ADXL345_RANGE_2G: +func (d *ADXL345Driver) shutdown() error { + d.powerCtl.measure = 0 + if d.connection == nil { + return errors.New("connection not available") + } + return d.connection.WriteByteData(adxl345Reg_POWER_CTL, d.powerCtl.toByte()) +} + +// convertToG converts the given raw value by range configuration to the unit [g] +func (d *adxl345DataFormat) convertToG(rawValue int16) float64 { + switch d.fullScaleRange { + case ADXL345FsRange_2G: return float64(rawValue) * 2 / 512 - case ADXL345_RANGE_4G: + case ADXL345FsRange_4G: return float64(rawValue) * 4 / 512 - case ADXL345_RANGE_8G: + case ADXL345FsRange_8G: return float64(rawValue) * 8 / 512 - case ADXL345_RANGE_16G: + case ADXL345FsRange_16G: return float64(rawValue) * 16 / 512 default: return 0 @@ -294,35 +291,29 @@ func (d *adxl345DataFormat) ConvertToSI(rawValue int16) float64 { } // toByte returns a byte from the powerCtl configuration -func (p *adxl345PowerCtl) toByte() (bits uint8) { - bits = 0x00 - bits = bits | (p.link << 5) - bits = bits | (p.autoSleep << 4) - bits = bits | (p.measure << 3) +func (p *adxl345PowerCtl) toByte() uint8 { + bits := p.wakeUp bits = bits | (p.sleep << 2) - bits = bits | p.wakeUp - - return bits + bits = bits | (p.measure << 3) + bits = bits | (p.autoSleep << 4) + return bits | (p.link << 5) } // toByte returns a byte from the dataFormat configuration -func (d *adxl345DataFormat) toByte() (bits uint8) { - bits = 0x00 - bits = bits | (d.selfTest << 7) - bits = bits | (d.spi << 6) - bits = bits | (d.intInvert << 5) - bits = bits | (d.fullRes << 3) +func (d *adxl345DataFormat) toByte() uint8 { + bits := uint8(d.fullScaleRange) bits = bits | (d.justify << 2) - bits = bits | d.sensorRange - - return bits + bits = bits | (d.fullRes << 3) + bits = bits | (d.intInvert << 5) + bits = bits | (d.spi << 6) + return bits | (d.selfTest << 7) } // toByte returns a byte from the bwRate configuration -func (b *adxl345BwRate) toByte() (bits uint8) { - bits = 0x00 - bits = bits | (b.lowPower << 4) - bits = bits | b.rate - +func (b *adxl345BwRate) toByte() uint8 { + bits := uint8(b.rate) + if b.lowPower { + bits = bits | adxl345Rate_LowPowerBit + } return bits } diff --git a/drivers/i2c/adxl345_driver_test.go b/drivers/i2c/adxl345_driver_test.go index b9ae464d..ef2d890b 100644 --- a/drivers/i2c/adxl345_driver_test.go +++ b/drivers/i2c/adxl345_driver_test.go @@ -1,7 +1,6 @@ package i2c import ( - "bytes" "errors" "strings" "testing" @@ -10,144 +9,314 @@ import ( "gobot.io/x/gobot/gobottest" ) +// this ensures that the implementation is based on i2c.Driver, which implements the gobot.Driver +// and tests all implementations, so no further tests needed here for gobot.Driver interface var _ gobot.Driver = (*ADXL345Driver)(nil) -// --------- HELPERS -func initTestADXL345Driver() (driver *ADXL345Driver) { - driver, _ = initTestADXL345DriverWithStubbedAdaptor() - return +func initTestADXL345WithStubbedAdaptor() (*ADXL345Driver, *i2cTestAdaptor) { + a := newI2cTestAdaptor() + d := NewADXL345Driver(a) + return d, a } -func initTestADXL345DriverWithStubbedAdaptor() (*ADXL345Driver, *i2cTestAdaptor) { - adaptor := newI2cTestAdaptor() - return NewADXL345Driver(adaptor), adaptor -} - -// --------- TESTS - func TestNewADXL345Driver(t *testing.T) { - // Does it return a pointer to an instance of ADXL345Driver? - var mma interface{} = NewADXL345Driver(newI2cTestAdaptor()) - _, ok := mma.(*ADXL345Driver) + var di interface{} = NewADXL345Driver(newI2cTestAdaptor()) + d, ok := di.(*ADXL345Driver) if !ok { t.Errorf("NewADXL345Driver() should have returned a *ADXL345Driver") } + gobottest.Refute(t, d.Driver, nil) + gobottest.Assert(t, strings.HasPrefix(d.Name(), "ADXL345"), true) + gobottest.Assert(t, d.powerCtl.measure, uint8(1)) + gobottest.Assert(t, d.dataFormat.fullScaleRange, ADXL345FsRangeConfig(0x00)) + gobottest.Assert(t, d.bwRate.rate, ADXL345RateConfig(0x0A)) + gobottest.Assert(t, d.bwRate.lowPower, true) } -// Methods -func TestADXL345Driver(t *testing.T) { - mma := initTestADXL345Driver() - - gobottest.Refute(t, mma.Connection(), nil) - gobottest.Assert(t, strings.HasPrefix(mma.Name(), "ADXL345"), true) -} - -func TestADXL345DriverSetName(t *testing.T) { - d := initTestADXL345Driver() - d.SetName("TESTME") - gobottest.Assert(t, d.Name(), "TESTME") -} - -func TestADXL345DriverOptions(t *testing.T) { - d := NewADXL345Driver(newI2cTestAdaptor(), WithBus(2)) +func TestADXL345Options(t *testing.T) { + // This is a general test, that options are applied in constructor by using the common WithBus() option and + // least one of this driver. Further tests for options can also be done by call of "WithOption(val)(d)". + d := NewADXL345Driver(newI2cTestAdaptor(), WithBus(2), WithADXL345LowPowerMode(false)) gobottest.Assert(t, d.GetBusOrDefault(1), 2) + gobottest.Assert(t, d.bwRate.lowPower, false) } -func TestADXL345DriverStart(t *testing.T) { - d := initTestADXL345Driver() - gobottest.Assert(t, d.Start(), nil) +func TestADXL345WithADXL345DataOutputRate(t *testing.T) { + // arrange + d, a := initTestADXL345WithStubbedAdaptor() + a.written = []byte{} // reset writes of former test + const ( + setVal = ADXL345RateConfig(0x0E) // 1.6kHz + ) + // act + WithADXL345DataOutputRate(setVal)(d) + // assert + gobottest.Assert(t, d.bwRate.rate, setVal) + gobottest.Assert(t, len(a.written), 0) } -func TestADXL345StartConnectError(t *testing.T) { - d, adaptor := initTestADXL345DriverWithStubbedAdaptor() - adaptor.Testi2cConnectErr(true) - gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection")) +func TestADXL345WithADXL345FullScaleRange(t *testing.T) { + // arrange + d, a := initTestADXL345WithStubbedAdaptor() + a.written = []byte{} // reset writes of former test + const ( + setVal = ADXL345FsRangeConfig(0x02) // +-8 g + ) + // act + WithADXL345FullScaleRange(setVal)(d) + // assert + gobottest.Assert(t, d.dataFormat.fullScaleRange, setVal) + gobottest.Assert(t, len(a.written), 0) } -func TestADXL345DriverStartWriteError(t *testing.T) { - mma, adaptor := initTestADXL345DriverWithStubbedAdaptor() - adaptor.i2cWriteImpl = func([]byte) (int, error) { - return 0, errors.New("write error") +func TestADXL345UseLowPower(t *testing.T) { + // sequence to set low power: + // * set value in data rate structure + // * write the data rate register (0x2C) + d, a := initTestADXL345WithStubbedAdaptor() + d.Start() + a.written = []byte{} // reset writes of former test + setVal := !d.bwRate.lowPower + const ( + wantReg = uint8(0x2C) + wantVal = uint8(0x0A) // only 100 Hz left over + ) + // act + err := d.UseLowPower(setVal) + // assert + gobottest.Assert(t, err, nil) + gobottest.Assert(t, d.bwRate.lowPower, setVal) + gobottest.Assert(t, len(a.written), 2) + gobottest.Assert(t, a.written[0], wantReg) + gobottest.Assert(t, a.written[1], wantVal) +} + +func TestADXL345SetRate(t *testing.T) { + // sequence to set rate: + // * set value in data rate structure + // * write the data rate register (0x2C) + d, a := initTestADXL345WithStubbedAdaptor() + d.Start() + a.written = []byte{} // reset writes of former test + const ( + setVal = ADXL345RateConfig(0x0F) // 3.2kHz + wantReg = uint8(0x2C) + wantVal = uint8(0x1F) // also low power bit + ) + // act + err := d.SetRate(setVal) + // assert + gobottest.Assert(t, err, nil) + gobottest.Assert(t, d.bwRate.rate, setVal) + gobottest.Assert(t, len(a.written), 2) + gobottest.Assert(t, a.written[0], wantReg) + gobottest.Assert(t, a.written[1], wantVal) +} + +func TestADXL345SetRange(t *testing.T) { + // sequence to set range: + // * set value in data format structure + // * write the data format register (0x31) + d, a := initTestADXL345WithStubbedAdaptor() + d.Start() + a.written = []byte{} // reset writes of former test + const ( + setVal = ADXL345FsRangeConfig(0x03) // +/- 16 g + wantReg = uint8(0x31) + wantVal = uint8(0x03) + ) + // act + err := d.SetRange(setVal) + // assert + gobottest.Assert(t, err, nil) + gobottest.Assert(t, d.dataFormat.fullScaleRange, setVal) + gobottest.Assert(t, len(a.written), 2) + gobottest.Assert(t, a.written[0], wantReg) + gobottest.Assert(t, a.written[1], wantVal) +} + +func TestADXL345RawXYZ(t *testing.T) { + // sequence to read: + // * prepare read, see test of initialize() + // * read data output registers (0x32, 3 x 16 bit, LSByte first) + // * apply two's complement converter + // + // arrange + var tests = map[string]struct { + inputX []uint8 + inputY []uint8 + inputZ []uint8 + wantX int16 + wantY int16 + wantZ int16 + }{ + "+FS_0_-FS": { + inputX: []uint8{0xFF, 0x07}, + inputY: []uint8{0x00, 0x00}, + inputZ: []uint8{0x00, 0xF8}, + wantX: (1<<11 - 1), + wantY: 0, + wantZ: -(1 << 11), + }, + "-4096_-1_+1": { + inputX: []uint8{0x00, 0xF0}, + inputY: []uint8{0xFF, 0xFF}, + inputZ: []uint8{0x01, 0x00}, + wantX: -4096, + wantY: -1, + wantZ: 1, + }, } - gobottest.Assert(t, mma.Start(), errors.New("write error")) -} - -func TestADXL345DriverHalt(t *testing.T) { - d := initTestADXL345Driver() - gobottest.Assert(t, d.Start(), nil) - gobottest.Assert(t, d.Halt(), nil) -} - -func TestADXL345DriverNullXYZ(t *testing.T) { - d, _ := initTestADXL345DriverWithStubbedAdaptor() + d, a := initTestADXL345WithStubbedAdaptor() d.Start() - x, y, z, _ := d.XYZ() - gobottest.Assert(t, x, 0.0) - gobottest.Assert(t, y, 0.0) - gobottest.Assert(t, z, 0.0) -} - -func TestADXL345DriverXYZ(t *testing.T) { - d, adaptor := initTestADXL345DriverWithStubbedAdaptor() - d.Start() - - adaptor.i2cReadImpl = func(b []byte) (int, error) { - buf := new(bytes.Buffer) - buf.Write([]byte{218, 0, 251, 255, 100, 0}) - copy(b, buf.Bytes()) - return buf.Len(), nil + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + a.written = []byte{} // reset writes of former test and start + // arrange reads + returnRead := append(append(tc.inputX, tc.inputY...), tc.inputZ...) + numCallsRead := 0 + a.i2cReadImpl = func(b []byte) (int, error) { + numCallsRead++ + copy(b, returnRead) + return len(b), nil + } + // act + gotX, gotY, gotZ, err := d.RawXYZ() + // assert + gobottest.Assert(t, err, nil) + gobottest.Assert(t, gotX, tc.wantX) + gobottest.Assert(t, gotY, tc.wantY) + gobottest.Assert(t, gotZ, tc.wantZ) + gobottest.Assert(t, numCallsRead, 1) + gobottest.Assert(t, len(a.written), 1) + gobottest.Assert(t, a.written[0], uint8(0x32)) + }) } - - x, y, z, _ := d.XYZ() - gobottest.Assert(t, x, 0.8515625) - gobottest.Assert(t, y, -0.01953125) - gobottest.Assert(t, z, 0.390625) } -func TestADXL345DriverXYZError(t *testing.T) { - d, adaptor := initTestADXL345DriverWithStubbedAdaptor() +func TestADXL345RawXYZError(t *testing.T) { + // arrange + d, a := initTestADXL345WithStubbedAdaptor() d.Start() - - adaptor.i2cReadImpl = func(b []byte) (int, error) { + a.i2cReadImpl = func(b []byte) (int, error) { return 0, errors.New("read error") } - - _, _, _, err := d.XYZ() - gobottest.Assert(t, err, errors.New("read error")) -} - - -func TestADXL345DriverRawXYZ(t *testing.T) { - d, adaptor := initTestADXL345DriverWithStubbedAdaptor() - d.Start() - - adaptor.i2cReadImpl = func(b []byte) (int, error) { - buf := new(bytes.Buffer) - buf.Write([]byte{218, 0, 251, 255, 100, 0}) - copy(b, buf.Bytes()) - return buf.Len(), nil - } - - x, y, z, _ := d.RawXYZ() - gobottest.Assert(t, int(x), 218) - gobottest.Assert(t, int(y), -5) - gobottest.Assert(t, int(z), 100) -} - -func TestADXL345DriverRawXYZError(t *testing.T) { - d, adaptor := initTestADXL345DriverWithStubbedAdaptor() - d.Start() - - adaptor.i2cReadImpl = func(b []byte) (int, error) { - return 0, errors.New("read error") - } - + // act _, _, _, err := d.RawXYZ() + // assert gobottest.Assert(t, err, errors.New("read error")) } -func TestADXL345DriverSetRange(t *testing.T) { - d := initTestADXL345Driver() - d.Start() - gobottest.Assert(t, d.SetRange(ADXL345_RANGE_16G), nil) +func TestADXL345XYZ(t *testing.T) { + // arrange + var tests = map[string]struct { + inputX []uint8 + inputY []uint8 + inputZ []uint8 + wantX float64 + wantY float64 + wantZ float64 + }{ + "null_value": { + inputX: []uint8{0, 0}, + inputY: []uint8{0, 0}, + inputZ: []uint8{0, 0}, + wantX: 0, + wantY: 0, + wantZ: 0, + }, + "some_value": { + inputX: []uint8{218, 0}, + inputY: []uint8{251, 255}, + inputZ: []uint8{100, 0}, + wantX: 0.8515625, + wantY: -0.01953125, + wantZ: 0.390625, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + d, a := initTestADXL345WithStubbedAdaptor() + d.Start() + a.written = []byte{} // reset writes of former test and start + // arrange reads + returnRead := append(append(tc.inputX, tc.inputY...), tc.inputZ...) + numCallsRead := 0 + a.i2cReadImpl = func(b []byte) (int, error) { + numCallsRead++ + copy(b, returnRead) + return len(b), nil + } + // act + x, y, z, _ := d.XYZ() + // assert + gobottest.Assert(t, x, tc.wantX) + gobottest.Assert(t, y, tc.wantY) + gobottest.Assert(t, z, tc.wantZ) + }) + } +} + +func TestADXL345XYZError(t *testing.T) { + // arrange + d, a := initTestADXL345WithStubbedAdaptor() + d.Start() + a.i2cReadImpl = func(b []byte) (int, error) { + return 0, errors.New("read error") + } + // act + _, _, _, err := d.XYZ() + // assert + gobottest.Assert(t, err, errors.New("read error")) +} + +func TestADXL345_initialize(t *testing.T) { + // sequence to prepare read in initialize(): + // * prepare rate register content (data output rate, low power mode) + // * prepare power control register content (wake up, sleep, measure, auto sleep, link) + // * prepare data format register (fullScaleRange, justify, fullRes, intInvert, spi, selfTest) + // * write 3 registers + // arrange + d, a := initTestADXL345WithStubbedAdaptor() + a.written = []byte{} // reset writes of former test + const ( + wantRateReg = uint8(0x2C) + wantRateRegVal = uint8(0x1A) // 100HZ and low power + wantPwrReg = uint8(0x2D) + wantPwrRegVal = uint8(0x08) // measurement on + wantFormatReg = uint8(0x31) + wantFormatRegVal = uint8(0x00) // FS to +/-2 g + ) + // act, assert - initialize() must be called on Start() + err := d.Start() + // assert + gobottest.Assert(t, err, nil) + gobottest.Assert(t, len(a.written), 6) + gobottest.Assert(t, a.written[0], wantRateReg) + gobottest.Assert(t, a.written[1], wantRateRegVal) + gobottest.Assert(t, a.written[2], wantPwrReg) + gobottest.Assert(t, a.written[3], wantPwrRegVal) + gobottest.Assert(t, a.written[4], wantFormatReg) + gobottest.Assert(t, a.written[5], wantFormatRegVal) +} + +func TestADXL345_shutdown(t *testing.T) { + // sequence to prepare read in shutdown(): + // * reset the measurement bit in structure + // * write the power control register (0x2D) + d, a := initTestADXL345WithStubbedAdaptor() + d.Start() + a.written = []byte{} // reset writes of former test + const ( + wantReg = uint8(0x2D) + wantVal = uint8(0x00) + ) + // act, assert - shutdown() must be called on Halt() + err := d.Halt() + // assert + gobottest.Assert(t, err, nil) + gobottest.Assert(t, len(a.written), 2) + gobottest.Assert(t, a.written[0], wantReg) + gobottest.Assert(t, a.written[1], wantVal) }