package i2c import ( "fmt" "math" "time" ) // CCS811DriveMode type type CCS811DriveMode uint8 // Operating modes which dictate how often measurements are being made. If 0x00 is used as an operating mode, measurements will be disabled const ( CCS811DriveModeIdle CCS811DriveMode = 0x00 CCS811DriveMode1Sec = 0x01 CCS811DriveMode10Sec = 0x02 CCS811DriveMode60Sec = 0x03 CCS811DriveMode250MS = 0x04 ) const ( //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 //Single byte read only register which indicates if a device is active, if new data is available or if an error occurred. ccs811RegStatus = 0x00 //This is Single byte register, which is used to enable sensor drive mode and interrupts. ccs811RegMeasMode = 0x01 //This multi-byte read only register contains the calculated eCO2 (ppm) and eTVOC (ppb) values followed by the STATUS register, ERROR_ID register and the RAW_DATA register. ccs811RegAlgResultData = 0x02 //Two byte read only register which contains the latest readings from the sensor. ccs811RegRawData = 0x03 //A multi-byte register that can be written with the current Humidity and Temperature values if known. ccs811RegEnvData = 0x05 //Register that holds the NTC value used for temperature calcualtions ccs811RegNtc = 0x06 //Asserting the SW_RESET will restart the CCS811 in Boot mode to enable new application firmware to be downloaded. ccs811RegSwReset = 0xFF //Single byte read only register which holds the HW ID which is 0x81 for this family of CCS81x devices. ccs811RegHwID = 0x20 //Single byte read only register that contains the hardware version. The value is 0x1X ccs811RegHwVersion = 0x21 //Two byte read only register which contain the version of the firmware bootloader stored in the CCS811 in the format Major.Minor.Trivial ccs811RegFwBootVersion = 0x23 //Two byte read only register which contain the version of the firmware application stored in the CCS811 in the format Major.Minor.Trivial ccs811RegFwAppVersion = 0x24 //To change the mode of the CCS811 from Boot mode to running the application, a single byte write of 0xF4 is required. ccs811RegAppStart = 0xF4 // Constants // The hardware ID code ccs811HwIDCode = 0x81 ) var ( // The sequence of bytes needed to do a software reset ccs811SwResetSequence = []byte{0x11, 0xE5, 0x72, 0x8A} ) // CCS811Status represents the current status of the device defined by the ccs811RegStatus. // The following definitions were taken from https://ams.com/documents/20143/36005/CCS811_DS000459_6-00.pdf/c7091525-c7e5-37ac-eedb-b6c6828b0dcf#page=15 type CCS811Status struct { //There is some sort of error on the i2c bus or there is an error with the internal sensor HasError byte //A new data sample is ready in ccs811RegAlgResultData DataReady byte //Valid application firmware loaded AppValid byte //Firmware is in application mode. CCS811 is ready to take sensor measurements FwMode byte } //NewCCS811Status returns a new instance of the package ccs811 status definition func NewCCS811Status(data uint8) *CCS811Status { return &CCS811Status{ HasError: data & 0x01, DataReady: (data >> 3) & 0x01, AppValid: (data >> 4) & 0x01, FwMode: (data >> 7) & 0x01, } } //CCS811MeasMode represents the current measurement configuration. //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 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 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 initial //state of the measurement mode register. func NewCCS811MeasMode() *CCS811MeasMode { return &CCS811MeasMode{ // Disable this by default as this library does not contain the functionality to use the internal interrupt feature. intThresh: 0x00, intDataRdy: 0x00, driveMode: CCS811DriveMode1Sec, } } // GetMeasMode returns the measurement mode func (mm *CCS811MeasMode) GetMeasMode() byte { return (mm.intThresh << 2) | (mm.intDataRdy << 3) | uint8((mm.driveMode << 4)) } //CCS811Driver is the Gobot driver for the CCS811 (air quality sensor) Adafruit breakout board type CCS811Driver struct { *Driver measMode *CCS811MeasMode ntcResistanceValue uint32 } //NewCCS811Driver creates a new driver for the CCS811 (air quality sensor) 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, } d.afterStart = d.initialize for _, option := range options { option(d) } return d } //WithCCS811MeasMode sets the sampling rate of the device func WithCCS811MeasMode(mode CCS811DriveMode) func(Config) { return func(c Config) { d, _ := c.(*CCS811Driver) d.measMode.driveMode = mode } } //WithCCS811NTCResistance sets reistor value used in the temperature calculations. //This resistor must be placed between pin 4 and pin 8 of the chip func WithCCS811NTCResistance(val uint32) func(Config) { return func(c Config) { d, _ := c.(*CCS811Driver) d.ntcResistanceValue = val } } //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 } return v, nil } //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 } return v, nil } //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 } return v, nil } //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 } cs := NewCCS811Status(s) return cs, nil } //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 := make([]byte, 4) err := d.connection.ReadBlockData(ccs811RegNtc, buf) if err != nil { return 0, err } vref := ((uint16(buf[0]) << 8) | uint16(buf[1])) vrntc := ((uint16(buf[2]) << 8) | uint16(buf[3])) rntc := (float32(vrntc) * float32(d.ntcResistanceValue) / float32(vref)) ntcTemp := float32(math.Log(float64(rntc / 10000.0))) ntcTemp /= 3380.0 ntcTemp += 1.0 / (25 + 273.15) ntcTemp = 1.0 / ntcTemp ntcTemp -= 273.15 return ntcTemp, nil } //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 := make([]byte, 4) err := d.connection.ReadBlockData(ccs811RegAlgResultData, data) if err != nil { return 0, 0, err } // Bit masks defined by https://ams.com/documents/20143/36005/CCS811_AN000369_2-00.pdf/25d0db9a-92b9-fa7f-362c-a7a4d1e292be#page=14 eco2 := (uint16(data[0]) << 8) | uint16(data[1]) tvoC := (uint16(data[2]) << 8) | uint16(data[3]) return eco2, tvoC, nil } //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 { return false, err } if !(s.DataReady == 0x01) || (s.HasError == 0x01) { return false, nil } return true, nil } //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()) } func (d *CCS811Driver) initialize() error { deviceID, err := d.connection.ReadByteData(ccs811RegHwID) if err != nil { return fmt.Errorf("Failed to get the device id from ccs811RegHwID with error: %s", err.Error()) } // Verify that the connected device is the CCS811 sensor if deviceID != ccs811HwIDCode { return fmt.Errorf("The fetched device id %d is not the known id %d with error", deviceID, ccs811HwIDCode) } if err := d.resetDevice(); err != nil { return fmt.Errorf("Was not able to reset the device with error: %s", err.Error()) } // Required sleep to allow device to switch states time.Sleep(100 * time.Millisecond) if err := d.startApp(); err != nil { return fmt.Errorf("Failed to start app code with error: %s", err.Error()) } if err := d.updateMeasMode(); err != nil { return fmt.Errorf("Failed to update the measMode register with error: %s", err.Error()) } return 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()) }