diff --git a/drivers/i2c/bme280_driver.go b/drivers/i2c/bme280_driver.go index 2649338e..ac8a4e8e 100644 --- a/drivers/i2c/bme280_driver.go +++ b/drivers/i2c/bme280_driver.go @@ -4,12 +4,27 @@ import ( "bytes" "encoding/binary" "errors" + "log" ) -const bme280RegisterControlHumidity = 0xF2 -const bme280RegisterHumidityMSB = 0xFD -const bme280RegisterCalibDigH1 = 0xa1 -const bme280RegisterCalibDigH2LSB = 0xe1 +const bme280Debug = true + +type BME280HumidityOversampling uint8 + +const ( + bme280RegCalibDigH1 = 0xA1 + bme280RegCalibDigH2LSB = 0xE1 + bme280RegControlHumidity = 0xF2 + bme280RegHumidityMSB = 0xFD + + // bits 0, 1, 3 of control humidity register + BME280CtrlHumidityNoMeasurement BME280HumidityOversampling = 0x00 // no measurement (value will be 0x08 0x00 0x00) + BME280CtrlHumidityOversampling1 BME280HumidityOversampling = 0x01 + BME280CtrlHumidityOversampling2 BME280HumidityOversampling = 0x02 + BME280CtrlHumidityOversampling4 BME280HumidityOversampling = 0x03 + BME280CtrlHumidityOversampling8 BME280HumidityOversampling = 0x04 + BME280CtrlHumidityOversampling16 BME280HumidityOversampling = 0x05 // same as 0x06, 0x07 +) type bmeHumidityCalibrationCoefficients struct { h1 uint8 @@ -28,7 +43,8 @@ type bmeHumidityCalibrationCoefficients struct { // type BME280Driver struct { *BMP280Driver - hc *bmeHumidityCalibrationCoefficients + humCalCoeffs *bmeHumidityCalibrationCoefficients + ctrlHumOversamp BME280HumidityOversampling } // NewBME280Driver creates a new driver with specified i2c interface. @@ -40,28 +56,89 @@ type BME280Driver struct { // i2c.WithAddress(int): address to use with this driver // func NewBME280Driver(c Connector, options ...func(Config)) *BME280Driver { - b := &BME280Driver{ - BMP280Driver: NewBMP280Driver(c), - hc: &bmeHumidityCalibrationCoefficients{}, + d := &BME280Driver{ + BMP280Driver: NewBMP280Driver(c), + humCalCoeffs: &bmeHumidityCalibrationCoefficients{}, + ctrlHumOversamp: BME280CtrlHumidityOversampling16, } + d.afterStart = d.initializationBME280 + // this loop is for options of this class, all options of base class BMP280Driver + // must be added in this class for usage for _, option := range options { - option(b) + option(d) } // TODO: expose commands to API - return b + return d } -// Start initializes the BME280 and loads the calibration coefficients. -func (d *BME280Driver) Start() (err error) { - bus := d.GetBusOrDefault(d.connector.GetDefaultBus()) - address := d.GetAddressOrDefault(bmp180Address) - - if d.connection, err = d.connector.GetConnection(address, bus); err != nil { - return err +// WithBME280PressureOversampling option sets the oversampling for pressure. +// Valid settings are of type "BMP280PressureOversampling" +func WithBME280PressureOversampling(val BMP280PressureOversampling) func(Config) { + return func(c Config) { + if d, ok := c.(*BME280Driver); ok { + d.ctrlPressOversamp = val + } else if bme280Debug { + log.Printf("Trying to set pressure oversampling for non-BME280Driver %v", c) + } } +} +// WithBME280TemperatureOversampling option sets oversampling for temperature. +// Valid settings are of type "BMP280TemperatureOversampling" +func WithBME280TemperatureOversampling(val BMP280TemperatureOversampling) func(Config) { + return func(c Config) { + if d, ok := c.(*BME280Driver); ok { + d.ctrlTempOversamp = val + } else if bme280Debug { + log.Printf("Trying to set temperature oversampling for non-BME280Driver %v", c) + } + } +} + +// WithBME280IIRFilter option sets the count of IIR filter coefficients. +// Valid settings are of type "BMP280IIRFilter" +func WithBME280IIRFilter(val BMP280IIRFilter) func(Config) { + return func(c Config) { + if d, ok := c.(*BME280Driver); ok { + d.confFilter = val + } else if bme280Debug { + log.Printf("Trying to set IIR filter for non-BME280Driver %v", c) + } + } +} + +// WithBME280HumidityOversampling option sets the oversampling for humidity. +// Valid settings are of type "BME280HumidityOversampling" +func WithBME280HumidityOversampling(val BME280HumidityOversampling) func(Config) { + return func(c Config) { + if d, ok := c.(*BME280Driver); ok { + d.ctrlHumOversamp = val + } else if bme280Debug { + log.Printf("Trying to set humidity oversampling for non-BME280Driver %v", c) + } + } +} + +// Humidity returns the current humidity in percentage of relative humidity +func (d *BME280Driver) Humidity() (humidity float32, err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + + var rawH uint32 + if rawH, err = d.rawHumidity(); err != nil { + return 0.0, err + } + humidity = d.calculateHumidity(rawH) + return +} + +func (d *BME280Driver) initializationBME280() (err error) { + // call the initialization routine of base class BMP280Driver, which do: + // * initializes temperature and pressure calibration coefficients + // * set the control register + // * set the configuration register if err := d.initialization(); err != nil { return err } @@ -73,26 +150,17 @@ func (d *BME280Driver) Start() (err error) { return nil } -// Humidity returns the current humidity in percentage of relative humidity -func (d *BME280Driver) Humidity() (humidity float32, err error) { - var rawH uint32 - if rawH, err = d.rawHumidity(); err != nil { - return 0.0, err - } - humidity = d.calculateHumidity(rawH) - return -} - // read the humidity calibration coefficients. func (d *BME280Driver) initHumidity() (err error) { - var coefficients []byte - if coefficients, err = d.read(bme280RegisterCalibDigH1, 1); err != nil { + var hch1 byte + if hch1, err = d.connection.ReadByteData(bme280RegCalibDigH1); err != nil { return err } - buf := bytes.NewBuffer(coefficients) - binary.Read(buf, binary.BigEndian, &d.hc.h1) + buf := bytes.NewBuffer([]byte{hch1}) + binary.Read(buf, binary.BigEndian, &d.humCalCoeffs.h1) - if coefficients, err = d.read(bme280RegisterCalibDigH2LSB, 7); err != nil { + coefficients := make([]byte, 7) + if err = d.connection.ReadBlockData(bme280RegCalibDigH2LSB, coefficients); err != nil { return err } buf = bytes.NewBuffer(coefficients) @@ -102,32 +170,32 @@ func (d *BME280Driver) initHumidity() (err error) { var addrE5 byte var addrE6 byte - binary.Read(buf, binary.LittleEndian, &d.hc.h2) // E1 ... - binary.Read(buf, binary.BigEndian, &d.hc.h3) // E3 - binary.Read(buf, binary.BigEndian, &addrE4) // E4 - binary.Read(buf, binary.BigEndian, &addrE5) // E5 - binary.Read(buf, binary.BigEndian, &addrE6) // E6 - binary.Read(buf, binary.BigEndian, &d.hc.h6) // ... E7 + binary.Read(buf, binary.LittleEndian, &d.humCalCoeffs.h2) // E1 ... + binary.Read(buf, binary.BigEndian, &d.humCalCoeffs.h3) // E3 + binary.Read(buf, binary.BigEndian, &addrE4) // E4 + binary.Read(buf, binary.BigEndian, &addrE5) // E5 + binary.Read(buf, binary.BigEndian, &addrE6) // E6 + binary.Read(buf, binary.BigEndian, &d.humCalCoeffs.h6) // ... E7 - d.hc.h4 = 0 + (int16(addrE4) << 4) | (int16(addrE5 & 0x0F)) - d.hc.h5 = 0 + (int16(addrE6) << 4) | (int16(addrE5) >> 4) + d.humCalCoeffs.h4 = 0 + (int16(addrE4) << 4) | (int16(addrE5 & 0x0F)) + d.humCalCoeffs.h5 = 0 + (int16(addrE6) << 4) | (int16(addrE5) >> 4) - d.connection.WriteByteData(bme280RegisterControlHumidity, 0x3F) - - // The 'ctrl_hum' register sets the humidity data acquisition options of + // The 'ctrl_hum' register (0xF2) sets the humidity data acquisition options of // the device. Changes to this register only become effective after a write - // operation to 'ctrl_meas'. Read the current value in, then write it back + // operation to 'ctrl_meas' (0xF4). So we read the current value in, then write it back + d.connection.WriteByteData(bme280RegControlHumidity, uint8(d.ctrlHumOversamp)) + var cmr uint8 - cmr, err = d.connection.ReadByteData(bmp280RegisterControl) + cmr, err = d.connection.ReadByteData(bmp280RegCtrl) if err == nil { - err = d.connection.WriteByteData(bmp280RegisterControl, cmr) + err = d.connection.WriteByteData(bmp280RegCtrl, cmr) } return err } func (d *BME280Driver) rawHumidity() (uint32, error) { - ret, err := d.read(bme280RegisterHumidityMSB, 2) - if err != nil { + ret := make([]byte, 2) + if err := d.connection.ReadBlockData(bme280RegHumidityMSB, ret); err != nil { return 0, err } if ret[0] == 0x80 && ret[1] == 0x00 { @@ -158,14 +226,14 @@ func (d *BME280Driver) calculateHumidity(rawH uint32) float32 { return 0 // TODO err is 'invalid data' from Bosch - include errors or not? } - x := float32(rawH) - (float32(d.hc.h4)*64.0 + - (float32(d.hc.h5) / 16384.0 * h)) + x := float32(rawH) - (float32(d.humCalCoeffs.h4)*64.0 + + (float32(d.humCalCoeffs.h5) / 16384.0 * h)) - y := float32(d.hc.h2) / 65536.0 * - (1.0 + float32(d.hc.h6)/67108864.0*h* - (1.0+float32(d.hc.h3)/67108864.0*h)) + y := float32(d.humCalCoeffs.h2) / 65536.0 * + (1.0 + float32(d.humCalCoeffs.h6)/67108864.0*h* + (1.0+float32(d.humCalCoeffs.h3)/67108864.0*h)) h = x * y - h = h * (1 - float32(d.hc.h1)*h/524288) + h = h * (1 - float32(d.humCalCoeffs.h1)*h/524288) return h } diff --git a/drivers/i2c/bme280_driver_test.go b/drivers/i2c/bme280_driver_test.go index c5f9b2e0..f5a00a22 100644 --- a/drivers/i2c/bme280_driver_test.go +++ b/drivers/i2c/bme280_driver_test.go @@ -3,93 +3,68 @@ package i2c import ( "bytes" "errors" + "strings" "testing" "gobot.io/x/gobot" "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 = (*BME280Driver)(nil) -// --------- HELPERS -func initTestBME280Driver() (driver *BME280Driver) { - driver, _ = initTestBME280DriverWithStubbedAdaptor() - return -} - -func initTestBME280DriverWithStubbedAdaptor() (*BME280Driver, *i2cTestAdaptor) { +func initTestBME280WithStubbedAdaptor() (*BME280Driver, *i2cTestAdaptor) { adaptor := newI2cTestAdaptor() return NewBME280Driver(adaptor), adaptor } -// --------- TESTS - func TestNewBME280Driver(t *testing.T) { - // Does it return a pointer to an instance of BME280Driver? - var bme280 interface{} = NewBME280Driver(newI2cTestAdaptor()) - _, ok := bme280.(*BME280Driver) + var di interface{} = NewBME280Driver(newI2cTestAdaptor()) + d, ok := di.(*BME280Driver) if !ok { t.Errorf("NewBME280Driver() should have returned a *BME280Driver") } + gobottest.Refute(t, d.Driver, nil) + gobottest.Assert(t, strings.HasPrefix(d.Name(), "BMP280"), true) + gobottest.Assert(t, d.defaultAddress, 0x77) + gobottest.Assert(t, d.ctrlPwrMode, uint8(0x03)) + gobottest.Assert(t, d.ctrlPressOversamp, BMP280PressureOversampling(0x05)) + gobottest.Assert(t, d.ctrlTempOversamp, BMP280TemperatureOversampling(0x01)) + gobottest.Assert(t, d.ctrlHumOversamp, BME280HumidityOversampling(0x05)) + gobottest.Assert(t, d.confFilter, BMP280IIRFilter(0x00)) + gobottest.Refute(t, d.calCoeffs, nil) } -func TestBME280Driver(t *testing.T) { - bme280 := initTestBME280Driver() - gobottest.Refute(t, bme280.Connection(), nil) +func TestBME280Options(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 := NewBME280Driver(newI2cTestAdaptor(), WithBus(2), + WithBME280PressureOversampling(0x01), + WithBME280TemperatureOversampling(0x02), + WithBME280IIRFilter(0x03), + WithBME280HumidityOversampling(0x04)) + gobottest.Assert(t, d.GetBusOrDefault(1), 2) + gobottest.Assert(t, d.ctrlPressOversamp, BMP280PressureOversampling(0x01)) + gobottest.Assert(t, d.ctrlTempOversamp, BMP280TemperatureOversampling(0x02)) + gobottest.Assert(t, d.confFilter, BMP280IIRFilter(0x03)) + gobottest.Assert(t, d.ctrlHumOversamp, BME280HumidityOversampling(0x04)) } -func TestBME280DriverStart(t *testing.T) { - bme280, adaptor := initTestBME280DriverWithStubbedAdaptor() - adaptor.i2cReadImpl = func(b []byte) (int, error) { - // Simulate returning a single byte for the - // ReadByteData(bmp280RegisterControl) call in Start() - return 1, nil - } - gobottest.Assert(t, bme280.Start(), nil) -} - -func TestBME280StartConnectError(t *testing.T) { - d, adaptor := initTestBME280DriverWithStubbedAdaptor() - adaptor.Testi2cConnectErr(true) - gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection")) -} - -func TestBME280DriverStartWriteError(t *testing.T) { - bme280, adaptor := initTestBME280DriverWithStubbedAdaptor() - adaptor.i2cWriteImpl = func([]byte) (int, error) { - return 0, errors.New("write error") - } - gobottest.Assert(t, bme280.Start(), errors.New("write error")) -} - -func TestBME280DriverStartReadError(t *testing.T) { - bme280, adaptor := initTestBME280DriverWithStubbedAdaptor() - adaptor.i2cReadImpl = func(b []byte) (int, error) { - return 0, errors.New("read error") - } - gobottest.Assert(t, bme280.Start(), errors.New("read error")) -} - -func TestBME280DriverHalt(t *testing.T) { - bme280 := initTestBME280Driver() - - gobottest.Assert(t, bme280.Halt(), nil) -} - -func TestBME280DriverMeasurements(t *testing.T) { - bme280, adaptor := initTestBME280DriverWithStubbedAdaptor() +func TestBME280Measurements(t *testing.T) { + bme280, adaptor := initTestBME280WithStubbedAdaptor() adaptor.i2cReadImpl = func(b []byte) (int, error) { buf := new(bytes.Buffer) // Values produced by dumping data from actual sensor - if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 { + if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 { buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16}) - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH1 { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH1 { buf.Write([]byte{75}) - } else if adaptor.written[len(adaptor.written)-1] == bmp280RegisterTempData { + } else if adaptor.written[len(adaptor.written)-1] == bmp280RegTempData { buf.Write([]byte{129, 0, 0}) - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH2LSB { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH2LSB { buf.Write([]byte{112, 1, 0, 19, 1, 0, 30}) - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterHumidityMSB { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegHumidityMSB { buf.Write([]byte{111, 83}) } copy(b, buf.Bytes()) @@ -101,16 +76,16 @@ func TestBME280DriverMeasurements(t *testing.T) { gobottest.Assert(t, hum, float32(51.20179)) } -func TestBME280DriverInitH1Error(t *testing.T) { - bme280, adaptor := initTestBME280DriverWithStubbedAdaptor() +func TestBME280InitH1Error(t *testing.T) { + bme280, adaptor := initTestBME280WithStubbedAdaptor() adaptor.i2cReadImpl = func(b []byte) (int, error) { buf := new(bytes.Buffer) // Values produced by dumping data from actual sensor - if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 { + if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 { buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16}) - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH1 { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH1 { return 0, errors.New("h1 read error") - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH2LSB { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH2LSB { buf.Write([]byte{112, 1, 0, 19, 1, 0, 30}) } copy(b, buf.Bytes()) @@ -120,16 +95,16 @@ func TestBME280DriverInitH1Error(t *testing.T) { gobottest.Assert(t, bme280.Start(), errors.New("h1 read error")) } -func TestBME280DriverInitH2Error(t *testing.T) { - bme280, adaptor := initTestBME280DriverWithStubbedAdaptor() +func TestBME280InitH2Error(t *testing.T) { + bme280, adaptor := initTestBME280WithStubbedAdaptor() adaptor.i2cReadImpl = func(b []byte) (int, error) { buf := new(bytes.Buffer) // Values produced by dumping data from actual sensor - if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 { + if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 { buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16}) - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH1 { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH1 { buf.Write([]byte{75}) - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH2LSB { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH2LSB { return 0, errors.New("h2 read error") } copy(b, buf.Bytes()) @@ -139,8 +114,8 @@ func TestBME280DriverInitH2Error(t *testing.T) { gobottest.Assert(t, bme280.Start(), errors.New("h2 read error")) } -func TestBME280DriverHumidityWriteError(t *testing.T) { - bme280, adaptor := initTestBME280DriverWithStubbedAdaptor() +func TestBME280HumidityWriteError(t *testing.T) { + bme280, adaptor := initTestBME280WithStubbedAdaptor() bme280.Start() adaptor.i2cWriteImpl = func([]byte) (int, error) { @@ -151,8 +126,8 @@ func TestBME280DriverHumidityWriteError(t *testing.T) { gobottest.Assert(t, hum, float32(0.0)) } -func TestBME280DriverHumidityReadError(t *testing.T) { - bme280, adaptor := initTestBME280DriverWithStubbedAdaptor() +func TestBME280HumidityReadError(t *testing.T) { + bme280, adaptor := initTestBME280WithStubbedAdaptor() bme280.Start() adaptor.i2cReadImpl = func([]byte) (int, error) { @@ -163,20 +138,20 @@ func TestBME280DriverHumidityReadError(t *testing.T) { gobottest.Assert(t, hum, float32(0.0)) } -func TestBME280DriverHumidityNotEnabled(t *testing.T) { - bme280, adaptor := initTestBME280DriverWithStubbedAdaptor() +func TestBME280HumidityNotEnabled(t *testing.T) { + bme280, adaptor := initTestBME280WithStubbedAdaptor() adaptor.i2cReadImpl = func(b []byte) (int, error) { buf := new(bytes.Buffer) // Values produced by dumping data from actual sensor - if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 { + if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 { buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16}) - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH1 { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH1 { buf.Write([]byte{75}) - } else if adaptor.written[len(adaptor.written)-1] == bmp280RegisterTempData { + } else if adaptor.written[len(adaptor.written)-1] == bmp280RegTempData { buf.Write([]byte{129, 0, 0}) - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH2LSB { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH2LSB { buf.Write([]byte{112, 1, 0, 19, 1, 0, 30}) - } else if adaptor.written[len(adaptor.written)-1] == bme280RegisterHumidityMSB { + } else if adaptor.written[len(adaptor.written)-1] == bme280RegHumidityMSB { buf.Write([]byte{0x80, 0x00}) } copy(b, buf.Bytes()) @@ -188,13 +163,28 @@ func TestBME280DriverHumidityNotEnabled(t *testing.T) { gobottest.Assert(t, hum, float32(0.0)) } -func TestBME280DriverSetName(t *testing.T) { - b := initTestBME280Driver() - b.SetName("TESTME") - gobottest.Assert(t, b.Name(), "TESTME") -} - -func TestBME280DriverOptions(t *testing.T) { - b := NewBME280Driver(newI2cTestAdaptor(), WithBus(2)) - gobottest.Assert(t, b.GetBusOrDefault(1), 2) +func TestBME280_initializationBME280(t *testing.T) { + bme280, adaptor := initTestBME280WithStubbedAdaptor() + readCallCounter := 0 + adaptor.i2cReadImpl = func(b []byte) (int, error) { + readCallCounter++ + if readCallCounter == 1 { + // Simulate returning 24 bytes for the coefficients (register bmp280RegCalib00) + return 24, nil + } + if readCallCounter == 2 { + // Simulate returning a single byte for the hc.h1 value (register bme280RegCalibDigH1) + return 1, nil + } + if readCallCounter == 3 { + // Simulate returning 7 bytes for the coefficients (register bme280RegCalibDigH2LSB) + return 7, nil + } + if readCallCounter == 4 { + // Simulate returning 1 byte for the cmr (register bmp280RegControl) + return 1, nil + } + return 0, nil + } + gobottest.Assert(t, bme280.Start(), nil) } diff --git a/drivers/i2c/bmp180_driver.go b/drivers/i2c/bmp180_driver.go index f3919dcf..545854cd 100644 --- a/drivers/i2c/bmp180_driver.go +++ b/drivers/i2c/bmp180_driver.go @@ -3,20 +3,23 @@ package i2c import ( "bytes" "encoding/binary" + "log" "time" - - "gobot.io/x/gobot" ) -const bmp180Address = 0x77 +const bmp180Debug = false -const bmp180RegisterAC1MSB = 0xAA +// the default address is applicable for SDO to VDD, for SDO to GND it will be 0x76 +const bmp180DefaultAddress = 0x77 -const bmp180RegisterCtl = 0xF4 -const bmp180CmdTemp = 0x2E -const bmp180RegisterTempMSB = 0xF6 -const bmp180CmdPressure = 0x34 -const bmp180RegisterPressureMSB = 0xF6 +const ( + bmp180RegisterAC1MSB = 0xAA // 11 x 16 bit calibration data (AC1..AC6, B1, B2, MB, MC, MD) + bmp180RegisterCtl = 0xF4 // control the value to read + bmp180RegisterDataMSB = 0xF6 // 16 bit data (temperature or pressure) + + bmp180CtlTemp = 0x2E + bmp180CtlPressure = 0x34 +) const ( // BMP180UltraLowPower is the lowest oversampling mode of the pressure measurement. @@ -32,7 +35,7 @@ const ( // BMP180OversamplingMode is the oversampling ratio of the pressure measurement. type BMP180OversamplingMode uint -type calibrationCoefficients struct { +type bmp180CalibrationCoefficients struct { ac1 int16 ac2 int16 ac3 int16 @@ -46,15 +49,12 @@ type calibrationCoefficients struct { md int16 } -// BMP180Driver is the gobot driver for the Bosch pressure sensor BMP180. +// BMP180Driver is the gobot driver for the Bosch pressure and temperature sensor BMP180. // Device datasheet: https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf type BMP180Driver struct { - name string - Mode BMP180OversamplingMode - connector Connector - connection Connection - Config - calibrationCoefficients *calibrationCoefficients + *Driver + oversampling BMP180OversamplingMode + calCoeffs *bmp180CalibrationCoefficients } // NewBMP180Driver creates a new driver with the i2c interface for the BMP180 device. @@ -66,80 +66,38 @@ type BMP180Driver struct { // i2c.WithAddress(int): address to use with this driver // func NewBMP180Driver(c Connector, options ...func(Config)) *BMP180Driver { - b := &BMP180Driver{ - name: gobot.DefaultName("BMP180"), - connector: c, - Mode: BMP180UltraLowPower, - Config: NewConfig(), - calibrationCoefficients: &calibrationCoefficients{}, + d := &BMP180Driver{ + Driver: NewDriver(c, "BMP180", bmp180DefaultAddress), + oversampling: BMP180UltraLowPower, + calCoeffs: &bmp180CalibrationCoefficients{}, } + d.afterStart = d.initialization for _, option := range options { - option(b) + option(d) } // TODO: expose commands to API - return b + return d } -// Name returns the name of the device. -func (d *BMP180Driver) Name() string { - return d.name -} - -// SetName sets the name of the device. -func (d *BMP180Driver) SetName(n string) { - d.name = n -} - -// Connection returns the connection of the device. -func (d *BMP180Driver) Connection() gobot.Connection { - return d.connector.(gobot.Connection) -} - -// Start initializes the BMP180 and loads the calibration coefficients. -func (d *BMP180Driver) Start() (err error) { - bus := d.GetBusOrDefault(d.connector.GetDefaultBus()) - address := d.GetAddressOrDefault(bmp180Address) - - if d.connection, err = d.connector.GetConnection(address, bus); err != nil { - return err +// WithBMP180oversampling option sets oversampling mode. +// Valid settings are of type "BMP180OversamplingMode" +func WithBMP180OversamplingMode(val BMP180OversamplingMode) func(Config) { + return func(c Config) { + if d, ok := c.(*BMP180Driver); ok { + d.oversampling = val + } else if bmp180Debug { + log.Printf("Trying to set oversampling mode for non-BMP180Driver %v", c) + } } - if err := d.initialization(); err != nil { - return err - } - return nil -} - -func (d *BMP180Driver) initialization() (err error) { - var coefficients []byte - // read the 11 calibration coefficients. - if coefficients, err = d.read(bmp180RegisterAC1MSB, 22); err != nil { - return err - } - buf := bytes.NewBuffer(coefficients) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac1) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac2) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac3) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac4) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac5) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac6) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.b1) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.b2) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.mb) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.mc) - binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.md) - - return nil -} - -// Halt halts the device. -func (d *BMP180Driver) Halt() (err error) { - return nil } // Temperature returns the current temperature, in celsius degrees. func (d *BMP180Driver) Temperature() (temp float32, err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + var rawTemp int16 if rawTemp, err = d.rawTemp(); err != nil { return 0, err @@ -149,23 +107,49 @@ func (d *BMP180Driver) Temperature() (temp float32, err error) { // Pressure returns the current pressure, in pascals. func (d *BMP180Driver) Pressure() (pressure float32, err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + var rawTemp int16 var rawPressure int32 if rawTemp, err = d.rawTemp(); err != nil { return 0, err } - if rawPressure, err = d.rawPressure(d.Mode); err != nil { + if rawPressure, err = d.rawPressure(d.oversampling); err != nil { return 0, err } - return d.calculatePressure(rawTemp, rawPressure, d.Mode), nil + return d.calculatePressure(rawTemp, rawPressure, d.oversampling), nil +} + +func (d *BMP180Driver) initialization() (err error) { + // read the 11 calibration coefficients. + coefficients := make([]byte, 22) + if err = d.connection.ReadBlockData(bmp180RegisterAC1MSB, coefficients); err != nil { + return err + } + buf := bytes.NewBuffer(coefficients) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac1) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac2) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac3) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac4) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac5) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac6) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.b1) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.b2) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.mb) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.mc) + binary.Read(buf, binary.BigEndian, &d.calCoeffs.md) + + return nil } func (d *BMP180Driver) rawTemp() (int16, error) { - if _, err := d.connection.Write([]byte{bmp180RegisterCtl, bmp180CmdTemp}); err != nil { + if _, err := d.connection.Write([]byte{bmp180RegisterCtl, bmp180CtlTemp}); err != nil { return 0, err } time.Sleep(5 * time.Millisecond) - ret, err := d.read(bmp180RegisterTempMSB, 2) + ret := make([]byte, 2) + err := d.connection.ReadBlockData(bmp180RegisterDataMSB, ret) if err != nil { return 0, err } @@ -175,18 +159,6 @@ func (d *BMP180Driver) rawTemp() (int16, error) { return rawTemp, nil } -func (d *BMP180Driver) read(address byte, n int) ([]byte, error) { - if _, err := d.connection.Write([]byte{address}); err != nil { - return nil, err - } - buf := make([]byte, n) - bytesRead, err := d.connection.Read(buf) - if bytesRead != n || err != nil { - return nil, err - } - return buf, nil -} - func (d *BMP180Driver) calculateTemp(rawTemp int16) float32 { b5 := d.calculateB5(rawTemp) t := (b5 + 8) >> 4 @@ -194,36 +166,36 @@ func (d *BMP180Driver) calculateTemp(rawTemp int16) float32 { } func (d *BMP180Driver) calculateB5(rawTemp int16) int32 { - x1 := (int32(rawTemp) - int32(d.calibrationCoefficients.ac6)) * int32(d.calibrationCoefficients.ac5) >> 15 - x2 := int32(d.calibrationCoefficients.mc) << 11 / (x1 + int32(d.calibrationCoefficients.md)) + x1 := (int32(rawTemp) - int32(d.calCoeffs.ac6)) * int32(d.calCoeffs.ac5) >> 15 + x2 := int32(d.calCoeffs.mc) << 11 / (x1 + int32(d.calCoeffs.md)) return x1 + x2 } -func (d *BMP180Driver) rawPressure(mode BMP180OversamplingMode) (rawPressure int32, err error) { - if _, err = d.connection.Write([]byte{bmp180RegisterCtl, bmp180CmdPressure + byte(mode<<6)}); err != nil { +func (d *BMP180Driver) rawPressure(oversampling BMP180OversamplingMode) (rawPressure int32, err error) { + if _, err = d.connection.Write([]byte{bmp180RegisterCtl, bmp180CtlPressure + byte(oversampling<<6)}); err != nil { return 0, err } - time.Sleep(pauseForReading(mode)) - var ret []byte - if ret, err = d.read(bmp180RegisterPressureMSB, 3); err != nil { + time.Sleep(bmp180PauseForReading(oversampling)) + ret := make([]byte, 3) + if err = d.connection.ReadBlockData(bmp180RegisterDataMSB, ret); err != nil { return 0, err } - rawPressure = (int32(ret[0])<<16 + int32(ret[1])<<8 + int32(ret[2])) >> (8 - uint(mode)) + rawPressure = (int32(ret[0])<<16 + int32(ret[1])<<8 + int32(ret[2])) >> (8 - uint(oversampling)) return rawPressure, nil } -func (d *BMP180Driver) calculatePressure(rawTemp int16, rawPressure int32, mode BMP180OversamplingMode) float32 { +func (d *BMP180Driver) calculatePressure(rawTemp int16, rawPressure int32, oversampling BMP180OversamplingMode) float32 { b5 := d.calculateB5(rawTemp) b6 := b5 - 4000 - x1 := (int32(d.calibrationCoefficients.b2) * (b6 * b6 >> 12)) >> 11 - x2 := (int32(d.calibrationCoefficients.ac2) * b6) >> 11 + x1 := (int32(d.calCoeffs.b2) * (b6 * b6 >> 12)) >> 11 + x2 := (int32(d.calCoeffs.ac2) * b6) >> 11 x3 := x1 + x2 - b3 := (((int32(d.calibrationCoefficients.ac1)*4 + x3) << uint(mode)) + 2) >> 2 - x1 = (int32(d.calibrationCoefficients.ac3) * b6) >> 13 - x2 = (int32(d.calibrationCoefficients.b1) * ((b6 * b6) >> 12)) >> 16 + b3 := (((int32(d.calCoeffs.ac1)*4 + x3) << uint(oversampling)) + 2) >> 2 + x1 = (int32(d.calCoeffs.ac3) * b6) >> 13 + x2 = (int32(d.calCoeffs.b1) * ((b6 * b6) >> 12)) >> 16 x3 = ((x1 + x2) + 2) >> 2 - b4 := (uint32(d.calibrationCoefficients.ac4) * uint32(x3+32768)) >> 15 - b7 := (uint32(rawPressure-b3) * (50000 >> uint(mode))) + b4 := (uint32(d.calCoeffs.ac4) * uint32(x3+32768)) >> 15 + b7 := (uint32(rawPressure-b3) * (50000 >> uint(oversampling))) var p int32 if b7 < 0x80000000 { p = int32((b7 << 1) / b4) @@ -236,9 +208,9 @@ func (d *BMP180Driver) calculatePressure(rawTemp int16, rawPressure int32, mode return float32(p + ((x1 + x2 + 3791) >> 4)) } -func pauseForReading(mode BMP180OversamplingMode) time.Duration { +func bmp180PauseForReading(oversampling BMP180OversamplingMode) time.Duration { var d time.Duration - switch mode { + switch oversampling { case BMP180UltraLowPower: d = 5 * time.Millisecond case BMP180Standard: diff --git a/drivers/i2c/bmp180_driver_test.go b/drivers/i2c/bmp180_driver_test.go index 91db9584..d8225d3a 100644 --- a/drivers/i2c/bmp180_driver_test.go +++ b/drivers/i2c/bmp180_driver_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "errors" + "strings" "testing" "time" @@ -11,62 +12,39 @@ 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 = (*BMP180Driver)(nil) -// --------- HELPERS -func initTestBMP180Driver() (driver *BMP180Driver) { - driver, _ = initTestBMP180DriverWithStubbedAdaptor() - return -} - -func initTestBMP180DriverWithStubbedAdaptor() (*BMP180Driver, *i2cTestAdaptor) { +func initTestBMP180WithStubbedAdaptor() (*BMP180Driver, *i2cTestAdaptor) { adaptor := newI2cTestAdaptor() return NewBMP180Driver(adaptor), adaptor } -// --------- TESTS - func TestNewBMP180Driver(t *testing.T) { // Does it return a pointer to an instance of BMP180Driver? - var bmp180 interface{} = NewBMP180Driver(newI2cTestAdaptor()) - _, ok := bmp180.(*BMP180Driver) + var di interface{} = NewBMP180Driver(newI2cTestAdaptor()) + d, ok := di.(*BMP180Driver) if !ok { t.Errorf("NewBMP180Driver() should have returned a *BMP180Driver") } + gobottest.Refute(t, d.Driver, nil) + gobottest.Assert(t, strings.HasPrefix(d.Name(), "BMP180"), true) + gobottest.Assert(t, d.defaultAddress, 0x77) + gobottest.Assert(t, d.oversampling, BMP180OversamplingMode(0x00)) + gobottest.Refute(t, d.calCoeffs, nil) } -func TestBMP180Driver(t *testing.T) { - bmp180 := initTestBMP180Driver() - gobottest.Refute(t, bmp180.Connection(), nil) +func TestBMP180Options(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 := NewBMP180Driver(newI2cTestAdaptor(), WithBus(2), WithBMP180OversamplingMode(0x01)) + gobottest.Assert(t, d.GetBusOrDefault(1), 2) + gobottest.Assert(t, d.oversampling, BMP180OversamplingMode(0x01)) } -func TestBMP180DriverStart(t *testing.T) { - bmp180, _ := initTestBMP180DriverWithStubbedAdaptor() - gobottest.Assert(t, bmp180.Start(), nil) -} - -func TestBMP180StartConnectError(t *testing.T) { - d, adaptor := initTestBMP180DriverWithStubbedAdaptor() - adaptor.Testi2cConnectErr(true) - gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection")) -} - -func TestBMP180DriverStartWriteError(t *testing.T) { - bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor() - adaptor.i2cWriteImpl = func([]byte) (int, error) { - return 0, errors.New("write error") - } - gobottest.Assert(t, bmp180.Start(), errors.New("write error")) -} - -func TestBMP180DriverHalt(t *testing.T) { - bmp180 := initTestBMP180Driver() - - gobottest.Assert(t, bmp180.Halt(), nil) -} - -func TestBMP180DriverMeasurements(t *testing.T) { - bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor() +func TestBMP180Measurements(t *testing.T) { + bmp180, adaptor := initTestBMP180WithStubbedAdaptor() adaptor.i2cReadImpl = func(b []byte) (int, error) { buf := new(bytes.Buffer) // Values from the datasheet example. @@ -82,9 +60,9 @@ func TestBMP180DriverMeasurements(t *testing.T) { binary.Write(buf, binary.BigEndian, int16(-32768)) binary.Write(buf, binary.BigEndian, int16(-8711)) binary.Write(buf, binary.BigEndian, int16(2868)) - } else if adaptor.written[len(adaptor.written)-2] == bmp180CmdTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterTempMSB { + } else if adaptor.written[len(adaptor.written)-2] == bmp180CtlTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB { binary.Write(buf, binary.BigEndian, int16(27898)) - } else if adaptor.written[len(adaptor.written)-2] == bmp180CmdPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterPressureMSB { + } else if adaptor.written[len(adaptor.written)-2] == bmp180CtlPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB { binary.Write(buf, binary.BigEndian, int16(23843)) // XLSB, not used in this test. buf.WriteByte(0) @@ -101,8 +79,8 @@ func TestBMP180DriverMeasurements(t *testing.T) { gobottest.Assert(t, pressure, float32(69964)) } -func TestBMP180DriverTemperatureError(t *testing.T) { - bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor() +func TestBMP180TemperatureError(t *testing.T) { + bmp180, adaptor := initTestBMP180WithStubbedAdaptor() adaptor.i2cReadImpl = func(b []byte) (int, error) { buf := new(bytes.Buffer) // Values from the datasheet example. @@ -118,9 +96,9 @@ func TestBMP180DriverTemperatureError(t *testing.T) { binary.Write(buf, binary.BigEndian, int16(-32768)) binary.Write(buf, binary.BigEndian, int16(-8711)) binary.Write(buf, binary.BigEndian, int16(2868)) - } else if adaptor.written[len(adaptor.written)-2] == bmp180CmdTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterTempMSB { + } else if adaptor.written[len(adaptor.written)-2] == bmp180CtlTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB { return 0, errors.New("temp error") - } else if adaptor.written[len(adaptor.written)-2] == bmp180CmdPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterPressureMSB { + } else if adaptor.written[len(adaptor.written)-2] == bmp180CtlPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB { binary.Write(buf, binary.BigEndian, int16(23843)) // XLSB, not used in this test. buf.WriteByte(0) @@ -133,8 +111,8 @@ func TestBMP180DriverTemperatureError(t *testing.T) { gobottest.Assert(t, err, errors.New("temp error")) } -func TestBMP180DriverPressureError(t *testing.T) { - bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor() +func TestBMP180PressureError(t *testing.T) { + bmp180, adaptor := initTestBMP180WithStubbedAdaptor() adaptor.i2cReadImpl = func(b []byte) (int, error) { buf := new(bytes.Buffer) // Values from the datasheet example. @@ -150,9 +128,9 @@ func TestBMP180DriverPressureError(t *testing.T) { binary.Write(buf, binary.BigEndian, int16(-32768)) binary.Write(buf, binary.BigEndian, int16(-8711)) binary.Write(buf, binary.BigEndian, int16(2868)) - } else if adaptor.written[len(adaptor.written)-2] == bmp180CmdTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterTempMSB { + } else if adaptor.written[len(adaptor.written)-2] == bmp180CtlTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB { binary.Write(buf, binary.BigEndian, int16(27898)) - } else if adaptor.written[len(adaptor.written)-2] == bmp180CmdPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterPressureMSB { + } else if adaptor.written[len(adaptor.written)-2] == bmp180CtlPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB { return 0, errors.New("press error") } copy(b, buf.Bytes()) @@ -163,8 +141,8 @@ func TestBMP180DriverPressureError(t *testing.T) { gobottest.Assert(t, err, errors.New("press error")) } -func TestBMP180DriverPressureWriteError(t *testing.T) { - bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor() +func TestBMP180PressureWriteError(t *testing.T) { + bmp180, adaptor := initTestBMP180WithStubbedAdaptor() bmp180.Start() adaptor.i2cWriteImpl = func([]byte) (int, error) { @@ -175,20 +153,56 @@ func TestBMP180DriverPressureWriteError(t *testing.T) { gobottest.Assert(t, err, errors.New("write error")) } -func TestBMP180DriverSetName(t *testing.T) { - b := initTestBMP180Driver() - b.SetName("TESTME") - gobottest.Assert(t, b.Name(), "TESTME") +func TestBMP180_initialization(t *testing.T) { + // sequence to read in initialization(): + // * read 22 bytes (11 x 16 bit calibration data), starting from AC1 register (0xAA) + // * fill calibration struct with data (MSByte read first) + // arrange + d, a := initTestBMP180WithStubbedAdaptor() + a.written = []byte{} // reset writes of former test + // Values from the datasheet example. + ac1 := []uint8{0x01, 0x98} + ac2 := []uint8{0xFF, 0xB8} + ac3 := []uint8{0xC7, 0xD1} + ac4 := []uint8{0x7F, 0xE5} + ac5 := []uint8{0x7F, 0xF5} + ac6 := []uint8{0x5A, 0x71} + b1 := []uint8{0x18, 0x2E} + b2 := []uint8{0x00, 0x04} + mb := []uint8{0x80, 0x00} + mc := []uint8{0xDD, 0xF9} + md := []uint8{0x0B, 0x34} + returnRead := append(append(append(append(append(ac1, ac2...), ac3...), ac4...), ac5...), ac6...) + returnRead = append(append(append(append(append(returnRead, b1...), b2...), mb...), mc...), md...) + numCallsRead := 0 + a.i2cReadImpl = func(b []byte) (int, error) { + numCallsRead++ + copy(b, returnRead) + return len(b), nil + } + // act, assert - initialization() must be called on Start() + err := d.Start() + // assert + gobottest.Assert(t, err, nil) + gobottest.Assert(t, numCallsRead, 1) + gobottest.Assert(t, len(a.written), 1) + gobottest.Assert(t, a.written[0], uint8(0xAA)) + gobottest.Assert(t, d.calCoeffs.ac1, int16(408)) + gobottest.Assert(t, d.calCoeffs.ac2, int16(-72)) + gobottest.Assert(t, d.calCoeffs.ac3, int16(-14383)) + gobottest.Assert(t, d.calCoeffs.ac4, uint16(32741)) + gobottest.Assert(t, d.calCoeffs.ac5, uint16(32757)) + gobottest.Assert(t, d.calCoeffs.ac6, uint16(23153)) + gobottest.Assert(t, d.calCoeffs.b1, int16(6190)) + gobottest.Assert(t, d.calCoeffs.b2, int16(4)) + gobottest.Assert(t, d.calCoeffs.mb, int16(-32768)) + gobottest.Assert(t, d.calCoeffs.mc, int16(-8711)) + gobottest.Assert(t, d.calCoeffs.md, int16(2868)) } -func TestBMP180DriverOptions(t *testing.T) { - b := NewBMP180Driver(newI2cTestAdaptor(), WithBus(2)) - gobottest.Assert(t, b.GetBusOrDefault(1), 2) -} - -func TestBMP180PauseForReading(t *testing.T) { - gobottest.Assert(t, pauseForReading(BMP180UltraLowPower), time.Duration(5*time.Millisecond)) - gobottest.Assert(t, pauseForReading(BMP180Standard), time.Duration(8*time.Millisecond)) - gobottest.Assert(t, pauseForReading(BMP180HighResolution), time.Duration(14*time.Millisecond)) - gobottest.Assert(t, pauseForReading(BMP180UltraHighResolution), time.Duration(26*time.Millisecond)) +func TestBMP180_bmp180PauseForReading(t *testing.T) { + gobottest.Assert(t, bmp180PauseForReading(BMP180UltraLowPower), time.Duration(5*time.Millisecond)) + gobottest.Assert(t, bmp180PauseForReading(BMP180Standard), time.Duration(8*time.Millisecond)) + gobottest.Assert(t, bmp180PauseForReading(BMP180HighResolution), time.Duration(14*time.Millisecond)) + gobottest.Assert(t, bmp180PauseForReading(BMP180UltraHighResolution), time.Duration(26*time.Millisecond)) } diff --git a/drivers/i2c/bmp280_driver.go b/drivers/i2c/bmp280_driver.go index ca04fc81..73486fa0 100644 --- a/drivers/i2c/bmp280_driver.go +++ b/drivers/i2c/bmp280_driver.go @@ -3,18 +3,70 @@ package i2c import ( "bytes" "encoding/binary" + "log" "math" - - "gobot.io/x/gobot" ) +const bmp280Debug = true + +// the default address is applicable for SDO to VDD, for SDO to GND it will be 0x76 +// this is also true for bme280 (which using this address as well) +const bmp280DefaultAddress = 0x77 + +type BMP280PressureOversampling uint8 +type BMP280TemperatureOversampling uint8 +type BMP280IIRFilter uint8 + const ( - bmp280RegisterControl = 0xf4 - bmp280RegisterConfig = 0xf5 - bmp280RegisterPressureData = 0xf7 - bmp280RegisterTempData = 0xfa - bmp280RegisterCalib00 = 0x88 - bmp280SeaLevelPressure = 1013.25 + bmp280RegCalib00 = 0x88 // 12 x 16 bit calibration data (T1..T3, P1..P9) + bmp280RegCtrl = 0xF4 // data acquisition options (oversampling of temperature and pressure, power mode) + bmp280RegConf = 0xF5 // rate, IIR-filter and interface options (SPI) + bmp280RegPressureData = 0xF7 + bmp280RegTempData = 0xFA + + // bits 0, 1 of control register + bmp280CtrlPwrSleepMode = 0x00 + bmp280CtrlPwrForcedMode = 0x01 + bmp280CtrlPwrForcedMode2 = 0x02 // same function as 0x01 + bmp280CtrlPwrNormalMode = 0x03 + + // bits 2, 3, 4 of control register (will be shifted on write) + BMP280CtrlPressNoMeasurement BMP280PressureOversampling = 0x00 // no measurement (value will be 0x08 0x00 0x00) + BMP280CtrlPressOversampling1 BMP280PressureOversampling = 0x01 // resolution 16 bit + BMP280CtrlPressOversampling2 BMP280PressureOversampling = 0x02 // resolution 17 bit + BMP280CtrlPressOversampling4 BMP280PressureOversampling = 0x03 // resolution 18 bit + BMP280CtrlPressOversampling8 BMP280PressureOversampling = 0x04 // resolution 19 bit + BMP280CtrlPressOversampling16 BMP280PressureOversampling = 0x05 // resolution 20 bit (same as 0x06, 0x07) + + // bits 5, 6, 7 of control register (will be shifted on write) + BMP280CtrlTempNoMeasurement BMP280TemperatureOversampling = 0x00 // no measurement (value will be 0x08 0x00 0x00) + BMP280CtrlTempOversampling1 BMP280TemperatureOversampling = 0x01 // resolution 16 bit + BMP280CtrlTempOversampling2 BMP280TemperatureOversampling = 0x02 // resolution 17 bit + BMP280CtrlTempOversampling4 BMP280TemperatureOversampling = 0x03 // resolution 18 bit + BMP280CtrlTempOversampling8 BMP280TemperatureOversampling = 0x04 // resolution 19 bit + BMP280CtrlTempOversampling16 BMP280TemperatureOversampling = 0x05 // resolution 20 bit + + // bit 0 of config register + bmp280ConfSPIBit = 0x01 // if set, SPI is used + + // bits 2, 3, 4 of config register (bit 1 is unused, will be shifted on write) + bmp280ConfStandBy0005 = 0x00 // 0.5 ms + bmp280ConfStandBy0625 = 0x01 // 62.5 ms + bmp280ConfStandBy0125 = 0x02 // 125 ms + bmp280ConfStandBy0250 = 0x03 // 250 ms + bmp280ConfStandBy0500 = 0x04 // 500 ms + bmp280ConfStandBy1000 = 0x05 // 1000 ms + bmp280ConfStandBy2000 = 0x06 // 2000 ms + bmp280ConfStandBy4000 = 0x07 // 4000 ms + + // bits 5, 6, 7 of config register + BMP280ConfFilterOff BMP280IIRFilter = 0x00 + BMP280ConfFilter2 BMP280IIRFilter = 0x01 + BMP280ConfFilter4 BMP280IIRFilter = 0x02 + BMP280ConfFilter8 BMP280IIRFilter = 0x03 + BMP280ConfFilter16 BMP280IIRFilter = 0x04 + + bmp280SeaLevelPressure = 1013.25 ) type bmp280CalibrationCoefficients struct { @@ -34,76 +86,82 @@ type bmp280CalibrationCoefficients struct { // BMP280Driver is a driver for the BMP280 temperature/pressure sensor type BMP280Driver struct { - name string - connector Connector - connection Connection - Config - - tpc *bmp280CalibrationCoefficients + *Driver + calCoeffs *bmp280CalibrationCoefficients + ctrlPwrMode uint8 + ctrlPressOversamp BMP280PressureOversampling + ctrlTempOversamp BMP280TemperatureOversampling + confFilter BMP280IIRFilter } // NewBMP280Driver 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 NewBMP280Driver(c Connector, options ...func(Config)) *BMP280Driver { - b := &BMP280Driver{ - name: gobot.DefaultName("BMP280"), - connector: c, - Config: NewConfig(), - tpc: &bmp280CalibrationCoefficients{}, + d := &BMP280Driver{ + Driver: NewDriver(c, "BMP280", bmp280DefaultAddress), + calCoeffs: &bmp280CalibrationCoefficients{}, + ctrlPwrMode: bmp280CtrlPwrNormalMode, + ctrlPressOversamp: BMP280CtrlPressOversampling16, + ctrlTempOversamp: BMP280CtrlTempOversampling1, + confFilter: BMP280ConfFilterOff, } + d.afterStart = d.initialization for _, option := range options { - option(b) + option(d) } // TODO: expose commands to API - return b + return d } -// Name returns the name of the device. -func (d *BMP280Driver) Name() string { - return d.name -} - -// SetName sets the name of the device. -func (d *BMP280Driver) SetName(n string) { - d.name = n -} - -// Connection returns the connection of the device. -func (d *BMP280Driver) Connection() gobot.Connection { - return d.connector.(gobot.Connection) -} - -// Start initializes the BMP280 and loads the calibration coefficients. -func (d *BMP280Driver) Start() (err error) { - bus := d.GetBusOrDefault(d.connector.GetDefaultBus()) - address := d.GetAddressOrDefault(bmp180Address) - - if d.connection, err = d.connector.GetConnection(address, bus); err != nil { - return err +// WithBMP280PressureOversampling option sets the oversampling for pressure. +// Valid settings are of type "BMP280PressureOversampling" +func WithBMP280PressureOversampling(val BMP280PressureOversampling) func(Config) { + return func(c Config) { + if d, ok := c.(*BMP280Driver); ok { + d.ctrlPressOversamp = val + } else if bmp280Debug { + log.Printf("Trying to set pressure oversampling for non-BMP280Driver %v", c) + } } - - if err := d.initialization(); err != nil { - return err - } - - return nil } -// Halt halts the device. -func (d *BMP280Driver) Halt() (err error) { - return nil +// WithBMP280TemperatureOversampling option sets oversampling for temperature. +// Valid settings are of type "BMP280TemperatureOversampling" +func WithBMP280TemperatureOversampling(val BMP280TemperatureOversampling) func(Config) { + return func(c Config) { + if d, ok := c.(*BMP280Driver); ok { + d.ctrlTempOversamp = val + } else if bmp280Debug { + log.Printf("Trying to set temperature oversampling for non-BMP280Driver %v", c) + } + } +} + +// WithBMP280IIRFilter option sets the count of IIR filter coefficients. +// Valid settings are of type "BMP280IIRFilter" +func WithBMP280IIRFilter(val BMP280IIRFilter) func(Config) { + return func(c Config) { + if d, ok := c.(*BMP280Driver); ok { + d.confFilter = val + } else if bmp280Debug { + log.Printf("Trying to set IIR filter for non-BMP280Driver %v", c) + } + } } // Temperature returns the current temperature, in celsius degrees. func (d *BMP280Driver) Temperature() (temp float32, err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + var rawT int32 if rawT, err = d.rawTemp(); err != nil { return 0.0, err @@ -114,6 +172,9 @@ func (d *BMP280Driver) Temperature() (temp float32, err error) { // Pressure returns the current barometric pressure, in Pa func (d *BMP280Driver) Pressure() (press float32, err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + var rawT, rawP int32 if rawT, err = d.rawTemp(); err != nil { return 0.0, err @@ -140,34 +201,38 @@ func (d *BMP280Driver) Altitude() (alt float32, err error) { // initialization reads the calibration coefficients. func (d *BMP280Driver) initialization() (err error) { - var coefficients []byte - if coefficients, err = d.read(bmp280RegisterCalib00, 24); err != nil { + coefficients := make([]byte, 24) + if err = d.connection.ReadBlockData(bmp280RegCalib00, coefficients); err != nil { return err } buf := bytes.NewBuffer(coefficients) - binary.Read(buf, binary.LittleEndian, &d.tpc.t1) - binary.Read(buf, binary.LittleEndian, &d.tpc.t2) - binary.Read(buf, binary.LittleEndian, &d.tpc.t3) - binary.Read(buf, binary.LittleEndian, &d.tpc.p1) - binary.Read(buf, binary.LittleEndian, &d.tpc.p2) - binary.Read(buf, binary.LittleEndian, &d.tpc.p3) - binary.Read(buf, binary.LittleEndian, &d.tpc.p4) - binary.Read(buf, binary.LittleEndian, &d.tpc.p5) - binary.Read(buf, binary.LittleEndian, &d.tpc.p6) - binary.Read(buf, binary.LittleEndian, &d.tpc.p7) - binary.Read(buf, binary.LittleEndian, &d.tpc.p8) - binary.Read(buf, binary.LittleEndian, &d.tpc.p9) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.t1) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.t2) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.t3) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p1) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p2) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p3) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p4) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p5) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p6) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p7) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p8) + binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p9) - d.connection.WriteByteData(bmp280RegisterControl, 0x3F) + ctrlReg := uint8(d.ctrlPwrMode) | uint8(d.ctrlPressOversamp)<<2 | uint8(d.ctrlTempOversamp)<<5 + d.connection.WriteByteData(bmp280RegCtrl, ctrlReg) + + confReg := uint8(bmp280ConfStandBy0005)<<2 | uint8(d.confFilter)<<5 + d.connection.WriteByteData(bmp280RegConf, confReg & ^uint8(bmp280ConfSPIBit)) return nil } func (d *BMP280Driver) rawTemp() (temp int32, err error) { - var data []byte var tp0, tp1, tp2 byte - if data, err = d.read(bmp280RegisterTempData, 3); err != nil { + data := make([]byte, 3) + if err = d.connection.ReadBlockData(bmp280RegTempData, data); err != nil { return 0, err } buf := bytes.NewBuffer(data) @@ -181,10 +246,10 @@ func (d *BMP280Driver) rawTemp() (temp int32, err error) { } func (d *BMP280Driver) rawPressure() (press int32, err error) { - var data []byte var tp0, tp1, tp2 byte - if data, err = d.read(bmp280RegisterPressureData, 3); err != nil { + data := make([]byte, 3) + if err = d.connection.ReadBlockData(bmp280RegPressureData, data); err != nil { return 0, err } buf := bytes.NewBuffer(data) @@ -198,8 +263,8 @@ func (d *BMP280Driver) rawPressure() (press int32, err error) { } func (d *BMP280Driver) calculateTemp(rawTemp int32) (float32, int32) { - tcvar1 := ((float32(rawTemp) / 16384.0) - (float32(d.tpc.t1) / 1024.0)) * float32(d.tpc.t2) - tcvar2 := (((float32(rawTemp) / 131072.0) - (float32(d.tpc.t1) / 8192.0)) * ((float32(rawTemp) / 131072.0) - float32(d.tpc.t1)/8192.0)) * float32(d.tpc.t3) + tcvar1 := ((float32(rawTemp) / 16384.0) - (float32(d.calCoeffs.t1) / 1024.0)) * float32(d.calCoeffs.t2) + tcvar2 := (((float32(rawTemp) / 131072.0) - (float32(d.calCoeffs.t1) / 8192.0)) * ((float32(rawTemp) / 131072.0) - float32(d.calCoeffs.t1)/8192.0)) * float32(d.calCoeffs.t3) temperatureComp := (tcvar1 + tcvar2) / 5120.0 tFine := int32(tcvar1 + tcvar2) @@ -210,33 +275,21 @@ func (d *BMP280Driver) calculatePress(rawPress int32, tFine int32) float32 { var var1, var2, p int64 var1 = int64(tFine) - 128000 - var2 = var1 * var1 * int64(d.tpc.p6) - var2 = var2 + ((var1 * int64(d.tpc.p5)) << 17) - var2 = var2 + (int64(d.tpc.p4) << 35) - var1 = (var1 * var1 * int64(d.tpc.p3) >> 8) + - ((var1 * int64(d.tpc.p2)) << 12) - var1 = ((int64(1) << 47) + var1) * (int64(d.tpc.p1)) >> 33 + var2 = var1 * var1 * int64(d.calCoeffs.p6) + var2 = var2 + ((var1 * int64(d.calCoeffs.p5)) << 17) + var2 = var2 + (int64(d.calCoeffs.p4) << 35) + var1 = (var1 * var1 * int64(d.calCoeffs.p3) >> 8) + + ((var1 * int64(d.calCoeffs.p2)) << 12) + var1 = ((int64(1) << 47) + var1) * (int64(d.calCoeffs.p1)) >> 33 if var1 == 0 { return 0 // avoid exception caused by division by zero } p = 1048576 - int64(rawPress) p = (((p << 31) - var2) * 3125) / var1 - var1 = (int64(d.tpc.p9) * (p >> 13) * (p >> 13)) >> 25 - var2 = (int64(d.tpc.p8) * p) >> 19 + var1 = (int64(d.calCoeffs.p9) * (p >> 13) * (p >> 13)) >> 25 + var2 = (int64(d.calCoeffs.p8) * p) >> 19 - p = ((p + var1 + var2) >> 8) + (int64(d.tpc.p7) << 4) + p = ((p + var1 + var2) >> 8) + (int64(d.calCoeffs.p7) << 4) return float32(p) / 256 } - -func (d *BMP280Driver) read(address byte, n int) ([]byte, error) { - if _, err := d.connection.Write([]byte{address}); err != nil { - return nil, err - } - buf := make([]byte, n) - bytesRead, err := d.connection.Read(buf) - if bytesRead != n || err != nil { - return nil, err - } - return buf, nil -} diff --git a/drivers/i2c/bmp280_driver_test.go b/drivers/i2c/bmp280_driver_test.go index d3d2c2a0..fbaa87b6 100644 --- a/drivers/i2c/bmp280_driver_test.go +++ b/drivers/i2c/bmp280_driver_test.go @@ -3,156 +3,209 @@ package i2c import ( "bytes" "errors" + "strings" "testing" "gobot.io/x/gobot" "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 = (*BMP280Driver)(nil) -// --------- HELPERS -func initTestBMP280Driver() (driver *BMP280Driver) { - driver, _ = initTestBMP280DriverWithStubbedAdaptor() - return -} - -func initTestBMP280DriverWithStubbedAdaptor() (*BMP280Driver, *i2cTestAdaptor) { +func initTestBMP280WithStubbedAdaptor() (*BMP280Driver, *i2cTestAdaptor) { adaptor := newI2cTestAdaptor() return NewBMP280Driver(adaptor), adaptor } -// --------- TESTS - func TestNewBMP280Driver(t *testing.T) { - // Does it return a pointer to an instance of BME280Driver? - var bmp280 interface{} = NewBMP280Driver(newI2cTestAdaptor()) - _, ok := bmp280.(*BMP280Driver) + var di interface{} = NewBMP280Driver(newI2cTestAdaptor()) + d, ok := di.(*BMP280Driver) if !ok { t.Errorf("NewBMP280Driver() should have returned a *BMP280Driver") } + gobottest.Refute(t, d.Driver, nil) + gobottest.Assert(t, strings.HasPrefix(d.Name(), "BMP280"), true) + gobottest.Assert(t, d.defaultAddress, 0x77) + gobottest.Assert(t, d.ctrlPwrMode, uint8(0x03)) + gobottest.Assert(t, d.ctrlPressOversamp, BMP280PressureOversampling(0x05)) + gobottest.Assert(t, d.ctrlTempOversamp, BMP280TemperatureOversampling(0x01)) + gobottest.Assert(t, d.confFilter, BMP280IIRFilter(0x00)) + gobottest.Refute(t, d.calCoeffs, nil) } -func TestBMP280Driver(t *testing.T) { - bmp280 := initTestBMP280Driver() - gobottest.Refute(t, bmp280.Connection(), nil) +func TestBMP280Options(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 := NewBMP280Driver(newI2cTestAdaptor(), WithBus(2), WithBMP280PressureOversampling(0x04)) + gobottest.Assert(t, d.GetBusOrDefault(1), 2) + gobottest.Assert(t, d.ctrlPressOversamp, BMP280PressureOversampling(0x04)) } -func TestBMP280DriverStart(t *testing.T) { - bmp280, _ := initTestBMP280DriverWithStubbedAdaptor() - gobottest.Assert(t, bmp280.Start(), nil) +func TestWithBMP280TemperatureOversampling(t *testing.T) { + // arrange + d, a := initTestBMP280WithStubbedAdaptor() + a.written = []byte{} // reset writes of former test + const ( + setVal = BMP280TemperatureOversampling(0x04) // 8 x + ) + // act + WithBMP280TemperatureOversampling(setVal)(d) + // assert + gobottest.Assert(t, d.ctrlTempOversamp, setVal) + gobottest.Assert(t, len(a.written), 0) } -func TestBMP280StartConnectError(t *testing.T) { - d, adaptor := initTestBMP280DriverWithStubbedAdaptor() - adaptor.Testi2cConnectErr(true) - gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection")) +func TestWithBMP280IIRFilter(t *testing.T) { + // arrange + d, a := initTestBMP280WithStubbedAdaptor() + a.written = []byte{} // reset writes of former test + const ( + setVal = BMP280IIRFilter(0x02) // 4 x + ) + // act + WithBMP280IIRFilter(setVal)(d) + // assert + gobottest.Assert(t, d.confFilter, setVal) + gobottest.Assert(t, len(a.written), 0) } -func TestBMP280DriverStartWriteError(t *testing.T) { - bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor() - adaptor.i2cWriteImpl = func([]byte) (int, error) { - return 0, errors.New("write error") - } - gobottest.Assert(t, bmp280.Start(), errors.New("write error")) -} - -func TestBMP280DriverStartReadError(t *testing.T) { - bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor() - adaptor.i2cReadImpl = func(b []byte) (int, error) { - return 0, errors.New("read error") - } - gobottest.Assert(t, bmp280.Start(), errors.New("read error")) -} - -func TestBMP280DriverHalt(t *testing.T) { - bmp280 := initTestBMP280Driver() - - gobottest.Assert(t, bmp280.Halt(), nil) -} - -func TestBMP280DriverMeasurements(t *testing.T) { - bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor() +func TestBMP280Measurements(t *testing.T) { + d, adaptor := initTestBMP280WithStubbedAdaptor() adaptor.i2cReadImpl = func(b []byte) (int, error) { buf := new(bytes.Buffer) // Values produced by dumping data from actual sensor - if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 { + if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 { buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16}) - } else if adaptor.written[len(adaptor.written)-1] == bmp280RegisterTempData { + } else if adaptor.written[len(adaptor.written)-1] == bmp280RegTempData { buf.Write([]byte{128, 243, 0}) - } else if adaptor.written[len(adaptor.written)-1] == bmp280RegisterPressureData { + } else if adaptor.written[len(adaptor.written)-1] == bmp280RegPressureData { buf.Write([]byte{77, 23, 48}) } copy(b, buf.Bytes()) return buf.Len(), nil } - bmp280.Start() - temp, err := bmp280.Temperature() + d.Start() + temp, err := d.Temperature() gobottest.Assert(t, err, nil) gobottest.Assert(t, temp, float32(25.014637)) - pressure, err := bmp280.Pressure() + pressure, err := d.Pressure() gobottest.Assert(t, err, nil) gobottest.Assert(t, pressure, float32(99545.414)) - alt, err := bmp280.Altitude() + alt, err := d.Altitude() gobottest.Assert(t, err, nil) gobottest.Assert(t, alt, float32(149.22713)) } -func TestBMP280DriverTemperatureWriteError(t *testing.T) { - bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor() - bmp280.Start() +func TestBMP280TemperatureWriteError(t *testing.T) { + d, adaptor := initTestBMP280WithStubbedAdaptor() + d.Start() adaptor.i2cWriteImpl = func([]byte) (int, error) { return 0, errors.New("write error") } - temp, err := bmp280.Temperature() + temp, err := d.Temperature() gobottest.Assert(t, err, errors.New("write error")) gobottest.Assert(t, temp, float32(0.0)) } -func TestBMP280DriverTemperatureReadError(t *testing.T) { - bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor() - bmp280.Start() +func TestBMP280TemperatureReadError(t *testing.T) { + d, adaptor := initTestBMP280WithStubbedAdaptor() + d.Start() adaptor.i2cReadImpl = func([]byte) (int, error) { return 0, errors.New("read error") } - temp, err := bmp280.Temperature() + temp, err := d.Temperature() gobottest.Assert(t, err, errors.New("read error")) gobottest.Assert(t, temp, float32(0.0)) } -func TestBMP280DriverPressureWriteError(t *testing.T) { - bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor() - bmp280.Start() +func TestBMP280PressureWriteError(t *testing.T) { + d, adaptor := initTestBMP280WithStubbedAdaptor() + d.Start() adaptor.i2cWriteImpl = func([]byte) (int, error) { return 0, errors.New("write error") } - press, err := bmp280.Pressure() + press, err := d.Pressure() gobottest.Assert(t, err, errors.New("write error")) gobottest.Assert(t, press, float32(0.0)) } -func TestBMP280DriverPressureReadError(t *testing.T) { - bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor() - bmp280.Start() +func TestBMP280PressureReadError(t *testing.T) { + d, adaptor := initTestBMP280WithStubbedAdaptor() + d.Start() adaptor.i2cReadImpl = func([]byte) (int, error) { return 0, errors.New("read error") } - press, err := bmp280.Pressure() + press, err := d.Pressure() gobottest.Assert(t, err, errors.New("read error")) gobottest.Assert(t, press, float32(0.0)) } -func TestBMP280DriverSetName(t *testing.T) { - b := initTestBMP280Driver() - b.SetName("TESTME") - gobottest.Assert(t, b.Name(), "TESTME") -} - -func TestBMP280DriverOptions(t *testing.T) { - b := NewBMP280Driver(newI2cTestAdaptor(), WithBus(2)) - gobottest.Assert(t, b.GetBusOrDefault(1), 2) +func TestBMP280_initialization(t *testing.T) { + // sequence to read and write in initialization(): + // * read 24 bytes (12 x 16 bit calibration data), starting from TC1 register (0x88) + // * fill calibration struct with data (LSByte read first) + // * prepare the content of control register + // * write the control register (0xF4) + // * prepare the content of config register + // * write the config register (0xF5) + // arrange + d, a := initTestBMP280WithStubbedAdaptor() + a.written = []byte{} // reset writes of former test + const ( + wantCalibReg = uint8(0x88) + wantCtrlReg = uint8(0xF4) + wantCtrlRegVal = uint8(0x37) // normal power mode, 16 x pressure and 1 x temperature oversampling + wantConfReg = uint8(0xF5) + wantConfRegVal = uint8(0x00) // no SPI, no filter, smallest standby (unused, because normal power mode) + ) + // Values from the datasheet example. + t1 := []uint8{0x70, 0x6B} + t2 := []uint8{0x43, 0x67} + t3 := []uint8{0x18, 0xFC} + p1 := []uint8{0x7D, 0x8E} + p2 := []uint8{0x43, 0xD6} + p3 := []uint8{0xD0, 0x0B} + p4 := []uint8{0x27, 0x0B} + p5 := []uint8{0x8C, 0x00} + p6 := []uint8{0xF9, 0xFF} + p7 := []uint8{0x8C, 0x3C} + p8 := []uint8{0xF8, 0xC6} + p9 := []uint8{0x70, 0x17} + returnRead := append(append(append(append(append(append(t1, t2...), t3...), p1...), p2...), p3...), p4...) + returnRead = append(append(append(append(append(returnRead, p5...), p6...), p7...), p8...), p9...) + numCallsRead := 0 + a.i2cReadImpl = func(b []byte) (int, error) { + numCallsRead++ + copy(b, returnRead) + return len(b), nil + } + // act, assert - initialization() must be called on Start() + err := d.Start() + // assert + gobottest.Assert(t, err, nil) + gobottest.Assert(t, numCallsRead, 1) + gobottest.Assert(t, len(a.written), 5) + gobottest.Assert(t, a.written[0], wantCalibReg) + gobottest.Assert(t, a.written[1], wantCtrlReg) + gobottest.Assert(t, a.written[2], wantCtrlRegVal) + gobottest.Assert(t, a.written[3], wantConfReg) + gobottest.Assert(t, a.written[4], wantConfRegVal) + gobottest.Assert(t, d.calCoeffs.t1, uint16(27504)) + gobottest.Assert(t, d.calCoeffs.t2, int16(26435)) + gobottest.Assert(t, d.calCoeffs.t3, int16(-1000)) + gobottest.Assert(t, d.calCoeffs.p1, uint16(36477)) + gobottest.Assert(t, d.calCoeffs.p2, int16(-10685)) + gobottest.Assert(t, d.calCoeffs.p3, int16(3024)) + gobottest.Assert(t, d.calCoeffs.p4, int16(2855)) + gobottest.Assert(t, d.calCoeffs.p5, int16(140)) + gobottest.Assert(t, d.calCoeffs.p6, int16(-7)) + gobottest.Assert(t, d.calCoeffs.p7, int16(15500)) + gobottest.Assert(t, d.calCoeffs.p8, int16(-14600)) + gobottest.Assert(t, d.calCoeffs.p9, int16(6000)) } diff --git a/drivers/i2c/bmp388_driver.go b/drivers/i2c/bmp388_driver.go index 732ec1ef..612cbd91 100644 --- a/drivers/i2c/bmp388_driver.go +++ b/drivers/i2c/bmp388_driver.go @@ -4,57 +4,66 @@ import ( "bytes" "encoding/binary" "fmt" + "log" "math" - - "gobot.io/x/gobot" ) +const bmp388Debug = false + +// the default address is applicable for SDO to VDD, for SDO to GND it will be 0x76 +const bmp388DefaultAddress = 0x77 + +// BMP388Accuracy accuracy type +type BMP388Accuracy uint8 +type BMP388IIRFilter uint8 + const ( bmp388ChipID = 0x50 - bmp388RegisterChipID = 0x00 - bmp388RegisterStatus = 0x03 - bmp388RegisterConfig = 0x1F - bmp388RegisterPressureData = 0x04 - bmp388RegisterTempData = 0x07 - bmp388RegisterCalib00 = 0x31 - bmp388RegisterCMD = 0x7E - // CMD : 0x00 nop (reserved. No command.) - // : 0x34 extmode_en_middle - // : 0xB0 fifo_flush (Clears all data in the FIFO, does not change FIFO_CONFIG registers) - // : 0xB6 softreset (Triggers a reset, all user configuration settings are overwritten with their default state) - bmp388RegisterODR = 0x1D // Output Data Rates - bmp388RegisterOSR = 0x1C // Oversampling Rates + bmp388RegChipID = 0x00 + bmp388RegStatus = 0x03 + bmp388RegPressureData = 0x04 // XLSB, 0x05 LSByte, 0x06 MSByte + bmp388RegTempData = 0x07 // XLSB, 0x08 LSByte, 0x09 MSByte + bmp388RegPWRCTRL = 0x1B // enable/disable pressure and temperature measurement, mode + bmp388RegOSR = 0x1C // Oversampling Rates + bmp388RegODR = 0x1D // Output Data Rates + bmp388RegConf = 0x1F // config filter for IIR coefficients + bmp388RegCalib00 = 0x31 + bmp388RegCMD = 0x7E - bmp388RegisterPWRCTRL = 0x1B - bmp388PWRCTRLSleep = 0 - bmp388PWRCTRLForced = 1 - bmp388PWRCTRLNormal = 3 + // bits 0, 1 of control register + bmp388PWRCTRLPressEnableBit = 0x01 + bmp388PWRCTRLTempEnableBit = 0x02 - bmp388SeaLevelPressure = 1013.25 + // bits 4, 5 of control register (will be shifted on write) + bmp388PWRCTRLSleep = 0x00 + bmp388PWRCTRLForced = 0x01 // same as 0x02 + bmp388PWRCTRLNormal = 0x03 - // IIR filter coefficients - bmp388IIRFIlterCoef0 = 0 // bypass-mode - bmp388IIRFIlterCoef1 = 1 - bmp388IIRFIlterCoef3 = 2 - bmp388IIRFIlterCoef7 = 3 - bmp388IIRFIlterCoef15 = 4 - bmp388IIRFIlterCoef31 = 5 - bmp388IIRFIlterCoef63 = 6 - bmp388IIRFIlterCoef127 = 7 -) + // bits 1, 2 ,3 of config filter IIR filter coefficients (will be shifted on write) + bmp388ConfFilterCoef0 BMP388IIRFilter = 0 // bypass-mode + bmp388ConfFilterCoef1 BMP388IIRFilter = 1 + bmp388ConfFilterCoef3 BMP388IIRFilter = 2 + bmp388ConfFilterCoef7 BMP388IIRFilter = 3 + bmp388ConfFilterCoef15 BMP388IIRFilter = 4 + bmp388ConfFilterCoef31 BMP388IIRFilter = 5 + bmp388ConfFilterCoef63 BMP388IIRFilter = 6 + bmp388ConfFilterCoef127 BMP388IIRFilter = 7 -// BMP388Accuracy accuracy type -type BMP388Accuracy uint8 - -// BMP388Accuracy accuracy modes -const ( + // oversampling rate, a single value is used (could be different for pressure and temperature) BMP388AccuracyUltraLow BMP388Accuracy = 0 // x1 sample BMP388AccuracyLow BMP388Accuracy = 1 // x2 samples BMP388AccuracyStandard BMP388Accuracy = 2 // x4 samples BMP388AccuracyHigh BMP388Accuracy = 3 // x8 samples BMP388AccuracyUltraHigh BMP388Accuracy = 4 // x16 samples BMP388AccuracyHighest BMP388Accuracy = 5 // x32 samples + + bmp388CMDReserved = 0x00 // reserved, no command + bmp388CMDExtModeEnMiddle = 0x34 + bmp388CMDFifoFlush = 0xB0 // clears all data in the FIFO, does not change FIFO_CONFIG registers + bmp388CMDSoftReset = 0xB6 // triggers a reset, all user configuration settings are overwritten with their default state + + bmp388SeaLevelPressure = 1013.25 ) type bmp388CalibrationCoefficients struct { @@ -76,96 +85,63 @@ type bmp388CalibrationCoefficients struct { // BMP388Driver is a driver for the BMP388 temperature/pressure sensor type BMP388Driver struct { - name string - connector Connector - connection Connection - Config - - tpc *bmp388CalibrationCoefficients + *Driver + calCoeffs *bmp388CalibrationCoefficients + ctrlPwrMode uint8 + confFilter BMP388IIRFilter } // NewBMP388Driver 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 NewBMP388Driver(c Connector, options ...func(Config)) *BMP388Driver { - b := &BMP388Driver{ - name: gobot.DefaultName("BMP388"), - connector: c, - Config: NewConfig(), - tpc: &bmp388CalibrationCoefficients{}, + d := &BMP388Driver{ + Driver: NewDriver(c, "BMP388", bmp388DefaultAddress), + calCoeffs: &bmp388CalibrationCoefficients{}, + ctrlPwrMode: bmp388PWRCTRLForced, + confFilter: bmp388ConfFilterCoef0, } + d.afterStart = d.initialization for _, option := range options { - option(b) + option(d) } // TODO: expose commands to API - return b + return d } -// Name returns the name of the device. -func (d *BMP388Driver) Name() string { - return d.name -} - -// SetName sets the name of the device. -func (d *BMP388Driver) SetName(n string) { - d.name = n -} - -// Connection returns the connection of the device. -func (d *BMP388Driver) Connection() gobot.Connection { - return d.connector.(gobot.Connection) -} - -// Start initializes the BMP388 and loads the calibration coefficients. -func (d *BMP388Driver) Start() (err error) { - var chipID uint8 - - bus := d.GetBusOrDefault(d.connector.GetDefaultBus()) - address := d.GetAddressOrDefault(bmp180Address) - - if d.connection, err = d.connector.GetConnection(address, bus); err != nil { - return err +// WithBMP388IIRFilter option sets count of IIR filter coefficients. +// Valid settings are of type "BMP388IIRFilter" +func WithBMP388IIRFilter(val BMP388IIRFilter) func(Config) { + return func(c Config) { + if d, ok := c.(*BMP388Driver); ok { + d.confFilter = val + } else if bmp388Debug { + log.Printf("Trying to set IIR filter for non-BMP388Driver %v", c) + } } - - if chipID, err = d.connection.ReadByteData(bmp388RegisterChipID); err != nil { - return err - } - - if bmp388ChipID != chipID { - return fmt.Errorf("Incorrect BMP388 chip ID '0%x' Expected 0x%x", chipID, bmp388ChipID) - } - - if err := d.initialization(); err != nil { - return err - } - - return nil -} - -// Halt halts the device. -func (d *BMP388Driver) Halt() (err error) { - return nil } // Temperature returns the current temperature, in celsius degrees. func (d *BMP388Driver) Temperature(accuracy BMP388Accuracy) (temp float32, err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + var rawT int32 - // Enable Pressure and Temperature measurement, set FORCED operating mode - var mode byte = (bmp388PWRCTRLForced << 4) | 3 // 1100|1|1 == mode|T|P - if err = d.connection.WriteByteData(bmp388RegisterPWRCTRL, mode); err != nil { + mode := uint8(d.ctrlPwrMode)<<4 | bmp388PWRCTRLPressEnableBit | bmp388PWRCTRLTempEnableBit + if err = d.connection.WriteByteData(bmp388RegPWRCTRL, mode); err != nil { return 0, err } // Set Accuracy for temperature - if err = d.connection.WriteByteData(bmp388RegisterOSR, uint8(accuracy<<3)); err != nil { + if err = d.connection.WriteByteData(bmp388RegOSR, uint8(accuracy<<3)); err != nil { return 0, err } @@ -178,16 +154,18 @@ func (d *BMP388Driver) Temperature(accuracy BMP388Accuracy) (temp float32, err e // Pressure returns the current barometric pressure, in Pa func (d *BMP388Driver) Pressure(accuracy BMP388Accuracy) (press float32, err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + var rawT, rawP int32 - // Enable Pressure and Temperature measurement, set FORCED operating mode - var mode byte = (bmp388PWRCTRLForced << 4) | 3 // 1100|1|1 == mode|T|P - if err = d.connection.WriteByteData(bmp388RegisterPWRCTRL, mode); err != nil { + mode := uint8(d.ctrlPwrMode)<<4 | bmp388PWRCTRLPressEnableBit | bmp388PWRCTRLTempEnableBit + if err = d.connection.WriteByteData(bmp388RegPWRCTRL, mode); err != nil { return 0, err } // Set Standard Accuracy for pressure - if err = d.connection.WriteByteData(bmp388RegisterOSR, uint8(accuracy)); err != nil { + if err = d.connection.WriteByteData(bmp388RegOSR, uint8(accuracy)); err != nil { return 0, err } @@ -215,25 +193,34 @@ func (d *BMP388Driver) Altitude(accuracy BMP388Accuracy) (alt float32, err error // initialization reads the calibration coefficients. func (d *BMP388Driver) initialization() (err error) { + var chipID uint8 + if chipID, err = d.connection.ReadByteData(bmp388RegChipID); err != nil { + return err + } + + if bmp388ChipID != chipID { + return fmt.Errorf("Incorrect BMP388 chip ID '0%x' Expected 0x%x", chipID, bmp388ChipID) + } + var ( - coefficients []byte - t1 uint16 - t2 uint16 - t3 int8 - p1 int16 - p2 int16 - p3 int8 - p4 int8 - p5 uint16 - p6 uint16 - p7 int8 - p8 int8 - p9 int16 - p10 int8 - p11 int8 + t1 uint16 + t2 uint16 + t3 int8 + p1 int16 + p2 int16 + p3 int8 + p4 int8 + p5 uint16 + p6 uint16 + p7 int8 + p8 int8 + p9 int16 + p10 int8 + p11 int8 ) - if coefficients, err = d.read(bmp388RegisterCalib00, 24); err != nil { + coefficients := make([]byte, 24) + if err = d.connection.ReadBlockData(bmp388RegCalib00, coefficients); err != nil { return err } buf := bytes.NewBuffer(coefficients) @@ -253,29 +240,26 @@ func (d *BMP388Driver) initialization() (err error) { binary.Read(buf, binary.LittleEndian, &p10) binary.Read(buf, binary.LittleEndian, &p11) - d.tpc.t1 = float32(float64(t1) / math.Pow(2, -8)) - d.tpc.t2 = float32(float64(t2) / math.Pow(2, 30)) - d.tpc.t3 = float32(float64(t3) / math.Pow(2, 48)) - d.tpc.p1 = float32((float64(p1) - math.Pow(2, 14)) / math.Pow(2, 20)) - d.tpc.p2 = float32((float64(p2) - math.Pow(2, 14)) / math.Pow(2, 29)) - d.tpc.p3 = float32(float64(p3) / math.Pow(2, 32)) - d.tpc.p4 = float32(float64(p4) / math.Pow(2, 37)) - d.tpc.p5 = float32(float64(p5) / math.Pow(2, -3)) - d.tpc.p6 = float32(float64(p6) / math.Pow(2, 6)) - d.tpc.p7 = float32(float64(p7) / math.Pow(2, 8)) - d.tpc.p8 = float32(float64(p8) / math.Pow(2, 15)) - d.tpc.p9 = float32(float64(p9) / math.Pow(2, 48)) - d.tpc.p10 = float32(float64(p10) / math.Pow(2, 48)) - d.tpc.p11 = float32(float64(p11) / math.Pow(2, 65)) + d.calCoeffs.t1 = float32(float64(t1) / math.Pow(2, -8)) + d.calCoeffs.t2 = float32(float64(t2) / math.Pow(2, 30)) + d.calCoeffs.t3 = float32(float64(t3) / math.Pow(2, 48)) + d.calCoeffs.p1 = float32((float64(p1) - math.Pow(2, 14)) / math.Pow(2, 20)) + d.calCoeffs.p2 = float32((float64(p2) - math.Pow(2, 14)) / math.Pow(2, 29)) + d.calCoeffs.p3 = float32(float64(p3) / math.Pow(2, 32)) + d.calCoeffs.p4 = float32(float64(p4) / math.Pow(2, 37)) + d.calCoeffs.p5 = float32(float64(p5) / math.Pow(2, -3)) + d.calCoeffs.p6 = float32(float64(p6) / math.Pow(2, 6)) + d.calCoeffs.p7 = float32(float64(p7) / math.Pow(2, 8)) + d.calCoeffs.p8 = float32(float64(p8) / math.Pow(2, 15)) + d.calCoeffs.p9 = float32(float64(p9) / math.Pow(2, 48)) + d.calCoeffs.p10 = float32(float64(p10) / math.Pow(2, 48)) + d.calCoeffs.p11 = float32(float64(p11) / math.Pow(2, 65)) - // Perform a power on reset. All user configuration settings are overwritten - // with their default state. - if err = d.connection.WriteByteData(bmp388RegisterCMD, 0xB6); err != nil { + if err = d.connection.WriteByteData(bmp388RegCMD, bmp388CMDSoftReset); err != nil { return err } - // set IIR filter to off - if err = d.connection.WriteByteData(bmp388RegisterConfig, bmp388IIRFIlterCoef0<<1); err != nil { + if err = d.connection.WriteByteData(bmp388RegConf, uint8(d.confFilter)<<1); err != nil { return err } @@ -283,10 +267,10 @@ func (d *BMP388Driver) initialization() (err error) { } func (d *BMP388Driver) rawTemp() (temp int32, err error) { - var data []byte var tp0, tp1, tp2 byte - if data, err = d.read(bmp388RegisterTempData, 3); err != nil { + data := make([]byte, 3) + if err = d.connection.ReadBlockData(bmp388RegTempData, data); err != nil { return 0, err } buf := bytes.NewBuffer(data) @@ -300,10 +284,10 @@ func (d *BMP388Driver) rawTemp() (temp int32, err error) { } func (d *BMP388Driver) rawPressure() (press int32, err error) { - var data []byte var tp0, tp1, tp2 byte - if data, err = d.read(bmp388RegisterPressureData, 3); err != nil { + data := make([]byte, 3) + if err = d.connection.ReadBlockData(bmp388RegPressureData, data); err != nil { return 0, err } buf := bytes.NewBuffer(data) @@ -319,43 +303,31 @@ func (d *BMP388Driver) rawPressure() (press int32, err error) { func (d *BMP388Driver) calculateTemp(rawTemp int32) float32 { // datasheet, sec 9.2 Temperature compensation - pd1 := float32(rawTemp) - d.tpc.t1 - pd2 := pd1 * d.tpc.t2 + pd1 := float32(rawTemp) - d.calCoeffs.t1 + pd2 := pd1 * d.calCoeffs.t2 - temperatureComp := pd2 + (pd1*pd1)*d.tpc.t3 + temperatureComp := pd2 + (pd1*pd1)*d.calCoeffs.t3 return temperatureComp } func (d *BMP388Driver) calculatePress(rawPress int32, tLin float64) float32 { - pd1 := float64(d.tpc.p6) * tLin - pd2 := float64(d.tpc.p7) * math.Pow(tLin, 2) - pd3 := float64(d.tpc.p8) * math.Pow(tLin, 3) - po1 := float64(d.tpc.p5) + pd1 + pd2 + pd3 + pd1 := float64(d.calCoeffs.p6) * tLin + pd2 := float64(d.calCoeffs.p7) * math.Pow(tLin, 2) + pd3 := float64(d.calCoeffs.p8) * math.Pow(tLin, 3) + po1 := float64(d.calCoeffs.p5) + pd1 + pd2 + pd3 - pd1 = float64(d.tpc.p2) * tLin - pd2 = float64(d.tpc.p3) * math.Pow(tLin, 2) - pd3 = float64(d.tpc.p4) * math.Pow(tLin, 3) - po2 := float64(rawPress) * (float64(d.tpc.p1) + pd1 + pd2 + pd3) + pd1 = float64(d.calCoeffs.p2) * tLin + pd2 = float64(d.calCoeffs.p3) * math.Pow(tLin, 2) + pd3 = float64(d.calCoeffs.p4) * math.Pow(tLin, 3) + po2 := float64(rawPress) * (float64(d.calCoeffs.p1) + pd1 + pd2 + pd3) pd1 = math.Pow(float64(rawPress), 2) - pd2 = float64(d.tpc.p9) + float64(d.tpc.p10)*tLin + pd2 = float64(d.calCoeffs.p9) + float64(d.calCoeffs.p10)*tLin pd3 = pd1 * pd2 - pd4 := pd3 + math.Pow(float64(rawPress), 3)*float64(d.tpc.p11) + pd4 := pd3 + math.Pow(float64(rawPress), 3)*float64(d.calCoeffs.p11) pressure := po1 + po2 + pd4 return float32(pressure) } - -func (d *BMP388Driver) read(address byte, n int) ([]byte, error) { - if _, err := d.connection.Write([]byte{address}); err != nil { - return nil, err - } - buf := make([]byte, n) - bytesRead, err := d.connection.Read(buf) - if bytesRead != n || err != nil { - return nil, err - } - return buf, nil -} diff --git a/drivers/i2c/bmp388_driver_test.go b/drivers/i2c/bmp388_driver_test.go index ba7b24ed..732ea5d7 100644 --- a/drivers/i2c/bmp388_driver_test.go +++ b/drivers/i2c/bmp388_driver_test.go @@ -4,171 +4,197 @@ import ( "bytes" "encoding/binary" "errors" + "strings" "testing" "gobot.io/x/gobot" "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 = (*BMP388Driver)(nil) -// --------- HELPERS -func initTestBMP388Driver() (driver *BMP388Driver) { - driver, _ = initTestBMP388DriverWithStubbedAdaptor() - return -} +func initTestBMP388WithStubbedAdaptor() (*BMP388Driver, *i2cTestAdaptor) { + a := newI2cTestAdaptor() -func initTestBMP388DriverWithStubbedAdaptor() (*BMP388Driver, *i2cTestAdaptor) { - adaptor := newI2cTestAdaptor() - adaptor.i2cReadImpl = func(b []byte) (int, error) { - buf := new(bytes.Buffer) - // Simulate returning of 0x50 for the - // ReadByteData(bmp388RegisterChipID) call in initialisation() - binary.Write(buf, binary.LittleEndian, uint8(0x50)) - copy(b, buf.Bytes()) - return buf.Len(), nil + readCallCounter := 0 + a.i2cReadImpl = func(b []byte) (int, error) { + readCallCounter++ + if readCallCounter == 1 { + buf := new(bytes.Buffer) + // Simulate returning of 0x50 for the + // ReadByteData(bmp388RegChipID) call in initialisation() + binary.Write(buf, binary.LittleEndian, uint8(0x50)) + copy(b, buf.Bytes()) + return buf.Len(), nil + } + if readCallCounter == 2 { + // Simulate returning 24 bytes for the coefficients (register bmp388RegCalib00) + return 24, nil + } + return 0, nil } - return NewBMP388Driver(adaptor), adaptor + return NewBMP388Driver(a), a } -// --------- TESTS - func TestNewBMP388Driver(t *testing.T) { - // Does it return a pointer to an instance of BMP388Driver? - var bmp388 interface{} = NewBMP388Driver(newI2cTestAdaptor()) - _, ok := bmp388.(*BMP388Driver) + var di interface{} = NewBMP388Driver(newI2cTestAdaptor()) + d, ok := di.(*BMP388Driver) if !ok { t.Errorf("NewBMP388Driver() should have returned a *BMP388Driver") } + gobottest.Refute(t, d.Driver, nil) + gobottest.Assert(t, strings.HasPrefix(d.Name(), "BMP388"), true) + gobottest.Assert(t, d.defaultAddress, 0x77) + gobottest.Assert(t, d.ctrlPwrMode, uint8(0x01)) // forced mode + gobottest.Assert(t, d.confFilter, BMP388IIRFilter(0x00)) // filter off + gobottest.Refute(t, d.calCoeffs, nil) } -func TestBMP388Driver(t *testing.T) { - bmp388 := initTestBMP388Driver() - gobottest.Refute(t, bmp388.Connection(), nil) +func TestBMP388Options(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 := NewBMP388Driver(newI2cTestAdaptor(), WithBus(2), WithBMP388IIRFilter(BMP388IIRFilter(0x03))) + gobottest.Assert(t, d.GetBusOrDefault(1), 2) + gobottest.Assert(t, d.confFilter, BMP388IIRFilter(0x03)) } -func TestBMP388DriverStart(t *testing.T) { - bmp388, _ := initTestBMP388DriverWithStubbedAdaptor() - gobottest.Assert(t, bmp388.Start(), nil) -} - -func TestBMP388StartConnectError(t *testing.T) { - bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor() - adaptor.Testi2cConnectErr(true) - gobottest.Assert(t, bmp388.Start(), errors.New("Invalid i2c connection")) -} - -func TestBMP388DriverStartWriteError(t *testing.T) { - bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor() - adaptor.i2cWriteImpl = func([]byte) (int, error) { - return 0, errors.New("write error") - } - gobottest.Assert(t, bmp388.Start(), errors.New("write error")) -} - -func TestBMP388DriverStartReadError(t *testing.T) { - bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor() - adaptor.i2cReadImpl = func(b []byte) (int, error) { - return 0, errors.New("read error") - } - gobottest.Assert(t, bmp388.Start(), errors.New("read error")) -} - -func TestBMP388DriverHalt(t *testing.T) { - bmp388 := initTestBMP388Driver() - - gobottest.Assert(t, bmp388.Halt(), nil) -} - -func TestBMP388DriverMeasurements(t *testing.T) { - bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor() - adaptor.i2cReadImpl = func(b []byte) (int, error) { +func TestBMP388Measurements(t *testing.T) { + d, a := initTestBMP388WithStubbedAdaptor() + a.i2cReadImpl = func(b []byte) (int, error) { buf := new(bytes.Buffer) - lastWritten := adaptor.written[len(adaptor.written)-1] + lastWritten := a.written[len(a.written)-1] switch lastWritten { - case bmp388RegisterChipID: + case bmp388RegChipID: // Simulate returning of 0x50 for the - // ReadByteData(bmp388RegisterChipID) call in initialisation() + // ReadByteData(bmp388RegChipID) call in initialisation() binary.Write(buf, binary.LittleEndian, uint8(0x50)) - case bmp388RegisterCalib00: + case bmp388RegCalib00: // Values produced by dumping data from actual sensor buf.Write([]byte{36, 107, 156, 73, 246, 104, 255, 189, 245, 35, 0, 151, 101, 184, 122, 243, 246, 211, 64, 14, 196, 0, 0, 0}) - case bmp388RegisterTempData: + case bmp388RegTempData: buf.Write([]byte{0, 28, 127}) - case bmp388RegisterPressureData: + case bmp388RegPressureData: buf.Write([]byte{0, 66, 113}) } copy(b, buf.Bytes()) return buf.Len(), nil } - bmp388.Start() - temp, err := bmp388.Temperature(2) + d.Start() + temp, err := d.Temperature(2) gobottest.Assert(t, err, nil) gobottest.Assert(t, temp, float32(22.906143)) - pressure, err := bmp388.Pressure(2) + pressure, err := d.Pressure(2) gobottest.Assert(t, err, nil) gobottest.Assert(t, pressure, float32(98874.85)) - alt, err := bmp388.Altitude(2) + alt, err := d.Altitude(2) gobottest.Assert(t, err, nil) gobottest.Assert(t, alt, float32(205.89395)) } -func TestBMP388DriverTemperatureWriteError(t *testing.T) { - bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor() - bmp388.Start() +func TestBMP388TemperatureWriteError(t *testing.T) { + d, a := initTestBMP388WithStubbedAdaptor() + d.Start() - adaptor.i2cWriteImpl = func([]byte) (int, error) { + a.i2cWriteImpl = func([]byte) (int, error) { return 0, errors.New("write error") } - temp, err := bmp388.Temperature(2) + temp, err := d.Temperature(2) gobottest.Assert(t, err, errors.New("write error")) gobottest.Assert(t, temp, float32(0.0)) } -func TestBMP388DriverTemperatureReadError(t *testing.T) { - bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor() - bmp388.Start() +func TestBMP388TemperatureReadError(t *testing.T) { + d, a := initTestBMP388WithStubbedAdaptor() + d.Start() - adaptor.i2cReadImpl = func([]byte) (int, error) { + a.i2cReadImpl = func([]byte) (int, error) { return 0, errors.New("read error") } - temp, err := bmp388.Temperature(2) + temp, err := d.Temperature(2) gobottest.Assert(t, err, errors.New("read error")) gobottest.Assert(t, temp, float32(0.0)) } -func TestBMP388DriverPressureWriteError(t *testing.T) { - bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor() - bmp388.Start() +func TestBMP388PressureWriteError(t *testing.T) { + d, a := initTestBMP388WithStubbedAdaptor() + d.Start() - adaptor.i2cWriteImpl = func([]byte) (int, error) { + a.i2cWriteImpl = func([]byte) (int, error) { return 0, errors.New("write error") } - press, err := bmp388.Pressure(2) + press, err := d.Pressure(2) gobottest.Assert(t, err, errors.New("write error")) gobottest.Assert(t, press, float32(0.0)) } -func TestBMP388DriverPressureReadError(t *testing.T) { - bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor() - bmp388.Start() +func TestBMP388PressureReadError(t *testing.T) { + d, a := initTestBMP388WithStubbedAdaptor() + d.Start() - adaptor.i2cReadImpl = func([]byte) (int, error) { + a.i2cReadImpl = func([]byte) (int, error) { return 0, errors.New("read error") } - press, err := bmp388.Pressure(2) + press, err := d.Pressure(2) gobottest.Assert(t, err, errors.New("read error")) gobottest.Assert(t, press, float32(0.0)) } -func TestBMP388DriverSetName(t *testing.T) { - b := initTestBMP388Driver() - b.SetName("TESTME") - gobottest.Assert(t, b.Name(), "TESTME") -} - -func TestBMP388DriverOptions(t *testing.T) { - b := NewBMP388Driver(newI2cTestAdaptor(), WithBus(2)) - gobottest.Assert(t, b.GetBusOrDefault(1), 2) +func TestBMP388_initialization(t *testing.T) { + // sequence to read and write in initialization(): + // * read chip ID register (0x00) and compare + // * read 24 bytes (12 x 16 bit calibration data), starting from TC1 register (0x31) + // * fill calibration struct with data (LSByte read first) + // * perform a soft reset by command register (0x7E) + // * prepare the content of config register + // * write the config register (0x1F) + // arrange + d, a := initTestBMP388WithStubbedAdaptor() + a.written = []byte{} // reset writes of former test + const ( + wantChipIDReg = uint8(0x00) + wantCalibReg = uint8(0x31) + wantCommandReg = uint8(0x7E) + wantCommandRegVal = uint8(0xB6) // soft reset + wantConfReg = uint8(0x1F) + wantConfRegVal = uint8(0x00) // no filter + ) + // Values produced by dumping data from actual sensor + returnRead := []byte{36, 107, 156, 73, 246, 104, 255, 189, 245, 35, 0, 151, 101, 184, 122, 243, 246, 211, 64, 14, 196, 0, 0, 0} + numCallsRead := 0 + a.i2cReadImpl = func(b []byte) (int, error) { + numCallsRead++ + if numCallsRead == 1 { + b[0] = 0x50 + } else { + copy(b, returnRead) + } + return len(b), nil + } + // act, assert - initialization() must be called on Start() + err := d.Start() + // assert + gobottest.Assert(t, err, nil) + gobottest.Assert(t, numCallsRead, 2) + gobottest.Assert(t, len(a.written), 6) + gobottest.Assert(t, a.written[0], wantChipIDReg) + gobottest.Assert(t, a.written[1], wantCalibReg) + gobottest.Assert(t, a.written[2], wantCommandReg) + gobottest.Assert(t, a.written[3], wantCommandRegVal) + gobottest.Assert(t, a.written[4], wantConfReg) + gobottest.Assert(t, a.written[5], wantConfRegVal) + gobottest.Assert(t, d.calCoeffs.t1, float32(7.021568e+06)) + gobottest.Assert(t, d.calCoeffs.t2, float32(1.7549843e-05)) + gobottest.Assert(t, d.calCoeffs.t3, float32(-3.5527137e-14)) + gobottest.Assert(t, d.calCoeffs.p1, float32(-0.015769958)) + gobottest.Assert(t, d.calCoeffs.p2, float32(-3.5410747e-05)) + gobottest.Assert(t, d.calCoeffs.p3, float32(8.1490725e-09)) + gobottest.Assert(t, d.calCoeffs.p4, float32(0)) + gobottest.Assert(t, d.calCoeffs.p5, float32(208056)) + gobottest.Assert(t, d.calCoeffs.p6, float32(490.875)) + gobottest.Assert(t, d.calCoeffs.p7, float32(-0.05078125)) + gobottest.Assert(t, d.calCoeffs.p8, float32(-0.00030517578)) + gobottest.Assert(t, d.calCoeffs.p9, float32(5.8957283e-11)) } diff --git a/examples/tinkerboard_bme280.go b/examples/tinkerboard_bme280.go new file mode 100644 index 00000000..f6bb2068 --- /dev/null +++ b/examples/tinkerboard_bme280.go @@ -0,0 +1,67 @@ +// +build example +// +// Do not build by default. + +package main + +import ( + "fmt" + "time" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/drivers/i2c" + "gobot.io/x/gobot/platforms/tinkerboard" +) + +// Wiring +// PWR Tinkerboard: 1 (+3.3V, VCC), 2(+5V), 6, 9, 14, 20 (GND) +// I2C1 Tinkerboard: 3 (SDA-ws), 5 (SCL-gn) +// BME280 plate: VIN (2.5..5V), GND, SDL, SDA, SDO to GND +func main() { + a := tinkerboard.NewAdaptor() + bme280 := i2c.NewBME280Driver(a, i2c.WithAddress(0x76), + i2c.WithBME280PressureOversampling(0x05), + i2c.WithBME280TemperatureOversampling(0x02), + i2c.WithBME280HumidityOversampling(0x01), + i2c.WithBME280IIRFilter(0x05)) + + work := func() { + gobot.Every(2*time.Second, func() { + t, e := bme280.Temperature() + fmt.Println("Temperature [°C]", t) + if e != nil { + fmt.Println(e) + } + + p, e := bme280.Pressure() + fmt.Println("Pressure [Pa]", p) // 100hPa = 1Pa + if e != nil { + fmt.Println(e) + } + + a, e := bme280.Altitude() + fmt.Println("Altitude [m]", a) + if e != nil { + fmt.Println(e) + } + + h, e := bme280.Humidity() + fmt.Println("Humidity [%]", h) + if e != nil { + fmt.Println(e) + } + fmt.Println("-------------") + }) + } + + robot := gobot.NewRobot("bme280bot", + []gobot.Connection{a}, + []gobot.Device{bme280}, + work, + ) + + err := robot.Start() + if err != nil { + fmt.Println(err) + } +}