/*
 * My FISHINO Uno Air Quality station
 * 
 * Copyleft Lumir Vanek
 * 
 * Updated: 7.6.2017
 */

#include <avr/pgmspace.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Fishino.h>
#include <SPI.h>

#define _USE_WIFI
#define _USE_RTC
#define _USE_CCS811
#define _USE_Si7021                 // I2C Si7021 Humidity and Temperature sensor


#ifdef _USE_RTC
  #include "RTClib.h"
  RTC_DS1307 rtc;
#endif

#ifdef _USE_Si7021
  #include "SparkFun_Si7021_Breakout_Library.h"
#endif

#ifdef _USE_CCS811
  #include <SparkFunCCS811.h>
#endif

// I2C bus adresses
#define I2C_LCD1           0x3F       // LCD 20x4 driven by HD44780, connected to I2C module PCF8574AT (7bit I2C address)
#define I2C_CCS811 0x5B //Default I2C Address

// WiFi AP definitions
const char AP_SSID[] = "SSID";       // Case sensitive !
const char AP_PASSWORD[] = "secret";

// IOT service site information
const char http_ip[] = "10.5.73.x";         // dlg360g5
const int http_port = 8080;

/*
 * Display messages and JAX-RS service names are stored to flash (program) memory to save SRAM.
 */
const char msg_welcome[] PROGMEM = "Fishino AQ Station";
const char msg_fw_version[] PROGMEM = "SW 6.6.2017";
const char msg_test_displ1[] PROGMEM = "123456789ABCEFGH";
const char msg_test_displ2[] PROGMEM = "!@#$%^&*()_-<>{}[]";
const char txt_dm_hms[] PROGMEM = "%02d.%02d. %02d:%02d:%02d";
// JAX-RS Services paths, as defined using @Path anotation in JAVA code
const char get_last_value_txt1[] PROGMEM = "get-last-value-txt1"; // JAX-RS Service that returns Sensor shorted name + value
const char get_last_value_txt2[] PROGMEM = "get-last-value-txt2"; // JAX-RS Service that returns Sensor value + unit
const char get_sensor_name[] PROGMEM = "get-sensor-name";         // JAX-RS Service that returns Sensor name
const char get_last_state_txt[] PROGMEM =  "get-last-state-txt"; // JAX-RS Service that returns Relay state

PROGMEM const char* const string_table[] =
{   
    msg_welcome, msg_fw_version, msg_test_displ1, msg_test_displ2, txt_dm_hms, get_last_value_txt1, get_last_value_txt2, get_sensor_name, get_last_state_txt
};

// Index constants for obtaining Strings from PROGMEM defined above
#define msg_ix_welcome              1
#define msg_ix_fw_version           2
#define msg_ix_test_displ1          3
#define msg_ix_test_displ2          4
#define txt_ix_dm_hms               5
#define svc_ix_get_last_value_txt1  6 
#define svc_ix_get_last_value_txt2  7 
#define svc_ix_get_sensor_name      8
#define svc_ix_get_last_state_txt   9

/*
 * LCD connected via PCF8574AT
 */
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7
#define BACKLIGHT_PIN    3

// DEM 20486 FGH-PW LCD MODULE
LiquidCrystal_I2C  lcd1(I2C_LCD1, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin, BACKLIGHT_PIN, POSITIVE);

/*
 * HD44780 LCD User-Defined Graphics
 * http://www.frank4dd.com/howto/rabbit/hd44780lcd-to-rabbit-rcm4010.htm
 */
/*byte custom_smiley[8] = {
  0x00,    //.....
  0x0A,   //.#.#.
  0x00,   //.....
  0x04,   //..#..
  0x00,   //.....
  0x11,   //#...#
  0x0E,   //.###.
  0x00    //.....
};*/

byte custom_frowny[8] = {
  0x00,   //.....
  0x0A,   //.#.#.
  0x00,   //.....
  0x04,   //..#..
  0x00,   //.....
  0x0E,   //.###.
  0x11,   //#...#
  0x00    //.....
};

byte custom_temperature[8] = //icon for termometer
{
    B00100,
    B01010,
    B01010,
    B01110,
    B01110,
    B11111,
    B11111,
    B01110
};

byte custom_humidity[8] = //icon for water droplet
{
    B00100,
    B00100,
    B01010,
    B01010,
    B10001,
    B10001,
    B10001,
    B01110,
};

byte custom_whitebox[8] = //icon for void box
{
    B11111,
    B10001,
    B10001,
    B10001,
    B10001,
    B10001,
    B10001,
    B11111,
};

byte custom_menuicon[8] = //icon for menu symbol
{
    B11111,
    B00000,
    B11111,
    B00000,
    B11111,
    B00000,
    B11111,
    B00000,
};

byte custom_degree[8] = //icon for degree symbol
{
    B00110,
    B01001,
    B01001,
    B00110,
    B00000,
    B00000,
    B00000,
    B00000,
};

//#define char_smiley      0
#define char_frowny      1
#define char_temperature 2
#define char_humidity    3
#define char_whitebox    4
#define char_menuicon    5
#define char_degree      6

FishinoClient client;

//************* Gargenhouse ************************** 
const char sensorCode_DallasTemp_01[] = "LV_TEMP_01";

//************* Outdoor ****************************** 
const char sensorCode_DallasTemp_02[] = "LV_TEMP_02";

//************* Greenhouse *************************** 
const char sensorCode_DallasTemp_03[] = "LV_TEMP_03";  
const char sensorCode_BMP180Temp_07[] = "LV_TEMP_07";
const char sensorCode_BMP180Pres_02[] = "LV_PRES_02";
const char sensorCode_BMP180Atm_02[] = "LV_ATM_02";
const char sensorCode_BH1750Light_01[] = "LV_LIGH_01";

const char sensorCode_Si7021Hum_02[] = "LV_HUM_02";
const char sensorCode_Si7021Temp_10[] = "LV_TEMP_10";

//************* Basen ******************************** 
const char sensorCode_DallasTemp_04[] = "LV_TEMP_04"; // Air
const char sensorCode_DallasTemp_08[] = "LV_TEMP_08"; // Water
const char sensorCode_DallasTemp_11[] = "LV_TEMP_11"; // Solar panel

//************* Testboard **************************** 
const char sensorCode_DallasTemp_06[] = "LV_TEMP_06";
const char sensorCode_BMP180Temp_05[] = "LV_TEMP_05";
const char sensorCode_BMP180Pres_01[] = "LV_PRES_01";
const char sensorCode_BMP180Atm_01[] = "LV_ATM_01";
const char sensorCode_Si7021Hum_01[] = "LV_HUM_01";
const char sensorCode_Si7021Temp_09[] = "LV_TEMP_09";

//************* Relays ************************** 
const char relayCode_01[] = "LV_RELAY_01";
const char relayCode_02[] = "LV_RELAY_02";


// Current LCD display page
int currentPage = 0;


/*
 * Content of I2C expander PCF8574P on expand shield
 * 
 * bit 0 - inside small blue LED
 * bit 1 - outside FLUX LED
 * bit 2 - outside FLUX LED
 * bit 3 - outside FLUX LED
 * bit 4 - outside FLUX LED
 * bit 5 - outside FLUX LED
 * bit 6 - Relay LV_RELAY_01 Workroom
 * bit 7 - Relay LV_RELAY_02 Outdoor 
 */
byte port1data = 0x00;

#ifdef _USE_Si7021
  //  The Si7021 has a default I2C address of 0x40 and cannot be changed!
  Weather si7021; //Create Instance of HTU21D or SI7021 temp and humidity sensor and MPL3115A2 barrometric sensor
  const char sensorCode_Si7021Hum[] = "LV_HUM_01";
  const char sensorCode_Si7021Temp[] = "LV_TEMP_09";
#endif

#ifdef _USE_CCS811
  CCS811 ccs811(I2C_CCS811);
  const char sensorCode_CCS811VOC[] = "LV_VOC_01";
  const char sensorCode_CCS811CO2[] = "LV_CO2_01";
#endif


void setup() 
{
    // Current LCD display page init
    currentPage = 0;
    
    Serial.begin(115200);
    Wire.begin(); 
    
    
#ifdef _USE_CCS811    
    /*
     * Init CCS811 I2C Air Quality sensor
     */
    delay(200);
    Serial << F("Init CCS811 I2C Air Quality sensor\n");
    //This begins the CCS811 sensor and prints error status of .begin()
    CCS811Core::status returnCode = ccs811.begin();
    Serial << F("CCS811 begin exited with: ");
    printCCS811DriverError(returnCode);

    returnCode = ccs811.setDriveMode(2); // A measurement is performed every 10 seconds
    Serial << F("CCS811 setDriveMode exited with: ");
    printCCS811DriverError(returnCode);
#endif     

#ifdef _USE_Si7021
    /*
     * Init Si7021 I2C temp and humidity sensor
     */
     Serial << F("Init Si7021 I2C temp and humidity sensor\n");
     si7021.begin();
#endif 

    /*
     *  Initialize the LCD
     */
    lcd1.begin(20, 4); // 20 chars, 4 lines
    //lcd1.createChar(char_smiley, custom_smiley);
    lcd1.createChar(char_frowny, custom_frowny);
    lcd1.createChar(char_temperature, custom_temperature);
    lcd1.createChar(char_humidity, custom_humidity);
    lcd1.createChar(char_menuicon, custom_menuicon);
    lcd1.createChar(char_degree, custom_degree);
    lcd1.createChar(char_whitebox, custom_whitebox);
    
    /*
     * Setup RTC
     */
#ifdef _USE_RTC
    rtc.begin();

    if (! rtc.isrunning())
    {
      Serial.println("RTC is NOT running!");
  
      // following line sets the RTC to the date & time this sketch was compiled
      rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    }
#endif
    
    /*
     * Uncomment to set RTC to compile datetime. Then comment it back and load sketch again 
     * to prevent set wrong datetime after reset !
     */
    //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

    /*
     * Hello message
     */
    sayHello();
        
    /*
     * Reset and test WiFi module
     */
    while(!Fishino.reset())
    {
      Serial << F("Fishino RESET FAILED, RETRYING...\n");
    }
  
    Serial << F("Fishino WiFi RESET OK\n");

    Fishino.setMode(STATION_MODE);
    Fishino.setPhyMode(PHY_MODE_11G);

    // Print WiFi MAC address:
    printMacAddress();

    Serial << F("FW version: ");
    Serial.println(Fishino.firmwareVersionStr());

#ifdef _USE_WIFI
    /*
     * Connect to WiFi
     */
     wifiConnect();
     printWifiStatus();
#endif
}

void loop()
{
    //digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    
    String line1 = String((char) char_frowny);
    String line2 = String((char) char_frowny);
    String line3 = String((char) char_frowny);
    String line4 = String((char) char_frowny);
    String relay;
    boolean success = true;

#ifdef _USE_WIFI
    /*
     * Relay 1 - bit 6
     */
    success &= getValue(getStringNonPadded(svc_ix_get_last_state_txt), relayCode_01, relay);
    if (relay.charAt(0) == '1')
    {
      Serial << F("\Relay 1 ON\n");
    }
    else
    {
      Serial << F("\Relay 1 OFF\n");
    }

    /*
     * Relay 2 - bit 7
     */
    success &= getValue(getStringNonPadded(svc_ix_get_last_state_txt), relayCode_02, relay);
    if (relay.charAt(0) == '1')
    {
      Serial << F("\nRelay 2 ON\n");
    }
    else
    {
      Serial << F("\nRelay 2 OFF\n");
    }
#endif
       
    
#ifdef _USE_WIFI
    if (!success)
    {
        clearLCD(lcd1);
        lcd1.print("Error. Resetting Fishino");
           
        while(!Fishino.reset())
        {
          Serial << F("Fishino RESET FAILED, RETRYING...\n");
        }
      
        Serial << F("Fishino WiFi RESET OK\n");
    
         Fishino.setMode(STATION_MODE);
         wifiConnect();
         printWifiStatus();
         return;
    }
#endif

    clearLCD(lcd1);
    lcd1.print(getStringPadded(msg_ix_welcome));
    
#ifdef _USE_RTC
    printRTC(1);
#endif
    //printPort1(3);
    delay(5000);  // wait for a 5 seconds
    
    clearLCD(lcd1);
    
            
   

    // Default used if Si7021 not works
    float humidity7021 = 40.0;
    float temperature7021 = 25.0;
    
#ifdef _USE_Si7021
    humidity7021 = si7021.getRH();
    temperature7021 = si7021.getTemp();

    Serial.print("Si7021 Humidity: ");
    Serial.print(humidity7021);
    Serial.println(" %");
    

    Serial.print("Si7021 Temperature: ");
    Serial.print(temperature7021);
    Serial.println(" deg C");
    
    clearLCD(lcd1);
    line1 = "Si7021 " + String((char) char_temperature) + " " + String(temperature7021, 1) + " " + String((char)char_degree) + "C";
    lcd1.print(line1);

    lcd1.setCursor(0, 1);
    line2 = "Si7021 " + String((char) char_humidity) + " " + String(humidity7021, 1) + " %";
    lcd1.print(line2);

#ifdef _USE_WIFI
    sendValue(sensorCode_Si7021Hum, humidity7021); 
    sendValue(sensorCode_Si7021Temp, temperature7021);
#endif

#endif

    if (humidity7021 < 0) { humidity7021 = 40.0; }
    if (humidity7021 > 100) { humidity7021 = 100.0; }
      
#ifdef _USE_CCS811
    lcd1.setCursor(0, 2);
    
    //Check to see if data is available
    if (ccs811.dataAvailable())
    {
      //Calling this function updates the global tVOC and eCO2 variables
      ccs811.readAlgorithmResults();

      //This sends the temperature data to the CCS811
      ccs811.setEnvironmentalData(humidity7021, temperature7021);
  
      Serial.print("CO2 concentration : ");
      float co2 = ccs811.getCO2();
      Serial.print(co2);
      Serial.println(" ppm");

      line3 = "CCS811 CO2 " + String(co2, 0) + " ppm";
      lcd1.print(line3);
        
#ifdef _USE_WIFI
      if (co2 >= 400)
      {
        sendValue(sensorCode_CCS811CO2, co2);
      }
#endif
      
      Serial.print("VOC concentration : ");
      float voc = ccs811.getTVOC();
      Serial.print(voc);
      Serial.println(" ppb");

      lcd1.setCursor(0, 3);
      line4 = "CCS811 VOC " + String(voc, 0) + " ppb";
      lcd1.print(line4);

#ifdef _USE_WIFI        
      if(voc > 0)
      {
        sendValue(sensorCode_CCS811VOC, voc);
      }
#endif
    }
    else if (ccs811.checkForStatusError())
    {
        //If the CCS811 found an internal error, print it.
        printCCS811SensorError();
    }
#endif

  delay(20000);
}

/*
 * Clear given LCD: 
 *
 */
void clearLCD(LiquidCrystal_I2C & lcd)
{
    lcd.clear();
    lcd.home();
}

/*
 *  Hello message and 3 beeps
 */
void sayHello()
{
    clearLCD(lcd1);
    lcd1.print(getStringPadded(msg_ix_welcome));
    lcd1.setCursor(0, 1); 
    lcd1.print(getStringPadded(msg_ix_fw_version));

#ifdef _USE_RTC
    printRTC(2);
#endif
    
    for(int i = 0; i < 5; i++)
    {
      lcd1.backlight();
      delay(250);
      lcd1.noBacklight();
    }

    lcd1.backlight();

    
}

#ifdef _USE_RTC

void printRTC(int row)
{
    char buf[25];
    DateTime now = rtc.now();
        
    sprintf(buf, "%02d.%02d. %02d:%02d:%02d", now.day(), now.month(), now.hour(), now.minute(), now.second());
    String dateTime = String(buf);
    
    lcd1.setCursor(0, row);
    lcd1.print(dateTime);
}

#endif

/*
 * Get message stored to flash (program) memory, padded to LCD lime size
 */
const char* getStringPadded(int index)
{
    static char buffer[21];    // make sure this is large enough for the largest string it must hold
    strcpy_P(buffer, (char*) pgm_read_word(&(string_table[index-1]))); // Necessary casts and dereferencing, just copy. 

    while (strlen(buffer) < 20)
    {
      int len = strlen(buffer);
      buffer[len] = ' ';
      buffer[len+1] = 0x00;
    }

    return buffer;
}

/*
 * Get message stored to flash (program) memory
 */
const char* getStringNonPadded(int index)
{
    static char buffer[25];    // make sure this is large enough for the largest string it must hold
    strcpy_P(buffer, (char*) pgm_read_word(&(string_table[index-1]))); // Necessary casts and dereferencing, just copy. 

    return buffer;
}

/*
 * Append spaces to rest of line to fit LCD 20 chars
 */
String padRight(String &line)
{
    while(line.length() < 20) { line = line + " "; }
    return line;
}

/*
 * Return byte as String representation
 */
void getByteAsString(String &_string, byte _byte)
{
    String binary = String(_byte, BIN);
    while(binary.length() < 8) { binary = "0" + binary; }

    binary.replace('0', (char) char_whitebox);
    binary.replace('1', (char) 0xff);

    String hexa = String(_byte, HEX);
    while(hexa.length() < 2) { hexa = "0" + hexa; }
    
    _string = _string + binary + " 0x" + hexa;
}

/**
 * Connect to WiFi AP
 * @return true if success, otherwise false
 */
boolean wifiConnect()
{
    lcd1.setCursor(0, 3);
    
    Serial << F("Connecting to AP\n");
    String msg = String(F("Connecting to AP"));
    lcd1.print(msg);
    
    // Initiate connection with SSID and PSK
    while(!Fishino.begin(AP_SSID, AP_PASSWORD))
    {
      Serial << ".";
      delay(2000);
    }
    Serial << "OK\n";

    int numberOfTries = 8;
    Serial << F("Waiting for IP...");

    Fishino.staStartDHCP();

    // Wait till connection is established
    while (Fishino.status() != STATION_GOT_IP) 
    {
      if(numberOfTries-- <= 0) 
      {
        Serial << F("WiFi connect failed !");
        lcd1.setCursor(0, 2);
        msg = String(F("WiFi connect failed !"));
        return false;
      }
      delay(1000);
      Serial << ".";
      lcd1.print(".");
    }
    
    Serial.println("");
    Serial << F("WiFi connected\n");

    lcd1.setCursor(0, 2);
    msg = String(F("WiFi connected"));
    lcd1.print(padRight(msg));
    
    return true;
}

void printWifiStatus()
{
    // print the SSID of the network you're attached to:
    Serial << F("SSID: ");
    Serial.println(Fishino.SSID());
  
    // print your WiFi shield's IP address:
    IPAddress ip = Fishino.localIP();
    Serial << F("IP Address: ");
    Serial.println(ip);

    lcd1.setCursor(0, 3);
    lcd1.print("IP: ");
    
    String s0(ip[0]);
    String s1(ip[1]);
    String s2(ip[2]);
    String s3(ip[3]);
    lcd1.print(s0); lcd1.print(".");
    lcd1.print(s1); lcd1.print(".");
    lcd1.print(s2); lcd1.print(".");
    lcd1.print(s3);
  
    // print the received signal strength:
    /*long rssi = Fishino.RSSI();
    Serial << F("Signal strength (RSSI):");
    Serial.print(rssi);
    Serial << F(" dBm\n");

    lcd1.setCursor(0, 3);
    lcd1.print("RSSI: ");
    String strRssi(rssi);
    lcd1.print(strRssi); lcd1.print(" dBm");*/

    // get phy mode and show it
    uint8_t mode = Fishino.getPhyMode();
    Serial.print("PHY MODE: (");
    Serial.print(mode);
    Serial.print(") ");
    switch(mode)
    {
      case PHY_MODE_11B:
        Serial.println("11b");
        lcd1.print(", 11b");
        break;
  
      case PHY_MODE_11G:
        Serial.println("11g");
        lcd1.print(", 11g");
        break;
  
      case PHY_MODE_11N:
        Serial.println("11n");
        lcd1.print(", 11n");
        break;
        
      default:
        Serial.println("UNKNOWN");
    }
}

void printMacAddress()
{
    // print your MAC address:
    byte const *mac = Fishino.macAddress();
    Serial << F("MAC: ");
    Serial.print(mac[5], HEX);
    Serial.print(":");
    Serial.print(mac[4], HEX);
    Serial.print(":");
    Serial.print(mac[3], HEX);
    Serial.print(":");
    Serial.print(mac[2], HEX);
    Serial.print(":");
    Serial.print(mac[1], HEX);
    Serial.print(":");
    Serial.println(mac[0], HEX);
}

/**
 * Get last value of given sensor from IOT service
 */
boolean getValue(const char* service, const char * sensorCode, String &dest)
{
    int nTry = 5;
    bool success;
    do
    {
      success = tryGetValue(service, sensorCode, dest);
      if (!success) { delay(1000); }
    } while (success == false && nTry-- > 0);

    return success;
}

/**
 * Get last value of given sensor from IOT service
 */
bool tryGetValue(const char* service, const char * sensorCode, String &dest)
{
    bool success = false; 
    if (!client.connected() && !client.available())
    {
      Serial << F("\nStarting connection to server...");
      if(client.connect(http_ip, http_port))
      {
        Serial << F("Connected to server\n");
      }
    }
     
    String url = String(F("/IotService/rest/"));
    url += service;
    url += F("?code=");
    url += sensorCode;

    Serial << F("GET data to URL: ");
    Serial.println(url);

    // Make an HTTP GET request
    
    client << F("GET ") << url << F(" HTTP/1.1\r\n");
    client << F("Host: ") << http_ip << F("\r\n"); 
    client << F("Connection: close\r\n");
    client << F("User-Agent: FishinoWiFi/1.1\r\n");
    client.println();

    //String reply = String("");
    delay(100);

    if (!client.available())
    {
      delay(1000);
    }
    
    while(client.available())
    {
      String line = client.readStringUntil('\r');

      if ((line.charAt(1) == '#') && (line.length() <= 25))
      {      
        Serial.print(line);
        dest = line.substring(3, line.length());
        success = true;
      }
      //Serial.println(line);
    }

    return success;
}

/**
 * Send value to IOT service
 */
void sendValue(const char * sensorCode, float value)
{  
    int numberOfTries = 5; 
    while(!client.connect(http_ip, http_port)) 
    {
      Serial.println("Connection to IOT service failed !");
      if(numberOfTries-- <=0) return;
      
      wifiConnect();
    }
    Serial.println("Connected");
 
    String url = "/IotService/rest/insert-value/" + String(sensorCode) + "/" + String(value);

    Serial.print("GET data to URL: ");
    Serial.println(url);

    client << F("GET ") << url << F(" HTTP/1.1\r\n");
    client << F("Host: ") << http_ip << F("\r\n"); 
    client << F("Connection: close\r\n");
    client << F("User-Agent: FishinoWiFi/1.1\r\n");
    client.println();
        
    delay(100);
    while(client.available())
    {
      String line = client.readStringUntil('\r');
      Serial.print(line);
    }
    
    Serial.println();
    Serial.println("Connection closed");
}

///////////////////////////////////////////////////////////////////////////
//
// CCS811 Air Quality sensor support
//
///////////////////////////////////////////////////////////////////////////
#ifdef _USE_CCS811

/*
 * Decode the CCS811Core::status type and prints the type of error to the serial terminal.
 *
 * Save the return value of any function of type CCS811Core::status, then pass
 * to this function to see what the output was.
 */
void printCCS811DriverError(CCS811Core::status errorCode)
{
    switch (errorCode)
    {
      case CCS811Core::SENSOR_SUCCESS:
        Serial.println(F("CCS811 SUCCESS"));
        break;
      case CCS811Core::SENSOR_ID_ERROR:
        Serial.println(F("CCS811 ID ERROR"));
        break;
      case CCS811Core::SENSOR_I2C_ERROR:
        Serial.println(F("CCS811 I2C ERROR"));
        break;
      case CCS811Core::SENSOR_INTERNAL_ERROR:
        Serial.println(F("CCS811 INTERNAL ERROR"));
        break;
      case CCS811Core::SENSOR_GENERIC_ERROR:
        Serial.println(F("CCS811 GENERIC ERROR"));
        break;
      default:
        Serial.println(F("Unspecified error."));
    }
}

/*
 * Gets, clears, then prints the errors saved within the error register.
 */
void printCCS811SensorError()
{
    uint8_t error = ccs811.getErrorRegister();
  
    if (error == 0xFF) //comm error
    {
      Serial.println("Failed to get ERROR_ID register.");
    }
    else
    {
      String msg("CCS811 Error: ");
      if (error & 1 << 5) msg += ("HeaterSupply");
      if (error & 1 << 4) msg += ("HeaterFault");
      if (error & 1 << 3) msg += ("MaxResistance");
      if (error & 1 << 2) msg += ("MeasModeInvalid");
      if (error & 1 << 1) msg += ("ReadRegInvalid");
      if (error & 1 << 0) msg += ("MsgInvalid");
      
      Serial.println(msg);
      lcd1.print(msg);
    }
}

#endif