/*!
 * @file Adafruit_SGP30.cpp
 *
 * @mainpage Adafruit SGP30 gas sensor driver
 *
 * @section intro_sec Introduction
 *
 * This is the documentation for Adafruit's SGP30 driver for the
 * Arduino platform.  It is designed specifically to work with the
 * Adafruit SGP30 breakout: http://www.adafruit.com/products/3709
 *
 * These sensors use I2C to communicate, 2 pins (SCL+SDA) are required
 * to interface with the breakout.
 *
 * Adafruit invests time and resources providing this open source code,
 * please support Adafruit and open-source hardware by purchasing
 * products from Adafruit!
 *
 *
 * @section author Author
 * Written by Ladyada for Adafruit Industries.
 *
 * @section license License
 * BSD license, all text here must be included in any redistribution.
 *
 */


#if ARDUINO >= 100
 #include "Arduino.h"
#else
 #include "WProgram.h"
#endif

#include "Adafruit_SGP30.h"

//#define I2C_DEBUG

/**************************************************************************/
/*! 
    @brief  Instantiates a new SGP30 class
*/
/**************************************************************************/
Adafruit_SGP30::Adafruit_SGP30() {
}

/**************************************************************************/
/*! 
    @brief  Setups the hardware and detects a valid SGP30. Initializes I2C
    then reads the serialnumber and checks that we are talking to an SGP30
    @param  theWire Optional pointer to I2C interface, otherwise use Wire
    @returns True if SGP30 found on I2C, False if something went wrong!
*/
/**************************************************************************/
boolean Adafruit_SGP30::begin(TwoWire *theWire) {
  _i2caddr = SGP30_I2CADDR_DEFAULT;
  if (theWire == NULL) {
    _i2c = &Wire;
  } else {
    _i2c = theWire;
  }

  _i2c->begin();

  
  uint8_t command[2];
  command[0] = 0x36;
  command[1] = 0x82;
  if (! readWordFromCommand(command, 2, 10, serialnumber, 3)) 
    return false;

  uint16_t featureset;
  command[0] = 0x20;
  command[1] = 0x2F;
  if (! readWordFromCommand(command, 2, 10, &featureset, 1)) 
    return false;
  //Serial.print("Featureset 0x"); Serial.println(featureset, HEX);
  if (featureset != SGP30_FEATURESET) 
    return false;
  if (! IAQinit()) 
    return false;

  return true;
}

/**************************************************************************/
/*! 
    @brief  Commands the sensor to begin the IAQ algorithm. Must be called after startup.
    @returns True if command completed successfully, false if something went wrong!
*/
/**************************************************************************/
boolean Adafruit_SGP30::IAQinit(void) {
  uint8_t command[2];
  command[0] = 0x20;
  command[1] = 0x03;
  return readWordFromCommand(command, 2, 10);
}

/**************************************************************************/
/*! 
    @brief  Commands the sensor to take a single eCO2/VOC measurement. Places results in {@link TVOC} and {@link eCO2}
    @returns True if command completed successfully, false if something went wrong!
*/
/**************************************************************************/
boolean Adafruit_SGP30::IAQmeasure(void) {
  uint8_t command[2];
  command[0] = 0x20;
  command[1] = 0x08;
  uint16_t reply[2];
  if (! readWordFromCommand(command, 2, 12, reply, 2))
    return false;
  TVOC = reply[1];
  eCO2 = reply[0];
  return true;
}
 
/**************************************************************************/
/*! 
    @brief Request baseline calibration values for both CO2 and TVOC IAQ calculations. Places results in parameter memory locaitons.
    @param eco2_base A pointer to a uint16_t which we will save the calibration value to
    @param tvoc_base A pointer to a uint16_t which we will save the calibration value to
    @returns True if command completed successfully, false if something went wrong!
*/
/**************************************************************************/
boolean Adafruit_SGP30::getIAQBaseline(uint16_t *eco2_base, uint16_t *tvoc_base) {
  uint8_t command[2];
  command[0] = 0x20;
  command[1] = 0x15;
  uint16_t reply[2];
  if (! readWordFromCommand(command, 2, 10, reply, 2))
    return false;
  *eco2_base = reply[0];
  *tvoc_base = reply[1];
  return true;
}

/**************************************************************************/
/*! 
    @brief Assign baseline calibration values for both CO2 and TVOC IAQ calculations.
    @param eco2_base A uint16_t which we will save the calibration value from
    @param tvoc_base A uint16_t which we will save the calibration value from
    @returns True if command completed successfully, false if something went wrong!
*/
/**************************************************************************/
boolean Adafruit_SGP30::setIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base) {
  uint8_t command[8];
  command[0] = 0x20;
  command[1] = 0x1e;
  command[2] = tvoc_base >> 8;
  command[3] = tvoc_base & 0xFF;
  command[4] = generateCRC(command+2, 2);
  command[5] = eco2_base >> 8;
  command[6] = eco2_base & 0xFF;
  command[7] = generateCRC(command+5, 2);

  return readWordFromCommand(command, 8, 10);
}

/**************************************************************************/
/*! 
    @brief  I2C low level interfacing
*/
/**************************************************************************/


boolean Adafruit_SGP30::readWordFromCommand(uint8_t command[], uint8_t commandLength, uint16_t delayms, uint16_t *readdata, uint8_t readlen)
{
  uint8_t data;

  _i2c->beginTransmission(_i2caddr);

#ifdef I2C_DEBUG
  Serial.print("\t\t-> ");
#endif

  for (uint8_t i=0; i<commandLength; i++) {
    _i2c->write(command[i]);
#ifdef I2C_DEBUG
    Serial.print("0x"); Serial.print(command[i], HEX); Serial.print(", ");
#endif
  }
#ifdef I2C_DEBUG
  Serial.println();
#endif
  _i2c->endTransmission();

  delay(delayms);

  if (readlen == 0) 
    return true;

  uint8_t replylen = readlen * (SGP30_WORD_LEN +1);
  if (_i2c->requestFrom(_i2caddr, replylen) != replylen) 
    return false;
  uint8_t replybuffer[replylen];
#ifdef I2C_DEBUG
  Serial.print("\t\t<- ");
#endif  
  for (uint8_t i=0; i<replylen; i++) {
    replybuffer[i] = _i2c->read();
#ifdef I2C_DEBUG
    Serial.print("0x"); Serial.print(replybuffer[i], HEX); Serial.print(", ");
#endif
  }

#ifdef I2C_DEBUG
  Serial.println();
#endif

  for (uint8_t i=0; i<readlen; i++) {
    uint8_t crc = generateCRC(replybuffer+i*3, 2);
#ifdef I2C_DEBUG
    Serial.print("\t\tCRC calced: 0x"); Serial.print(crc, HEX);
    Serial.print(" vs. 0x"); Serial.println(replybuffer[i * 3 + 2], HEX);
#endif
    if (crc != replybuffer[i * 3 + 2])
      return false;
    // success! store it
    readdata[i] = replybuffer[i*3];
    readdata[i] <<= 8;
    readdata[i] |= replybuffer[i*3 + 1];
#ifdef I2C_DEBUG
    Serial.print("\t\tRead: 0x"); Serial.println(readdata[i], HEX);
#endif
  }
  return true;
}

uint8_t Adafruit_SGP30::generateCRC(uint8_t *data, uint8_t datalen) {
  // calculates 8-Bit checksum with given polynomial
  uint8_t crc = SGP30_CRC8_INIT;

  for (uint8_t i=0; i<datalen; i++) {
    crc ^= data[i];
    for (uint8_t b=0; b<8; b++) {
      if (crc & 0x80)
	crc = (crc << 1) ^ SGP30_CRC8_POLYNOMIAL;
      else
	crc <<= 1;
    }
  }
  return crc;
}
