#include <Arduino.h>
#include <Wire.h>

#include <WiFi.h>

#include <HTTPSRedirect.h>

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#include <SPI.h>
#include <mySD.h>

const char* ssid     = "SSID1";
const char* password = "PASS1";

const char* ssid2     = "SSID2";
const char* password2 = "PASS2";

bool wifiStat = 0; 
bool uploadStat = 0;
int sdStat = 1; 
bool bme280Stat = 1;  
bool oledStat = 1; 

bool booted = 0;
int uploadCounter = 1;

bool serialLoggingEnabled = false;

        #define SCREEN_WIDTH 128 
        #define SCREEN_HEIGHT 64 
        #define OLED_RESET -1 
        Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

        static const uint8_t irhmIcon[240] = {
    0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, 
    0x00, 0xa0, 0x00, 0x00, 0x00, 0x60, 
    0x01, 0xa0, 0x00, 0x00, 0x00, 0xa0, 
    0x03, 0x20, 0x00, 0x00, 0x01, 0x20, 
    0x06, 0x20, 0x00, 0x00, 0x02, 0x20, 
    0x0c, 0x20, 0x00, 0x00, 0x04, 0x20, 
    0x18, 0x20, 0x00, 0x00, 0x08, 0x20, 
    0x30, 0x20, 0x00, 0x00, 0x10, 0x20, 
    0x60, 0x20, 0x00, 0x00, 0x20, 0x20, 
    0xc0, 0x20, 0x00, 0x00, 0x40, 0x20, 
    0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 
    0x90, 0x20, 0x00, 0x00, 0x80, 0x20, 
    0x98, 0x20, 0x00, 0x00, 0x80, 0x20, 
    0x9e, 0x20, 0x00, 0x00, 0x8e, 0x20, 
    0x9f, 0x20, 0x00, 0x00, 0xfe, 0x20, 
    0x9f, 0xff, 0xff, 0xff, 0xfe, 0x20, 
    0x8f, 0xff, 0xff, 0xff, 0xfe, 0x20, 
    0x87, 0xff, 0xff, 0xff, 0xfc, 0x20, 
    0x87, 0xff, 0xff, 0xff, 0xfc, 0x20, 
    0x87, 0xfe, 0x00, 0x1f, 0xfc, 0x20, 
    0x8f, 0xff, 0x00, 0x3f, 0xfe, 0x20, 
    0x8f, 0xbf, 0x80, 0x7e, 0xbe, 0x20, 
    0x8f, 0x3f, 0x80, 0xff, 0x9e, 0x20, 
    0x9f, 0x3f, 0xc1, 0xff, 0x9e, 0x20, 
    0x9f, 0x3f, 0xe3, 0xff, 0x9e, 0x20, 
    0x9f, 0x3f, 0xf7, 0xff, 0x9e, 0x20, 
    0x9f, 0x3f, 0xff, 0xef, 0xbe, 0x20, 
    0x9e, 0x3c, 0xff, 0xc4, 0xbc, 0x20, 
    0x9f, 0x08, 0xff, 0xe0, 0xfc, 0x20, 
    0x8f, 0x83, 0xff, 0xf9, 0xff, 0xe0, 
    0x87, 0xcf, 0xff, 0xff, 0xf8, 0x20, 
    0x87, 0xff, 0xfe, 0xff, 0xf0, 0x60, 
    0x83, 0xff, 0x9e, 0x7f, 0xe0, 0xc0, 
    0x83, 0xff, 0x1c, 0x1f, 0xc1, 0x80, 
    0x86, 0xfc, 0x08, 0x06, 0x83, 0x00, 
    0x8c, 0x00, 0x00, 0x00, 0x86, 0x00, 
    0x98, 0x00, 0x00, 0x00, 0x8c, 0x00, 
    0xb0, 0x00, 0x00, 0x00, 0x98, 0x00, 
    0xe0, 0x00, 0x00, 0x00, 0xf0, 0x00, 
    0xff, 0xff, 0xff, 0xff, 0xe0, 0x00
        };
        static const uint8_t tickIcon[24] = {
    0x00, 0x00, 
    0x00, 0x00, 
    0x00, 0x00, 
    0x00, 0xc0, 
    0x21, 0xe0, 
    0x73, 0xc0, 
    0x7f, 0x80, 
    0x3f, 0x00, 
    0x1e, 0x00, 
    0x0c, 0x00, 
    0x04, 0x00, 
    0x00, 0x00
};
        static const uint8_t crossIcon[24] = {
    0x00, 0x00, 
    0x20, 0x40, 
    0x70, 0xe0, 
    0x79, 0xc0, 
    0x3f, 0x80, 
    0x1e, 0x00, 
    0x0f, 0x00, 
    0x1f, 0x80, 
    0x33, 0x80, 
    0x61, 0xc0, 
    0x40, 0x80, 
    0x00, 0x00
};       
        static const uint8_t wifiIcon[24] = {
    0x00, 0x00, 
    0x3f, 0xc0, 
    0x40, 0x20, 
    0x80, 0x10, 
    0x1f, 0x80, 
    0x20, 0x40, 
    0x40, 0x20, 
    0x0f, 0x00, 
    0x10, 0x80, 
    0x26, 0x40, 
    0x06, 0x00, 
    0x0f, 0x00
};
        static const uint8_t sdIcon[24] = {
    0x00, 0x00, 
    0x7f, 0xc0, 
    0x55, 0x40, 
    0x55, 0x40, 
    0x7f, 0xc0, 
    0x7f, 0xe0, 
    0x7f, 0xe0, 
    0x7f, 0xc0, 
    0x7f, 0xc0, 
    0x7f, 0xe0, 
    0x40, 0x60, 
    0x7f, 0xe0
};
        static const uint8_t uploadIcon[24] = {
    0x00, 0x00, 
    0x7e, 0x00, 
    0xc3, 0xf0, 
    0x80, 0x10, 
    0x82, 0x10, 
    0x87, 0x10, 
    0x8a, 0x90, 
    0x82, 0x10, 
    0x82, 0x10, 
    0x80, 0x10, 
    0xff, 0xf0, 
    0x00, 0x00
};

        #define SEALEVELPRESSURE_HPA (1013.25)
        Adafruit_BME280 bme;

        const int chipSelect = 5;
        int logCounter =1;

unsigned long readSensorTimer = 5010UL;
const unsigned long readSensorInterval = 5000UL;

unsigned long displayStatsTimer = 5010UL;
const unsigned long displayStatsInterval = 5000UL;

unsigned long logToFileTimer = 60010UL;
const unsigned long logToFileInterval = 60000UL; 

unsigned long uploadStatsTimer = 0;
const unsigned long uploadStatsInterval = 60000UL;

void setup() {

            Serial.begin(115200);
            Serial.println("-- IRHM Air Quality Logger --");

        if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
        Serial.println(F("SSD1306 allocation failed"));    
        oledStat = 0;
        }

        if (! bme.begin(0x76, &Wire)) {
        Serial.println("Could not find a valid BME280 sensor, check wiring!");
        bme280Stat = 0;
        }
               bme.setSampling(Adafruit_BME280::MODE_FORCED,
                       Adafruit_BME280::SAMPLING_X1, 
                       Adafruit_BME280::SAMPLING_X1, 
                       Adafruit_BME280::SAMPLING_X1, 
                       Adafruit_BME280::FILTER_OFF   );

        if (!SD.begin(chipSelect)) {
          Serial.println("Card failed, or not present");
        sdStat = 0;
        }

       Serial.println("---------------");
       Serial.println("- Active Settings -");
       Serial.println("Filter Off | Manual Mode | 1X Oversampling Measurement");
       Serial.println("1 - Enable Serial Logging | 0 - Disable Serial Logging");
       Serial.println("---------------");
       Serial.println("- On-board  Devices  Status- ");
       Serial.print("Wireless LAN = ");Serial.print(wifiStat);Serial.print(" SD Card = ");Serial.print(sdStat);Serial.print(" BME280 = ");Serial.print(bme280Stat);Serial.print(" OLED Display = ");Serial.println(oledStat);
       Serial.println("---------------");

    readSensorTimer = millis();
    displayStatsTimer = millis();
    logToFileTimer = millis();
    uploadStatsTimer = millis();
}

int wifiConnected(){

      if (WiFi.status() != WL_CONNECTED) {
             wifiStat = 0;
             uploadStat = 0;
             WiFi.begin(ssid, password);
             WiFi.setHostname("IRHM Logger. 1");
             Serial.println("\n");
             Serial.print("Connecting to ");
             Serial.print(ssid); Serial.print(".");
            for (int i = 0; i < 5; i++)
            {
              Serial.print(".");
              delay (1000);
            }
            }

      if (WiFi.status() != WL_CONNECTED) {
             wifiStat = 0;
             uploadStat = 0;
             WiFi.begin(ssid2, password2);
             WiFi.setHostname("IRHM Logger. 1");
             Serial.println("\n");
             Serial.print("Connecting to ");
             Serial.print(ssid2); Serial.print(".");
            for (int i = 0; i < 5; i++)
            {
              Serial.print(".");
              delay (1000);
            }
            }

            if(WiFi.status() != WL_CONNECTED) {
              Serial.println("\n");
              Serial.println("Unable to connect to WIFI!");
              wifiStat = 0;
              return 0;
            } else {
            Serial.println("\n");
            Serial.println("Wifi Connection established!");  
            Serial.print("IP address:");
            Serial.println(WiFi.localIP());
            wifiStat = 1;
            return 1;
            }

}

void uploadStats(float temp, float hum, float psr) {

          if (wifiConnected())
          {      
          Serial.println("Beginning Cloud Upload...");

                    Serial.print("Heap Size 1 = "); Serial.println(esp_get_free_heap_size());
          const char *GScriptId = "ENTERYOURGOOGLESCRIPTID";

          String payload_base =  "{\"command\": \"append_row\", \"sheet_name\": \"Status Reports\", \"values\": ";
          String payload = "";

          const char* host = "script.google.com";
          const int httpsPort = 443;
          const char* fingerprint = "";
          String url = String("/macros/s/") + GScriptId + "/exec?cal";
          HTTPSRedirect* client = nullptr;

      client = new HTTPSRedirect(httpsPort);
      client->setInsecure();
      client->setPrintResponseBody(true);
      client->setContentTypeHeader("application/json");

          Serial.print("Connecting to ");
          Serial.println(host);

          bool flag = false;
          for (int i=0; i<5; i++){ 
            int retval = client->connect(host, httpsPort);
            if (retval == 1){
                flag = true;
                Serial.println("Connected");
            break;
            }
            else
              Serial.println("Connection failed. Retrying...");
            }

            if (!flag){
              Serial.print("Could not connect to server: ");
              Serial.println(host);
            }

            delete client;    
            client = nullptr; 

          flag = false;
          if (!flag)
          {
              client = new HTTPSRedirect(httpsPort);
              client->setInsecure();
              flag = true;
              client->setPrintResponseBody(true);
              client->setContentTypeHeader("application/json");
          }
                     Serial.print("Heap Size 2 = "); Serial.println(esp_get_free_heap_size());
          if (client != nullptr)
          {
            if (!client->connected())
            {
                client->connect(host, httpsPort);
            }
          }else{
          Serial.println("Error creating client object!");
          }

        payload = payload_base + "\"" + uploadCounter++ + "," + temp + "," + hum + "," + psr/133.322 + "\"}";

        Serial.println("Publishing data...");
        Serial.println(payload);
        if(client->POST(url, host, payload)){ 

          uploadStat = 1;
        }
          else{

        Serial.println("Error while connecting");
          uploadStat = 0;
        }

  Serial.println("Data uploaded to Cloud.");
            delete client;    
            client = nullptr; 
      }

}

void displayStats(float temp, float hum, float psr, int wifiStat) {

    if (serialLoggingEnabled)
    {
    Serial.println("-- Sensor Data Acquired ----------");
    Serial.print("Temparature - ");Serial.print(temp); Serial.println(" *C");
    Serial.print("Humidity    - ");Serial.print(hum); Serial.println("%");
    Serial.print("Pressure    - ");Serial.print(psr/133.322); Serial.println(" mmHg");
    Serial.print("Apx. Alti.  - ");Serial.print(temp); Serial.println(" Meters");
    }

          display.clearDisplay();
          display.setTextSize(1);             
          display.setTextColor(WHITE);        
          display.setCursor(50,0);
          display.print(temp);
          display.print(" C"));
          display.setCursor(50,11);
          display.print(hum);
          display.print(" %"));
          display.setCursor(50,22);
          display.print(psr/133.322);
          display.print(" mmHg");
          display.drawBitmap(0, 0, irhmIcon, 43, 40, 1);
          display.drawBitmap(0, 41, wifiIcon, 12, 12, 1);
          if (wifiStat == 1){display.drawBitmap(0, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(0, 54, crossIcon, 12, 12, 1);}
          display.drawBitmap(13, 41, uploadIcon, 12, 12, 1);
          if (uploadStat == 1){display.drawBitmap(13, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(13, 54, crossIcon, 12, 12, 1);}
          display.drawBitmap(26, 41, sdIcon, 12, 12, 1);
          if (sdStat == 1){display.drawBitmap(26, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(26, 54, crossIcon, 12, 12, 1);}

              if (wifiStat == 1)
              {
              display.setCursor(50,33);
              display.print(WiFi.localIP());
              }else if (wifiStat == 0){
              display.setCursor(50,33);
              display.print("No network :(");
              }else{
              display.setCursor(50,33);
              display.print("Upload Failed!"); 
              }
         display.display();

  Serial.println("Display updated successfully.");
}

void logToFile(float temp, float hum, float psr){

  File dataFile = SD.open("datalog.csv", FILE_WRITE);

  if (dataFile) {
    dataFile.print(logCounter++);
    dataFile.print(",");
    dataFile.print(temp);
    dataFile.print(",");
    dataFile.print(hum);
    dataFile.print(",");
    dataFile.println(psr/133.322);

    dataFile.close();

    Serial.println("Data logged.");
    sdStat = 1;
  }

  else {
    Serial.println("Error opening datalog.csv");
    sdStat = 2;
  }
}

void readSensor() {
  bme.takeForcedMeasurement();
  Serial.println("Sensor data acquired.");
}

void loop() {

  if (esp_get_free_heap_size() <= 15000)
  {
  Serial.print("Heap Size Below Treshold = "); Serial.println(esp_get_free_heap_size());
  }

  if (booted == false){
    Serial.println("System Booting...");
    Serial.print("Heap Size Initial = "); Serial.println(esp_get_free_heap_size());
          display.clearDisplay();
          display.setTextSize(1);             
          display.setTextColor(WHITE);        
          display.setCursor(50,0);
          display.print("Atmo. Logger");
          display.setCursor(50,11);
          display.print("Initialising");
          display.setCursor(50,20);
          display.print("------------");
          display.drawBitmap(0, 0, irhmIcon, 43, 40, 1);
          display.drawBitmap(0, 41, wifiIcon, 12, 12, 1);
          if (wifiConnected() == 1){display.drawBitmap(0, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(0, 54, crossIcon, 12, 12, 1);}
          display.drawBitmap(13, 41, uploadIcon, 12, 12, 1);
          if (uploadStat == 1){display.drawBitmap(13, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(13, 54, crossIcon, 12, 12, 1);}
          display.drawBitmap(26, 41, sdIcon, 12, 12, 1);
          if (sdStat == 1){display.drawBitmap(26, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(26, 54, crossIcon, 12, 12, 1);}

          display.display();

         if (((wifiStat == 0 || wifiStat == 2) && (sdStat == 0 || sdStat == 2)) || bme280Stat == 0)
         {
           Serial.println("Unable to Log Data. Components are either missing or inoperable.");
           Serial.print("Wireless LAN = ");Serial.print(wifiStat);Serial.print(" SD Card = ");Serial.print(sdStat);;Serial.print(" BME280 = ");Serial.println(bme280Stat);
           Serial.println("Restarting in 5 seconds!");
           display.setCursor(50,30);
           display.print("SD/WiFi/Sens");
           display.setCursor(61,41);
           display.print("Missing");
           display.setCursor(53,51);
           display.print("Restarting!");
           display.display();
           delay(20000);
           ESP.restart();
         }

         uploadStats(bme.readTemperature(), bme.readHumidity(), bme.readPressure());

    booted = true;
    delay(2000);
  }

    if (Serial.available() > 0) {
    char command = Serial.read(); 
    if (command == 1) {
      serialLoggingEnabled = true;
      }
      else if (command == 0)
      {
      serialLoggingEnabled = false;
      }

}

  if(((millis() -  readSensorTimer) >= readSensorInterval) && bme280Stat == 1){
    readSensor();
    readSensorTimer = millis();
  }

  if(((millis() -  displayStatsTimer) >= displayStatsInterval) && bme280Stat == 1 && oledStat == 1){
    displayStats(bme.readTemperature(), bme.readHumidity(), bme.readPressure(), wifiStat);
    displayStatsTimer = millis();
  }

  if(((millis() - logToFileTimer) >= logToFileInterval) && bme280Stat == 1 && sdStat == 1){
    logToFile(bme.readTemperature(), bme.readHumidity(), bme.readPressure());
    logToFileTimer = millis();
  }

  if(((millis() - uploadStatsTimer) >= uploadStatsInterval) && bme280Stat == 1){
    uploadStats(bme.readTemperature(), bme.readHumidity(), bme.readPressure());
    uploadStatsTimer = millis();
  }

}
/*Temparature, Humidity & Pressure Logger - Berkman Lord Gülenç
This script will log the data from BME280 sensor to Google Sheets and to an SD Card that is connected through SPI to ESP32.
In addition, it will display the stats on Adafruit_SSD1306 OLED display.

Please make sure to add all the libraries, HTTPSRedirect must be downloaded from the link provided.

https://github.com/jbuszkie/HTTPSRedirect

Click on the link below to learn more about the Google Sheets script by StorageB.

https://github.com/StorageB/Google-Sheets-Logging

- Pinout for ESP32-DevKitC V4
- Card Reader SPI
VIN  - 5V - PLEASE MAKE SURE YOUR CARD READER SUPPORTS 5VOLTS.
CS   - GPIO 5
MOSI - GPIO 23
CLK  - GPIO 18
MISO - GPIO 19
GND	 - GND

- I2C
SCL GPIO 22
SDA GPIO 21

*/


#include <Arduino.h>
#include <Wire.h>

//WIFI Libraries
#include <WiFi.h>

//https://github.com/jbuszkie/HTTPSRedirect
//For ESP32, make sure to uncomment Line 89, stop(); in HTTPSRedirect.h
#include <HTTPSRedirect.h>


//OLED Display Libraries
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//Bosch BME280 Sensor Libraries
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

//SD Card Reader Libraries
#include <SPI.h>
#include <mySD.h>

//Hard-coded Wifi Settings - Temporary
const char* ssid     = "SSID1";
const char* password = "PASS1";

const char* ssid2     = "SSID2";
const char* password2 = "PASS2";

bool wifiStat = 0; //0 - Not Connected, 1 - Connected, 2 - No IP
bool uploadStat = 0;
int sdStat = 1; //0 - Not Initialized, 1 - Initialized, 3 - No File
bool bme280Stat = 1;  //0 - Not Initialized, 1 - Initialized
bool oledStat = 1; //0 - Not Initialized, 1 - Initialized

bool booted = 0;
int uploadCounter = 1;

//Logging sensor data to Serial Console. Disabled to avoid clutter.
//Enable by typing 1 to Serial Monitor as Binary.
bool serialLoggingEnabled = false;

//User Control Pins - Digital - Internal Pullup
//int debugPin     = 15;
//int measurePin   = 2;

        //OLED Display Initialization
        #define SCREEN_WIDTH 128 // OLED display width, in pixels
        #define SCREEN_HEIGHT 64 // OLED display height, in pixels
        #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
        Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

        //Screen Icons - Icons are made using the LCD Image Converter and Pixilart.com
		//https://www.pixilart.com
		//https://sourceforge.net/projects/lcd-image-converter/
		//https://create.arduino.cc/projecthub/Arnov_Sharma_makes/displaying-your-own-photo-on-oled-display-5a8e8b
        static const uint8_t irhmIcon[240] = {
    0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, 
    0x00, 0xa0, 0x00, 0x00, 0x00, 0x60, 
    0x01, 0xa0, 0x00, 0x00, 0x00, 0xa0, 
    0x03, 0x20, 0x00, 0x00, 0x01, 0x20, 
    0x06, 0x20, 0x00, 0x00, 0x02, 0x20, 
    0x0c, 0x20, 0x00, 0x00, 0x04, 0x20, 
    0x18, 0x20, 0x00, 0x00, 0x08, 0x20, 
    0x30, 0x20, 0x00, 0x00, 0x10, 0x20, 
    0x60, 0x20, 0x00, 0x00, 0x20, 0x20, 
    0xc0, 0x20, 0x00, 0x00, 0x40, 0x20, 
    0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 
    0x90, 0x20, 0x00, 0x00, 0x80, 0x20, 
    0x98, 0x20, 0x00, 0x00, 0x80, 0x20, 
    0x9e, 0x20, 0x00, 0x00, 0x8e, 0x20, 
    0x9f, 0x20, 0x00, 0x00, 0xfe, 0x20, 
    0x9f, 0xff, 0xff, 0xff, 0xfe, 0x20, 
    0x8f, 0xff, 0xff, 0xff, 0xfe, 0x20, 
    0x87, 0xff, 0xff, 0xff, 0xfc, 0x20, 
    0x87, 0xff, 0xff, 0xff, 0xfc, 0x20, 
    0x87, 0xfe, 0x00, 0x1f, 0xfc, 0x20, 
    0x8f, 0xff, 0x00, 0x3f, 0xfe, 0x20, 
    0x8f, 0xbf, 0x80, 0x7e, 0xbe, 0x20, 
    0x8f, 0x3f, 0x80, 0xff, 0x9e, 0x20, 
    0x9f, 0x3f, 0xc1, 0xff, 0x9e, 0x20, 
    0x9f, 0x3f, 0xe3, 0xff, 0x9e, 0x20, 
    0x9f, 0x3f, 0xf7, 0xff, 0x9e, 0x20, 
    0x9f, 0x3f, 0xff, 0xef, 0xbe, 0x20, 
    0x9e, 0x3c, 0xff, 0xc4, 0xbc, 0x20, 
    0x9f, 0x08, 0xff, 0xe0, 0xfc, 0x20, 
    0x8f, 0x83, 0xff, 0xf9, 0xff, 0xe0, 
    0x87, 0xcf, 0xff, 0xff, 0xf8, 0x20, 
    0x87, 0xff, 0xfe, 0xff, 0xf0, 0x60, 
    0x83, 0xff, 0x9e, 0x7f, 0xe0, 0xc0, 
    0x83, 0xff, 0x1c, 0x1f, 0xc1, 0x80, 
    0x86, 0xfc, 0x08, 0x06, 0x83, 0x00, 
    0x8c, 0x00, 0x00, 0x00, 0x86, 0x00, 
    0x98, 0x00, 0x00, 0x00, 0x8c, 0x00, 
    0xb0, 0x00, 0x00, 0x00, 0x98, 0x00, 
    0xe0, 0x00, 0x00, 0x00, 0xf0, 0x00, 
    0xff, 0xff, 0xff, 0xff, 0xe0, 0x00
        };
        static const uint8_t tickIcon[24] = {
    0x00, 0x00, 
    0x00, 0x00, 
    0x00, 0x00, 
    0x00, 0xc0, 
    0x21, 0xe0, 
    0x73, 0xc0, 
    0x7f, 0x80, 
    0x3f, 0x00, 
    0x1e, 0x00, 
    0x0c, 0x00, 
    0x04, 0x00, 
    0x00, 0x00
};
        static const uint8_t crossIcon[24] = {
    0x00, 0x00, 
    0x20, 0x40, 
    0x70, 0xe0, 
    0x79, 0xc0, 
    0x3f, 0x80, 
    0x1e, 0x00, 
    0x0f, 0x00, 
    0x1f, 0x80, 
    0x33, 0x80, 
    0x61, 0xc0, 
    0x40, 0x80, 
    0x00, 0x00
};       
        static const uint8_t wifiIcon[24] = {
    0x00, 0x00, 
    0x3f, 0xc0, 
    0x40, 0x20, 
    0x80, 0x10, 
    0x1f, 0x80, 
    0x20, 0x40, 
    0x40, 0x20, 
    0x0f, 0x00, 
    0x10, 0x80, 
    0x26, 0x40, 
    0x06, 0x00, 
    0x0f, 0x00
};
        static const uint8_t sdIcon[24] = {
    0x00, 0x00, 
    0x7f, 0xc0, 
    0x55, 0x40, 
    0x55, 0x40, 
    0x7f, 0xc0, 
    0x7f, 0xe0, 
    0x7f, 0xe0, 
    0x7f, 0xc0, 
    0x7f, 0xc0, 
    0x7f, 0xe0, 
    0x40, 0x60, 
    0x7f, 0xe0
};
        static const uint8_t uploadIcon[24] = {
    0x00, 0x00, 
    0x7e, 0x00, 
    0xc3, 0xf0, 
    0x80, 0x10, 
    0x82, 0x10, 
    0x87, 0x10, 
    0x8a, 0x90, 
    0x82, 0x10, 
    0x82, 0x10, 
    0x80, 0x10, 
    0xff, 0xf0, 
    0x00, 0x00
};

        //Bosch BME280 Initialization
        #define SEALEVELPRESSURE_HPA (1013.25)
        Adafruit_BME280 bme;

        //SD Card Initialization
        const int chipSelect = 5;
        int logCounter =1;


//Timer Variables for Measurement and Logging - 60000UL - 60 Seconds
unsigned long readSensorTimer = 5010UL;
const unsigned long readSensorInterval = 5000UL;

unsigned long displayStatsTimer = 5010UL;
const unsigned long displayStatsInterval = 5000UL;

unsigned long logToFileTimer = 60010UL;
const unsigned long logToFileInterval = 60000UL; 

unsigned long uploadStatsTimer = 0;
const unsigned long uploadStatsInterval = 60000UL;


void setup() {
  // put your setup code here, to run once:

            //Serial Setup
            Serial.begin(115200);
            Serial.println("-- IRHM Air Quality Logger --");
  
  //User Control Pins
  //pinMode(debugPin, INPUT_PULLUP);
  //pinMode(measurePin, INPUT_PULLUP);

        // OLED Display Setup
        // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
        if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
        Serial.println(F("SSD1306 allocation failed"));    
        oledStat = 0;
        }

        //BME280 Sensor Setup
        if (! bme.begin(0x76, &Wire)) {
        Serial.println("Could not find a valid BME280 sensor, check wiring!");
        bme280Stat = 0;
        }
               bme.setSampling(Adafruit_BME280::MODE_FORCED,
                       Adafruit_BME280::SAMPLING_X1, // temperature
                       Adafruit_BME280::SAMPLING_X1, // pressure
                       Adafruit_BME280::SAMPLING_X1, // humidity
                       Adafruit_BME280::FILTER_OFF   );
                       // suggested rate is 1/60Hz (1m)
                       //delayTime = 30000; // in milliseconds
        
        //SD Card Setup
        if (!SD.begin(chipSelect)) {
          Serial.println("Card failed, or not present");
        sdStat = 0;
        }



       Serial.println("---------------");
       Serial.println("- Active Settings -");
       Serial.println("Filter Off | Manual Mode | 1X Oversampling Measurement");
       Serial.println("1 - Enable Serial Logging | 0 - Disable Serial Logging");
       Serial.println("---------------");
       Serial.println("- On-board  Devices  Status- ");
       Serial.print("Wireless LAN = ");Serial.print(wifiStat);Serial.print(" SD Card = ");Serial.print(sdStat);Serial.print(" BME280 = ");Serial.print(bme280Stat);Serial.print(" OLED Display = ");Serial.println(oledStat);
       Serial.println("---------------");

    readSensorTimer = millis();
    displayStatsTimer = millis();
    logToFileTimer = millis();
    uploadStatsTimer = millis();
}

int wifiConnected(){

      //Try Primary Network
      if (WiFi.status() != WL_CONNECTED) {
             wifiStat = 0;
             uploadStat = 0;
             WiFi.begin(ssid, password);
             WiFi.setHostname("IRHM Logger. 1");
             Serial.println("\n");
             Serial.print("Connecting to ");
             Serial.print(ssid); Serial.print(".");
            for (int i = 0; i < 5; i++)
            {
              Serial.print(".");
              delay (1000);
            }
            }

      //Try Secondary Network
      if (WiFi.status() != WL_CONNECTED) {
             wifiStat = 0;
             uploadStat = 0;
             WiFi.begin(ssid2, password2);
             WiFi.setHostname("IRHM Logger. 1");
             Serial.println("\n");
             Serial.print("Connecting to ");
             Serial.print(ssid2); Serial.print(".");
            for (int i = 0; i < 5; i++)
            {
              Serial.print(".");
              delay (1000);
            }
            }

            if(WiFi.status() != WL_CONNECTED) {
              Serial.println("\n");
              Serial.println("Unable to connect to WIFI!");
              wifiStat = 0;
              return 0;
            } else {
            Serial.println("\n");
            Serial.println("Wifi Connection established!");  
            Serial.print("IP address:");
            Serial.println(WiFi.localIP());
            wifiStat = 1;
            return 1;
            }
     
}

void uploadStats(float temp, float hum, float psr) {
       
  // This function uploads data to Google Sheets.
  // More info and original script can be found at https://github.com/StorageB/Google-Sheets-Logging 
  
          if (wifiConnected())
          {      
          Serial.println("Beginning Cloud Upload...");
          //Cloud Connection Settings
          //Enter Google Script Deployment ID:
                    Serial.print("Heap Size 1 = "); Serial.println(esp_get_free_heap_size());
          const char *GScriptId = "ENTERYOURGOOGLESCRIPTID";
          // Enter command (insert_row or append_row) and your Google Sheets sheet name (default is Sheet1):
          String payload_base =  "{\"command\": \"append_row\", \"sheet_name\": \"Status Reports\", \"values\": ";
          String payload = "";
          // Google Sheets setup (do not edit)
          const char* host = "script.google.com";
          const int httpsPort = 443;
          const char* fingerprint = "";
          String url = String("/macros/s/") + GScriptId + "/exec?cal";
          HTTPSRedirect* client = nullptr;
                   

      // Use HTTPSRedirect class to create a new TLS connection
      client = new HTTPSRedirect(httpsPort);
      client->setInsecure();
      client->setPrintResponseBody(true);
      client->setContentTypeHeader("application/json");
  
          Serial.print("Connecting to ");
          Serial.println(host);

          // Try to connect for a maximum of 5 times
          bool flag = false;
          for (int i=0; i<5; i++){ 
            int retval = client->connect(host, httpsPort);
            if (retval == 1){
                flag = true;
                Serial.println("Connected");
            break;
            }
            else
              Serial.println("Connection failed. Retrying...");
            }

            if (!flag){
              Serial.print("Could not connect to server: ");
              Serial.println(host);
            }
  
            delete client;    // delete HTTPSRedirect object
            client = nullptr; // delete HTTPSRedirect object

          flag = false;
          if (!flag)
          {
              client = new HTTPSRedirect(httpsPort);
              client->setInsecure();
              flag = true;
              client->setPrintResponseBody(true);
              client->setContentTypeHeader("application/json");
          }
                     Serial.print("Heap Size 2 = "); Serial.println(esp_get_free_heap_size());
          if (client != nullptr)
          {
            if (!client->connected())
            {
                client->connect(host, httpsPort);
            }
          }else{
          Serial.println("Error creating client object!");
          }

        // Create json object string to send to Google Sheets. Pascals are converted to mmHg
        payload = payload_base + "\"" + uploadCounter++ + "," + temp + "," + hum + "," + psr/133.322 + "\"}";

        // Publish data to Google Sheets
        Serial.println("Publishing data...");
        Serial.println(payload);
        if(client->POST(url, host, payload)){ 
          // do stuff here if publish was successful
          uploadStat = 1;
        }
          else{
         // do stuff here if publish was not successful
        Serial.println("Error while connecting");
          uploadStat = 0;
        }
  
  
  Serial.println("Data uploaded to Cloud.");
            delete client;    // delete HTTPSRedirect object
            client = nullptr; // delete HTTPSRedirect object
      }

}

void displayStats(float temp, float hum, float psr, int wifiStat) {
  // This function updates the display with real-time sensor data.

    //Serial Monitor
    if (serialLoggingEnabled)
    {
    Serial.println("-- Sensor Data Acquired ----------");
    Serial.print("Temparature - ");Serial.print(temp); Serial.println(" *C");
    Serial.print("Humidity    - ");Serial.print(hum); Serial.println("%");
    Serial.print("Pressure    - ");Serial.print(psr/133.322); Serial.println(" mmHg");
    Serial.print("Apx. Alti.  - ");Serial.print(temp); Serial.println(" Meters");
    }


          //OLED Display
          display.clearDisplay();
          display.setTextSize(1);             // Normal 1:1 pixel scale
          display.setTextColor(WHITE);        // Draw white text
          display.setCursor(50,0);
          display.print(temp);
          display.print(" C"));
          display.setCursor(50,11);
          display.print(hum);
          display.print(" %"));
          display.setCursor(50,22);
          display.print(psr/133.322);
          display.print(" mmHg");
          display.drawBitmap(0, 0, irhmIcon, 43, 40, 1);
          display.drawBitmap(0, 41, wifiIcon, 12, 12, 1);
          if (wifiStat == 1){display.drawBitmap(0, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(0, 54, crossIcon, 12, 12, 1);}
          display.drawBitmap(13, 41, uploadIcon, 12, 12, 1);
          if (uploadStat == 1){display.drawBitmap(13, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(13, 54, crossIcon, 12, 12, 1);}
          display.drawBitmap(26, 41, sdIcon, 12, 12, 1);
          if (sdStat == 1){display.drawBitmap(26, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(26, 54, crossIcon, 12, 12, 1);}

              //Wifi Status
              if (wifiStat == 1)
              {
              display.setCursor(50,33);
              display.print(WiFi.localIP());
              }else if (wifiStat == 0){
              display.setCursor(50,33);
              display.print("No network :(");
              }else{
              display.setCursor(50,33);
              display.print("Upload Failed!"); 
              }
         display.display();

        ////User Control Pins
        /*if (digitalRead(debugPin) == LOW)
            {
                //Debug Info
            }*/

  Serial.println("Display updated successfully.");
}

void logToFile(float temp, float hum, float psr){

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  File dataFile = SD.open("datalog.csv", FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile) {
    dataFile.print(logCounter++);
    dataFile.print(",");
    dataFile.print(temp);
    dataFile.print(",");
    dataFile.print(hum);
    dataFile.print(",");
    dataFile.println(psr/133.322);

    dataFile.close();
    // print to the serial port too:
    Serial.println("Data logged.");
    sdStat = 1;
  }
  // if the file isn't open, pop up an error:
  else {
    Serial.println("Error opening datalog.csv");
    sdStat = 2;
  }
}

void readSensor() {
  bme.takeForcedMeasurement();
  Serial.println("Sensor data acquired.");
}


void loop() {
  // put your main code here, to run repeatedly:

  if (esp_get_free_heap_size() <= 15000)
  {
  Serial.print("Heap Size Below Treshold = "); Serial.println(esp_get_free_heap_size());
  }
  
          
 //Sensor is read every 30 seconds.
 //Display is updated by Sensor Read function.
 //Data is uploaded every minute.

  if (booted == false){
    Serial.println("System Booting...");
    Serial.print("Heap Size Initial = "); Serial.println(esp_get_free_heap_size());
          display.clearDisplay();
          display.setTextSize(1);             // Normal 1:1 pixel scale
          display.setTextColor(WHITE);        // Draw white text
          display.setCursor(50,0);
          display.print("Atmo. Logger");
          display.setCursor(50,11);
          display.print("Initialising");
          display.setCursor(50,20);
          display.print("------------");
          display.drawBitmap(0, 0, irhmIcon, 43, 40, 1);
          display.drawBitmap(0, 41, wifiIcon, 12, 12, 1);
          if (wifiConnected() == 1){display.drawBitmap(0, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(0, 54, crossIcon, 12, 12, 1);}
          display.drawBitmap(13, 41, uploadIcon, 12, 12, 1);
          if (uploadStat == 1){display.drawBitmap(13, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(13, 54, crossIcon, 12, 12, 1);}
          display.drawBitmap(26, 41, sdIcon, 12, 12, 1);
          if (sdStat == 1){display.drawBitmap(26, 54, tickIcon, 12, 12, 1);}else{display.drawBitmap(26, 54, crossIcon, 12, 12, 1);}

          display.display();
  
        // System Check
         if (((wifiStat == 0 || wifiStat == 2) && (sdStat == 0 || sdStat == 2)) || bme280Stat == 0)
         {
           Serial.println("Unable to Log Data. Components are either missing or inoperable.");
           Serial.print("Wireless LAN = ");Serial.print(wifiStat);Serial.print(" SD Card = ");Serial.print(sdStat);;Serial.print(" BME280 = ");Serial.println(bme280Stat);
           Serial.println("Restarting in 5 seconds!");
           display.setCursor(50,30);
           display.print("SD/WiFi/Sens");
           display.setCursor(61,41);
           display.print("Missing");
           display.setCursor(53,51);
           display.print("Restarting!");
           display.display();
           delay(20000);
           ESP.restart();
         }

         uploadStats(bme.readTemperature(), bme.readHumidity(), bme.readPressure());

    booted = true;
    delay(2000);
  }

    if (Serial.available() > 0) {
    char command = Serial.read(); // read the next char received
    if (command == 1) {
      serialLoggingEnabled = true;
      }
      else if (command == 0)
      {
      serialLoggingEnabled = false;
      }
      
}
  //Read Sensor
  if(((millis() -  readSensorTimer) >= readSensorInterval) && bme280Stat == 1){
    readSensor();
    readSensorTimer = millis();
  }

  //Display Stats on OLED
  if(((millis() -  displayStatsTimer) >= displayStatsInterval) && bme280Stat == 1 && oledStat == 1){
    displayStats(bme.readTemperature(), bme.readHumidity(), bme.readPressure(), wifiStat);
    displayStatsTimer = millis();
  }

  //Log sensor data to the SD Card
  if(((millis() - logToFileTimer) >= logToFileInterval) && bme280Stat == 1 && sdStat == 1){
    logToFile(bme.readTemperature(), bme.readHumidity(), bme.readPressure());
    logToFileTimer = millis();
  }

  //Upload sensor data to Google Sheets
  if(((millis() - uploadStatsTimer) >= uploadStatsInterval) && bme280Stat == 1){
    uploadStats(bme.readTemperature(), bme.readHumidity(), bme.readPressure());
    uploadStatsTimer = millis();
  }

              /*if (digitalRead(measurePin) == LOW){
              readSensor();
              delay(5000);
              }

              if (digitalRead(debugPin) == LOW){
              Serial.print("Sensor Timer - ");
              Serial.println(readSensorTimer);
              Serial.print("Upload Timer - ");
              Serial.println(uploadStatsTimer);
              Serial.print("Millis - ");
              Serial.println(millis());
                readSensor();
                delay(5000);
              }*/
}