CCS811 use ReadBlockData()

This commit is contained in:
Thomas Kohler 2022-10-10 09:25:17 +02:00 committed by GitHub
commit 91f94d9d4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 185 additions and 193 deletions

View File

@ -4,8 +4,6 @@ import (
"fmt"
"math"
"time"
"gobot.io/x/gobot"
)
// CCS811DriveMode type
@ -22,7 +20,7 @@ const (
const (
//DefaultAddress is the default I2C address for the ccs811
//the default I2C address for the ccs811 applies for ADDR to GND, for ADDR to VDD it will be 0x5B
ccs811DefaultAddress = 0x5A
//Registers, all definitions have been taken from the datasheet
@ -74,7 +72,7 @@ type CCS811Status struct {
FwMode byte
}
//NewCCS811Status returns a new instance of the package ccs811 status definiton
//NewCCS811Status returns a new instance of the package ccs811 status definition
func NewCCS811Status(data uint8) *CCS811Status {
return &CCS811Status{
HasError: data & 0x01,
@ -88,16 +86,16 @@ func NewCCS811Status(data uint8) *CCS811Status {
//The following definitions were taken from the bit fields of the ccs811RegMeasMode defined in
//https://ams.com/documents/20143/36005/CCS811_DS000459_6-00.pdf/c7091525-c7e5-37ac-eedb-b6c6828b0dcf#page=16
type CCS811MeasMode struct {
//If intThresh is 1 a data measurement will only be taken when the sensor value mets the threshold constraint.
//If intThresh is 1 a data measurement will only be taken when the sensor value meets the threshold constraint.
//The threshold value is set in the threshold register (0x10)
intThresh uint8
//If intDataRdy is 1, the nINT signal (pin 3 of the device) will be driven low when new data is avaliable.
//If intDataRdy is 1, the nINT signal (pin 3 of the device) will be driven low when new data is available.
intDataRdy uint8
//driveMode represents the sampling rate of the sensor. If the value is 0, the measurement process is idle.
driveMode CCS811DriveMode
}
//NewCCS811MeasMode returns a new instance of the package ccs811 measurement mode configuration. This represents the desired intial
//NewCCS811MeasMode returns a new instance of the package ccs811 measurement mode configuration. This represents the desired initial
//state of the measurement mode register.
func NewCCS811MeasMode() *CCS811MeasMode {
return &CCS811MeasMode{
@ -115,30 +113,26 @@ func (mm *CCS811MeasMode) GetMeasMode() byte {
//CCS811Driver is the Gobot driver for the CCS811 (air quality sensor) Adafruit breakout board
type CCS811Driver struct {
name string
connector Connector
connection Connection
*Driver
measMode *CCS811MeasMode
ntcResistanceValue uint32
Config
}
//NewCCS811Driver creates a new driver for the CCS811 (air quality sensor)
func NewCCS811Driver(a Connector, options ...func(Config)) *CCS811Driver {
l := &CCS811Driver{
name: gobot.DefaultName("CCS811"),
connector: a,
measMode: NewCCS811MeasMode(),
func NewCCS811Driver(c Connector, options ...func(Config)) *CCS811Driver {
d := &CCS811Driver{
Driver: NewDriver(c, "CCS811", ccs811DefaultAddress),
measMode: NewCCS811MeasMode(),
//Recommended resistance value is 100,000
ntcResistanceValue: 100000,
Config: NewConfig(),
}
d.afterStart = d.initialize
for _, option := range options {
option(l)
option(d)
}
return l
return d
}
//WithCCS811MeasMode sets the sampling rate of the device
@ -158,32 +152,11 @@ func WithCCS811NTCResistance(val uint32) func(Config) {
}
}
//Start initializes the sensor
func (d *CCS811Driver) Start() (err error) {
bus := d.GetBusOrDefault(d.connector.GetDefaultBus())
address := d.GetAddressOrDefault(ccs811DefaultAddress)
if d.connection, err = d.connector.GetConnection(address, bus); err != nil {
return err
}
return d.initialize()
}
//Name returns the Name for the Driver
func (d *CCS811Driver) Name() string { return d.name }
//SetName sets the Name for the Driver
func (d *CCS811Driver) SetName(n string) { d.name = n }
//Connection returns the connection for the Driver
func (d *CCS811Driver) Connection() gobot.Connection { return d.connector.(gobot.Connection) }
//Halt returns true if devices is halted successfully
func (d *CCS811Driver) Halt() (err error) { return }
//GetHardwareVersion returns the hardware version of the device in the form of 0x1X
func (d *CCS811Driver) GetHardwareVersion() (uint8, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
v, err := d.connection.ReadByteData(ccs811RegHwVersion)
if err != nil {
return 0, err
@ -194,6 +167,9 @@ func (d *CCS811Driver) GetHardwareVersion() (uint8, error) {
//GetFirmwareBootVersion returns the bootloader version
func (d *CCS811Driver) GetFirmwareBootVersion() (uint16, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
v, err := d.connection.ReadWordData(ccs811RegFwBootVersion)
if err != nil {
return 0, err
@ -204,6 +180,9 @@ func (d *CCS811Driver) GetFirmwareBootVersion() (uint16, error) {
//GetFirmwareAppVersion returns the app code version
func (d *CCS811Driver) GetFirmwareAppVersion() (uint16, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
v, err := d.connection.ReadWordData(ccs811RegFwAppVersion)
if err != nil {
return 0, err
@ -214,6 +193,9 @@ func (d *CCS811Driver) GetFirmwareAppVersion() (uint16, error) {
//GetStatus returns the current status of the device
func (d *CCS811Driver) GetStatus() (*CCS811Status, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
s, err := d.connection.ReadByteData(ccs811RegStatus)
if err != nil {
return nil, err
@ -226,8 +208,11 @@ func (d *CCS811Driver) GetStatus() (*CCS811Status, error) {
//GetTemperature returns the device temperature in celcius.
//If you do not have an NTC resistor installed, this function should not be called
func (d *CCS811Driver) GetTemperature() (float32, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
buf, err := d.read(ccs811RegNtc, 4)
buf := make([]byte, 4)
err := d.connection.ReadBlockData(ccs811RegNtc, buf)
if err != nil {
return 0, err
}
@ -248,8 +233,11 @@ func (d *CCS811Driver) GetTemperature() (float32, error) {
//GetGasData returns the data for the gas sensor.
//eco2 is returned in ppm and tvoc is returned in ppb
func (d *CCS811Driver) GetGasData() (uint16, uint16, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
data, err := d.read(ccs811RegAlgResultData, 4)
data := make([]byte, 4)
err := d.connection.ReadBlockData(ccs811RegAlgResultData, data)
if err != nil {
return 0, 0, err
}
@ -261,7 +249,7 @@ func (d *CCS811Driver) GetGasData() (uint16, uint16, error) {
return eco2, tvoC, nil
}
//HasData returns true if the device has not errored and temperature/gas data is avaliable
//HasData returns true if the device has not errored and temperature/gas data is available
func (d *CCS811Driver) HasData() (bool, error) {
s, err := d.GetStatus()
if err != nil {
@ -277,35 +265,22 @@ func (d *CCS811Driver) HasData() (bool, error) {
//EnableExternalInterrupt enables the external output hardware interrupt pin 3.
func (d *CCS811Driver) EnableExternalInterrupt() error {
d.mutex.Lock()
defer d.mutex.Unlock()
d.measMode.intDataRdy = 1
return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode())
}
//DisableExternalInterrupt disables the external output hardware interrupt pin 3.
func (d *CCS811Driver) DisableExternalInterrupt() error {
d.mutex.Lock()
defer d.mutex.Unlock()
d.measMode.intDataRdy = 0
return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode())
}
//updateMeasMode writes the current value of measMode to the measurement mode register.
func (d *CCS811Driver) updateMeasMode() error {
return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode())
}
//ResetDevice does a software reset of the device. After this operation is done,
//the user must start the app code before the sensor can take any measurements
func (d *CCS811Driver) resetDevice() error {
return d.connection.WriteBlockData(ccs811RegSwReset, ccs811SwResetSequence)
}
//startApp starts the app code in the device. This operation has to be done after a
//software reset to start taking sensor measurements.
func (d *CCS811Driver) startApp() error {
//Write without data is needed to start the app code
_, err := d.connection.Write([]byte{ccs811RegAppStart})
return err
}
func (d *CCS811Driver) initialize() error {
deviceID, err := d.connection.ReadByteData(ccs811RegHwID)
if err != nil {
@ -335,15 +310,21 @@ func (d *CCS811Driver) initialize() error {
return nil
}
// An implementation of the ReadBlockData i2c operation. This code was copied from the BMP280Driver code
func (d *CCS811Driver) read(reg byte, n int) ([]byte, error) {
if _, err := d.connection.Write([]byte{reg}); 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
//ResetDevice does a software reset of the device. After this operation is done,
//the user must start the app code before the sensor can take any measurements
func (d *CCS811Driver) resetDevice() error {
return d.connection.WriteBlockData(ccs811RegSwReset, ccs811SwResetSequence)
}
//startApp starts the app code in the device. This operation has to be done after a
//software reset to start taking sensor measurements.
func (d *CCS811Driver) startApp() error {
//Write without data is needed to start the app code
_, err := d.connection.Write([]byte{ccs811RegAppStart})
return err
}
//updateMeasMode writes the current value of measMode to the measurement mode register.
func (d *CCS811Driver) updateMeasMode() error {
return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode())
}

View File

@ -2,87 +2,57 @@ package i2c
import (
"errors"
"strings"
"testing"
"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
)
// The CCS811 Meets the Driver Definition
// 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 = (*CCS811Driver)(nil)
// --------- HELPERS
func initTestCCS811Driver() (driver *CCS811Driver) {
driver, _ = initTestCCS811DriverWithStubbedAdaptor()
return
func initTestCCS811WithStubbedAdaptor() (*CCS811Driver, *i2cTestAdaptor) {
a := newI2cTestAdaptor()
return NewCCS811Driver(a), a
}
func initTestCCS811DriverWithStubbedAdaptor() (*CCS811Driver, *i2cTestAdaptor) {
adaptor := newI2cTestAdaptor()
return NewCCS811Driver(adaptor), adaptor
}
// --------- BASE TESTS
func TestNewCCS811Driver(t *testing.T) {
// Does it return a pointer to an instance of CCS811Driver?
var c interface{} = NewCCS811Driver(newI2cTestAdaptor())
_, ok := c.(*CCS811Driver)
var di interface{} = NewCCS811Driver(newI2cTestAdaptor())
d, ok := di.(*CCS811Driver)
if !ok {
t.Errorf("NewCCS811Driver() should have returned a *CCS811Driver")
}
gobottest.Refute(t, d.Driver, nil)
gobottest.Assert(t, strings.HasPrefix(d.Name(), "CCS811"), true)
gobottest.Assert(t, d.defaultAddress, 0x5A)
gobottest.Refute(t, d.measMode, nil)
gobottest.Assert(t, d.ntcResistanceValue, uint32(100000))
}
func TestCCS811DriverSetName(t *testing.T) {
// Does it change the name of the driver
d := initTestCCS811Driver()
d.SetName("TESTME")
gobottest.Assert(t, d.Name(), "TESTME")
}
func TestCCS811Connection(t *testing.T) {
// Does it create an instance of gobot.Connection
ccs811 := initTestCCS811Driver()
gobottest.Refute(t, ccs811.Connection(), nil)
}
// // --------- CONFIG OVERIDE TESTS
func TestCCS811DriverWithBus(t *testing.T) {
// Can it update the bus
d := NewCCS811Driver(newI2cTestAdaptor(), WithBus(2))
func TestCCS811Options(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 := NewCCS811Driver(newI2cTestAdaptor(), WithBus(2), WithAddress(0xFF), WithCCS811NTCResistance(0xFF))
gobottest.Assert(t, d.GetBusOrDefault(1), 2)
}
func TestCCS811DriverWithAddress(t *testing.T) {
// Can it update the address
d := NewCCS811Driver(newI2cTestAdaptor(), WithAddress(0xFF))
gobottest.Assert(t, d.GetAddressOrDefault(0x5a), 0xFF)
gobottest.Assert(t, d.ntcResistanceValue, uint32(0xFF))
}
func TestCCS811DriverWithCCS811MeasMode(t *testing.T) {
// Can it update the measurement mode
func TestCCS811WithCCS811MeasMode(t *testing.T) {
d := NewCCS811Driver(newI2cTestAdaptor(), WithCCS811MeasMode(CCS811DriveMode10Sec))
gobottest.Assert(t, d.measMode.driveMode, CCS811DriveMode(CCS811DriveMode10Sec))
}
func TestCCS811DriverWithCCS811NTCResistance(t *testing.T) {
// Can it update the ntc resitor value used for temp calcuations
d := NewCCS811Driver(newI2cTestAdaptor(), WithCCS811NTCResistance(0xFF))
gobottest.Assert(t, d.ntcResistanceValue, uint32(0xFF))
}
// // --------- DRIVER SPECIFIC TESTS
func TestCCS811DriverGetGasData(t *testing.T) {
cases := []struct {
func TestCCS811GetGasData(t *testing.T) {
var tests = map[string]struct {
readReturn func([]byte) (int, error)
eco2 uint16
tvoc uint16
err error
}{
// Can it compute the gas data with ideal values taken from the bus
{
"ideal values taken from the bus": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{1, 156, 0, 86})
return 4, nil
@ -91,8 +61,7 @@ func TestCCS811DriverGetGasData(t *testing.T) {
tvoc: 86,
err: nil,
},
// Can it compute the gas data with the max values possible taken from the bus
{
"max values possible taken from the bus": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{255, 255, 255, 255})
return 4, nil
@ -101,8 +70,7 @@ func TestCCS811DriverGetGasData(t *testing.T) {
tvoc: 65535,
err: nil,
},
// Does it return an error when the i2c operation fails
{
"error when the i2c operation fails": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{255, 255, 255, 255})
return 4, errors.New("Error")
@ -112,31 +80,31 @@ func TestCCS811DriverGetGasData(t *testing.T) {
err: errors.New("Error"),
},
}
d, adaptor := initTestCCS811DriverWithStubbedAdaptor()
// Create stub function as it is needed by read submethod in driver code
adaptor.i2cWriteImpl = func([]byte) (int, error) { return 0, nil }
d.Start()
for _, c := range cases {
adaptor.i2cReadImpl = c.readReturn
eco2, tvoc, err := d.GetGasData()
gobottest.Assert(t, eco2, c.eco2)
gobottest.Assert(t, tvoc, c.tvoc)
gobottest.Assert(t, err, c.err)
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
d, a := initTestCCS811WithStubbedAdaptor()
// Create stub function as it is needed by read submethod in driver code
a.i2cWriteImpl = func([]byte) (int, error) { return 0, nil }
d.Start()
a.i2cReadImpl = tc.readReturn
// act
eco2, tvoc, err := d.GetGasData()
// assert
gobottest.Assert(t, eco2, tc.eco2)
gobottest.Assert(t, tvoc, tc.tvoc)
gobottest.Assert(t, err, tc.err)
})
}
}
func TestCCS811DriverGetTemperature(t *testing.T) {
cases := []struct {
func TestCCS811GetTemperature(t *testing.T) {
var tests = map[string]struct {
readReturn func([]byte) (int, error)
temp float32
err error
}{
// Can it compute the temperature data with ideal values taken from the bus
{
"ideal values taken from the bus": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{10, 197, 0, 248})
return 4, nil
@ -144,8 +112,7 @@ func TestCCS811DriverGetTemperature(t *testing.T) {
temp: 27.811005,
err: nil,
},
// Can it compute the temperature data without bus values overflowing
{
"without bus values overflowing": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{129, 197, 10, 248})
return 4, nil
@ -153,8 +120,7 @@ func TestCCS811DriverGetTemperature(t *testing.T) {
temp: 29.48822,
err: nil,
},
// Can it compute a negative temperature
{
"negative temperature": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{255, 255, 255, 255})
return 4, nil
@ -162,8 +128,7 @@ func TestCCS811DriverGetTemperature(t *testing.T) {
temp: -25.334152,
err: nil,
},
// Does it return an error if the i2c bus errors
{
"error if the i2c bus errors": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{129, 197, 0, 248})
return 4, errors.New("Error")
@ -172,30 +137,30 @@ func TestCCS811DriverGetTemperature(t *testing.T) {
err: errors.New("Error"),
},
}
d, adaptor := initTestCCS811DriverWithStubbedAdaptor()
// Create stub function as it is needed by read submethod in driver code
adaptor.i2cWriteImpl = func([]byte) (int, error) { return 0, nil }
d.Start()
for _, c := range cases {
adaptor.i2cReadImpl = c.readReturn
temp, err := d.GetTemperature()
gobottest.Assert(t, temp, c.temp)
gobottest.Assert(t, err, c.err)
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
d, a := initTestCCS811WithStubbedAdaptor()
// Create stub function as it is needed by read submethod in driver code
a.i2cWriteImpl = func([]byte) (int, error) { return 0, nil }
d.Start()
a.i2cReadImpl = tc.readReturn
// act
temp, err := d.GetTemperature()
// assert
gobottest.Assert(t, temp, tc.temp)
gobottest.Assert(t, err, tc.err)
})
}
}
func TestCCS811DriverHasData(t *testing.T) {
cases := []struct {
func TestCCS811HasData(t *testing.T) {
var tests = map[string]struct {
readReturn func([]byte) (int, error)
result bool
err error
}{
// Does it return true for HasError = 0 and DataRdy = 1
{
"true for HasError=0 and DataRdy=1": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{0x08})
return 1, nil
@ -203,8 +168,7 @@ func TestCCS811DriverHasData(t *testing.T) {
result: true,
err: nil,
},
// Does it return false for HasError = 1 and DataRdy = 1
{
"false for HasError=1 and DataRdy=1": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{0x09})
return 1, nil
@ -212,8 +176,7 @@ func TestCCS811DriverHasData(t *testing.T) {
result: false,
err: nil,
},
// Does it return false for HasError = 1 and DataRdy = 0
{
"false for HasError=1 and DataRdy=0": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{0x01})
return 1, nil
@ -221,8 +184,7 @@ func TestCCS811DriverHasData(t *testing.T) {
result: false,
err: nil,
},
// Does it return false for HasError = 0 and DataRdy = 0
{
"false for HasError=0 and DataRdy=0": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{0x00})
return 1, nil
@ -230,8 +192,7 @@ func TestCCS811DriverHasData(t *testing.T) {
result: false,
err: nil,
},
// Does it return an error when the i2c read operation fails
{
"error when the i2c read operation fails": {
readReturn: func(b []byte) (int, error) {
copy(b, []byte{0x00})
return 1, errors.New("Error")
@ -240,17 +201,67 @@ func TestCCS811DriverHasData(t *testing.T) {
err: errors.New("Error"),
},
}
d, adaptor := initTestCCS811DriverWithStubbedAdaptor()
// Create stub function as it is needed by read submethod in driver code
adaptor.i2cWriteImpl = func([]byte) (int, error) { return 0, nil }
d.Start()
for _, c := range cases {
adaptor.i2cReadImpl = c.readReturn
result, err := d.HasData()
gobottest.Assert(t, result, c.result)
gobottest.Assert(t, err, c.err)
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
d, a := initTestCCS811WithStubbedAdaptor()
// Create stub function as it is needed by read submethod in driver code
a.i2cWriteImpl = func([]byte) (int, error) { return 0, nil }
d.Start()
a.i2cReadImpl = tc.readReturn
// act
result, err := d.HasData()
// assert
gobottest.Assert(t, result, tc.result)
gobottest.Assert(t, err, tc.err)
})
}
}
func TestCCS811_initialize(t *testing.T) {
// sequence for initialization the device on Start()
// * write hardware ID register (0x20)
// * read the ID
// * prepare software reset register content: a sequence of four bytes must
// be written to this register in a single I²C sequence: 0x11, 0xE5, 0x72, 0x8A
// * write software reset register content (0xFF)
// * write application start register (0xF4)
// * prepare measurement mode register content
// * INT_THRESH = 0 (normal mode)
// * INT_DATARDY = 0 (disable interrupt mode)
// * DRIVE_MODE = 0x01 (constant power, value every 1 sec)
// * write measure mode register content (0x01)
//
// arrange
d, a := initTestCCS811WithStubbedAdaptor()
a.written = []byte{} // reset writes of former test
const (
wantChipIDReg = uint8(0x20)
wantChipIDRegVal = uint8(0x20)
wantResetReg = uint8(0xFF)
wantAppStartReg = uint8(0xF4)
wantMeasReg = uint8(0x01)
wantMeasRegVal = uint8(0x10)
)
wantResetRegSequence := []byte{0x11, 0xE5, 0x72, 0x8A}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
// chip ID
b[0] = 0x81
return len(b), nil
}
// arrange, act - initialize() 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), 9)
gobottest.Assert(t, a.written[0], wantChipIDReg)
gobottest.Assert(t, a.written[1], wantResetReg)
gobottest.Assert(t, a.written[2:6], wantResetRegSequence)
gobottest.Assert(t, a.written[6], wantAppStartReg)
gobottest.Assert(t, a.written[7], wantMeasReg)
gobottest.Assert(t, a.written[8], wantMeasRegVal)
}