2018-10-08 23:13:54 -06:00
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 (
2022-09-21 12:40:10 +02:00
//the default I2C address for the ccs811 applies for ADDR to GND, for ADDR to VDD it will be 0x5B
2018-10-08 23:13:54 -06:00
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
}
2022-09-21 12:40:10 +02:00
//NewCCS811Status returns a new instance of the package ccs811 status definition
2018-10-08 23:13:54 -06:00
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 {
2022-09-21 12:40:10 +02:00
//If intThresh is 1 a data measurement will only be taken when the sensor value meets the threshold constraint.
2018-10-08 23:13:54 -06:00
//The threshold value is set in the threshold register (0x10)
intThresh uint8
2022-09-21 12:40:10 +02:00
//If intDataRdy is 1, the nINT signal (pin 3 of the device) will be driven low when new data is available.
2018-10-08 23:13:54 -06:00
intDataRdy uint8
//driveMode represents the sampling rate of the sensor. If the value is 0, the measurement process is idle.
driveMode CCS811DriveMode
}
2022-09-21 12:40:10 +02:00
//NewCCS811MeasMode returns a new instance of the package ccs811 measurement mode configuration. This represents the desired initial
2018-10-08 23:13:54 -06:00
//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 {
2022-09-21 12:40:10 +02:00
* Driver
2018-10-08 23:13:54 -06:00
measMode * CCS811MeasMode
ntcResistanceValue uint32
}
//NewCCS811Driver creates a new driver for the CCS811 (air quality sensor)
2022-09-21 12:40:10 +02:00
func NewCCS811Driver ( c Connector , options ... func ( Config ) ) * CCS811Driver {
d := & CCS811Driver {
Driver : NewDriver ( c , "CCS811" , ccs811DefaultAddress ) ,
measMode : NewCCS811MeasMode ( ) ,
2018-10-08 23:13:54 -06:00
//Recommended resistance value is 100,000
ntcResistanceValue : 100000 ,
}
2022-09-21 12:40:10 +02:00
d . afterStart = d . initialize
2018-10-08 23:13:54 -06:00
for _ , option := range options {
2022-09-21 12:40:10 +02:00
option ( d )
2018-10-08 23:13:54 -06:00
}
2022-09-21 12:40:10 +02:00
return d
2018-10-08 23:13:54 -06:00
}
//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 ) {
2022-09-21 12:40:10 +02:00
d . mutex . Lock ( )
defer d . mutex . Unlock ( )
2018-10-08 23:13:54 -06:00
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 ) {
2022-09-21 12:40:10 +02:00
d . mutex . Lock ( )
defer d . mutex . Unlock ( )
2018-10-08 23:13:54 -06:00
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 ) {
2022-09-21 12:40:10 +02:00
d . mutex . Lock ( )
defer d . mutex . Unlock ( )
2018-10-08 23:13:54 -06:00
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 ) {
2022-09-21 12:40:10 +02:00
d . mutex . Lock ( )
defer d . mutex . Unlock ( )
2018-10-08 23:13:54 -06:00
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 ) {
2022-09-21 12:40:10 +02:00
d . mutex . Lock ( )
defer d . mutex . Unlock ( )
2018-10-08 23:13:54 -06:00
2022-09-21 12:40:10 +02:00
buf := make ( [ ] byte , 4 )
err := d . connection . ReadBlockData ( ccs811RegNtc , buf )
2018-10-08 23:13:54 -06:00
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 ) {
2022-09-21 12:40:10 +02:00
d . mutex . Lock ( )
defer d . mutex . Unlock ( )
2018-10-08 23:13:54 -06:00
2022-09-21 12:40:10 +02:00
data := make ( [ ] byte , 4 )
err := d . connection . ReadBlockData ( ccs811RegAlgResultData , data )
2018-10-08 23:13:54 -06:00
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
}
2022-09-21 12:40:10 +02:00
//HasData returns true if the device has not errored and temperature/gas data is available
2018-10-08 23:13:54 -06:00
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 {
2022-09-21 12:40:10 +02:00
d . mutex . Lock ( )
defer d . mutex . Unlock ( )
2018-10-08 23:13:54 -06:00
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 {
2022-09-21 12:40:10 +02:00
d . mutex . Lock ( )
defer d . mutex . Unlock ( )
2018-10-08 23:13:54 -06:00
2022-09-21 12:40:10 +02:00
d . measMode . intDataRdy = 0
2018-10-08 23:13:54 -06:00
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
}
2022-09-21 12:40:10 +02:00
//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 ( ) )
2018-10-08 23:13:54 -06:00
}