/*!
 * @file DFRobot_CCS811.h
 * @brief Define the basic structure of class DFRobot_CCS811
 * @copyright	Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @license     The MIT License (MIT)
 * @author [yufeng](yufeng.luo@dfrobot.com)
 * @maintainer  [fary](feng.yang@dfrobot.com)
 * @version  V1.0
 * @date  2019-07-13
 * @get from https://www.dfrobot.com
 * @url https://github.com/DFRobot/DFRobot_CCS811
 */

#ifndef _DFROBOT_CCS811_H
#define _DFROBOT_CCS811_H

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


/*I2C ADDRESS*/
#define CCS811_I2C_ADDRESS1                      0x5A
#define CCS811_I2C_ADDRESS2                      0x5B

#define CCS811_REG_STATUS                        0x00
#define CCS811_REG_MEAS_MODE                     0x01
#define CCS811_REG_ALG_RESULT_DATA               0x02
#define CCS811_REG_RAW_DATA                      0x03
#define CCS811_REG_ENV_DATA                      0x05
#define CCS811_REG_NTC                           0x06
#define CCS811_REG_THRESHOLDS                    0x10
#define CCS811_REG_BASELINE                      0x11
#define CCS811_REG_HW_ID                         0x20
#define CCS811_REG_HW_VERSION                    0x21
#define CCS811_REG_FW_BOOT_VERSION               0x23
#define CCS811_REG_FW_APP_VERSION                0x24
#define CCS811_REG_INTERNAL_STATE                0xA0
#define CCS811_REG_ERROR_ID                      0xE0
#define CCS811_REG_SW_RESET                      0xFF

#define CCS811_BOOTLOADER_APP_ERASE              0xF1
#define CCS811_BOOTLOADER_APP_DATA               0xF2
#define CCS811_BOOTLOADER_APP_VERIFY             0xF3
#define CCS811_BOOTLOADER_APP_START              0xF4

#define CCS811_HW_ID                             0x81
//Open the macro to see the detailed program execution process.
//#define ENABLE_DBG

#ifdef ENABLE_DBG
#define DBG(...) {Serial.print("[");Serial.print(__FUNCTION__); Serial.print("(): "); Serial.print(__LINE__); Serial.print(" ] "); Serial.println(__VA_ARGS__);}
#else
#define DBG(...)
#endif

class DFRobot_CCS811
{
public:
    #define ERR_OK             0      //OK 
    #define ERR_DATA_BUS      -1      //error in data bus
    #define ERR_IC_VERSION    -2      //chip version mismatch
    
    uint8_t _deviceAddr;
    
    /**
     * @enum eCycle_t 
     * @brief Measurement mode
     */
    typedef enum{
        eClosed,      /**< Idle (Measurements are disabled in this mode) */
        eCycle_1s,    /**< Constant power mode, IAQ measurement every second */
        eCycle_10s,   /**< Pulse heating mode IAQ measurement every 10 seconds */
        eCycle_60s,   /**< Low power pulse heating mode IAQ measurement every 60 seconds */
        eCycle_250ms  /**< Constant power mode, sensor measurement every 250ms 1xx: Reserved modes (For future use) */
    }eCycle_t;

    /**
     * @fn DFRobot_CCS811
     * @brief Constructor 
     * @param pWire TwoWire
     * @param deviceAddr iic addr
     */
    DFRobot_CCS811(TwoWire *pWire = &Wire, uint8_t deviceAddr = 0x5A){_pWire = pWire; _deviceAddr = deviceAddr;};
    
    /**
     * @fn begin
     * @brief initialization function
     * @return initialization status
     * @retval ERR_OK          initialization success
     * @retval ERR_DATA_BUS    error in data bus
     * @retval ERR_IC_VERSION  chip version mismatch
     */ 
    int begin();

    /**
     * @fn checkDataReady
     * @brief Judge if there is data to read 
     * @return  the result of checking
     * @retval  true   there is
     * @retval  false  there is no
     */
    bool checkDataReady();

    /**
     * @fn softReset
     * @brief Reset sensor, clear all configured data.
     */
    void softReset();

    /**
     * @fn setInTempHum
     * @brief Set environment parameter 
     * @param temperature Set temperature value, unit: centigrade, range (-40~85℃)
     * @param humidity    Set humidity value, unit: RH, range (0~100)
     */
    void setInTempHum(float temperature, float humidity);

    /**
     * @fn setMeasurementMode
     * @brief Measurement parameter configuration 
     * @param mode   Measurements mode
     * @n            eClosed       Idle (Measurements are disabled in this mode)
     * @n            eCycle_1s     Constant power mode, IAQ measurement every second
     * @n            eCycle_10s    Pulse heating mode IAQ measurement every 10 seconds
     * @n            eCycle_60s    Low power pulse heating mode IAQ measurement every 60 seconds
     * @n            eCycle_250ms  Constant power mode, sensor measurement every 250ms 1xx: Reserved modes (For future use)
     * @param thresh
     * @n            0             for Interrupt mode operates normally
     * @n            1             for interrupt mode only asserts the nINT signal (driven low) if the new
     * @param interrupt
     * @n            0             for Interrupt generation is disabled
     * @n            1             for the nINT signal is asserted (driven low) when a new sample is ready in
     */
    void setMeasurementMode(eCycle_t mode, uint8_t thresh = 0, uint8_t interrupt = 0);

    /**
     * @fn setThresholds
     * @brief Set interrupt thresholds 
     * @param lowToMed: interrupt triggered value in range low to middle 
     * @param medToHigh: interrupt triggered value in range middle to high 
     */
    void setThresholds(uint16_t lowToMed, uint16_t medToHigh);

    /**
     * @fn getMeasurementMode
     * @brief Get current configured parameter
     * @return configuration code, needs to be converted into binary code to analyze
     * @n       The 2nd: Interrupt mode (if enabled) operates normally,1: Interrupt mode (if enabled) only asserts the nINT signal (driven low) if the new
     * @n       The 3rd: Interrupt generation is disabled,1: The nINT signal is asserted (driven low) when a new sample is ready in
     * @n       The 4th: 6th: in typedef enum eCycle_t
     */
    uint8_t getMeasurementMode();

    /**
     * @fn getCO2PPM
     * @brief Get the current carbon dioxide concentration
     * @return current carbon dioxide concentration, unit:ppm
     */
    uint16_t getCO2PPM();

    /**
     * @fn getTVOCPPB
     * @brief Get current TVOC concentration
     * @return Return current TVOC concentration, unit: ppb
     */
    uint16_t getTVOCPPB();

    /**
     * @fn readBaseLine
     * @brief get the current baseline number
     * @return a Hexadecimal number of the current baseline number
     */
    uint16_t readBaseLine();

    /**
     * @fn writeBaseLine
     * @brief write a baseline number into register
     * @param baseLine Hexadecimal number get from baseLine()
     */
    void writeBaseLine(uint16_t baseLine);
    
protected:

    /**
     * @struct sError_id 
     * @brief Error id 
     */
    typedef struct{
        uint8_t sWRITE_REG_INVALID: 1;/**< The CCS811 received an I²C write request addressed to this station but with invalid register address ID */
        uint8_t sREAD_REG_INVALID:  1;/**< The CCS811 received an I²C read request to a mailbox ID that is invalid */
        uint8_t sMEASMODE_INVALID:  1;/**< The CCS811 received an I²C request to write an unsupported mode to MEAS_MODE */
        uint8_t sMAX_RESISTANCE:    1;/**< The sensor resistance measurement has reached or exceeded the maximum range */
        uint8_t sHEATER_FAULT:      1;/**< The The Heater current in the CCS811 is not in range */
        uint8_t sHEATER_SUPPLY:     1;/**< The Heater voltage is not being applied correctly */
    } __attribute__ ((packed))sError_id;
    
    /**
     * @struct sMeas_mode 
     * @brief Measurement mode
     */
    typedef struct{
        uint8_t sINT_THRESH:  1;/**< ALG_RESULT_DATA crosses one of the thresholds set in the THRESHOLDS register by more than the hysteresis value (also in the THRESHOLDS register) */
        uint8_t sINT_DATARDY: 1;/**< At the end of each measurement cycle (250ms, 1s, 10s, 60s) a flag is set in the STATUS register regardless of the setting of this bit. */
        uint8_t sDRIVE_MODE:  3;/**< A new sample is placed in ALG_RESULT_DATA and RAW_DATA registers and the DATA_READY bit in the STATUS register is set at the defined measurement interval. */
    } __attribute__ ((packed))sMeas_mode;

    /**
     * @struct sStatus 
     * @brief Sensor Status 
     */
    typedef struct{
        uint8_t sERROR:      1;/**< This bit is cleared by reading ERROR_ID.It is not sufficient to read the ERROR field of ALG_RESULT_DATA and STATUS */
        uint8_t sDATA_READY: 1;/**< ALG_RESULT_DATA is read on the I²C interface */
        uint8_t sAPP_VALID:  1;
        uint8_t sAPP_VERIFY: 1;/**< After issuing a VERIFY command the application software must wait 70ms before issuing any transactions to CCS811 over the I²C interface */
        uint8_t sAPP_ERASE:  1;/**< After issuing the ERASE command the application software must wait 500ms before issuing any transactions to the CCS811 over the I2C interface. */
        uint8_t sFW_MODE:    1;
    } __attribute__ ((packed))sStatus;

    /**
     * @fn writeReg
     * @brief Write register function 
     * @param reg  register address 8bits
     * @param pBuf Storage cache of the data to be written into 
     * @param size Length of the data to be written into 
     */
    void writeReg(uint8_t reg, const void* pBuf, size_t size);

    /**
     * @fn readReg
     * @brief Read register function 
     * @param reg  register address 8bits
     * @param pBuf Storage cache of the data to be read
     * @param size Length of the data to be read
     * @return Return the actually read length, fails to read if return 0.  
     */
    uint8_t readReg(uint8_t reg, const void* pBuf, size_t size);

private:
    TwoWire *_pWire;
    
    uint16_t eCO2;
    uint16_t eTVOC;
};

#endif
