//ESP Temperature, humitidy and pressure logger based on BME280 sensor and ESP32. Pushes the data to a web database for display. 
//See full project at http://projects.xief.net
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files.
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.


#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "esp_deep_sleep.h"

// #################################### constants ##############################################
String sensorLocation = "Outside1"; //name of the sensor. Will also be used in the webpage for referencing

//BME correction factors for polynomial. For no correction set: B=1, D=C=A=0
#define CORR_D 0  //x^3 
#define CORR_C 0  //x^2
#define CORR_B  1 //x
#define CORR_A  0  //x^0

const char* ssid     = "XXXXXXXXXX"; //change accordingly
const char* password = "XXXXXXXX"; //change accordingly
const char* serverName = "your domain/logger-post-esp-data.php"; //change accordingly
String apiKeyValue = "XXXXXXXX"; // change accordingly, needs to match setting in web page. 
unsigned long wifiMaxWait = 5000; //how long to try initiate wifi connection before aborting, in ms

#define LED 2 //pin of status LED, will be high when ESP active
#define PINVCC 23 //pin of VCC of BME280 - will only briefly be high to power bme280

#define SEALEVELPRESSURE_HPA (1013.25)

// Deep sleep, based on https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/
#define uS_TO_S_FACTOR 1000000  /* Conversion factor for micro seconds to seconds */
unsigned long time_to_sleep = 60;        // Time ESP32 will go to sleep (in seconds), before pushing another data value to cloud
unsigned long time_to_sleep_error = 10;  //in case of error try again faster

// watchdog timer to reset in case it gets stuck, watchdog will trigger reset of ESP
// based on https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Timer/WatchdogTimer/WatchdogTimer.ino
const int wdtTimeout = 10000;  //time in ms to trigger the watchdog in case something gets stuck
hw_timer_t *timer = NULL;
void IRAM_ATTR resetModule() {
  ets_printf("reboot\n");
  esp_restart();
}

Adafruit_BME280 bme;  // I2C SCL -> G22; SDA -> G21; VCC ->G21
bool BMEworking = true; //if initalization fails, will be set to false later and error message pushed to server


//########################################################## intialization #############################################
void setup() {

  //set up watchdog, first thing // try to  move further up in code
  timer = timerBegin(0, 80, true);                  //timer 0, div 80
  timerAttachInterrupt(timer, &resetModule, true);  //attach callback
  timerAlarmWrite(timer, wdtTimeout * 1000, false); //set time in us
  timerAlarmEnable(timer);                          //enable interrupt

  
  btStop(); //stop bluetooth -does not need to consume current
  
  pinMode(LED,OUTPUT);
  digitalWrite(LED,HIGH); //turn on status LED
  pinMode(PINVCC, OUTPUT); 
  digitalWrite(PINVCC,HIGH); //activate power for BME
  
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  bool status = bme.begin(0x76);
  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address! Sending error value to database.");
    BMEworking = false;
  }

  if (BMEworking) {
    bme.setSampling(Adafruit_BME280::MODE_FORCED, // setting BME to weather station configuration
                      Adafruit_BME280::SAMPLING_X1, // temperature
                      Adafruit_BME280::SAMPLING_X1, // pressure
                      Adafruit_BME280::SAMPLING_X1, // humidity
                      Adafruit_BME280::FILTER_OFF   );
  }

  //Print the wakeup reason for ESP32
  //print_wakeup_reason();  //delete if compiled

 unsigned long startMillis = millis();
  while(WiFi.status() != WL_CONNECTED) { 
    if (millis() - startMillis >= wifiMaxWait) {
      Serial.println("Connecting to wifi took to long -> go to sleep");
      goToSleep(time_to_sleep_error);
    }
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());
  
}

//########################################### main loop ####################################
void loop() { 

  //Check WiFi connection status
  if(WiFi.status()== WL_CONNECTED){
    HTTPClient http;
    http.begin(serverName);
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
    String httpRequestData;
    if (BMEworking) {
      bme.takeForcedMeasurement(); // has no effect in normal mode //needed for forced mode (See advanced bme280 example)
      float hum = bme.readHumidity();
      float temp = bme.readTemperature();
      Serial.println("Humidity Measured: " + String(hum));
      Serial.println("Humidity Corrected: " + String(correctHumidity(hum)));

      if (hum == 0 || temp ==0) { //if one of the values is exactly 0 it is most likely a wrong measurement. Discard
        httpRequestData = "api_key=" + apiKeyValue 
                    + "&location=BMEfail_" + sensorLocation + "&temperature=" + String(-101)
                    + "&humidity=" + String(100) + "&pressure=" + String(-101) + "";
        goToSleep(time_to_sleep_error); //quick sleep and try again
        
      } else {
      httpRequestData = "api_key=" + apiKeyValue 
                          + "&location=" + sensorLocation + "&temperature=" + String(bme.readTemperature())
                          + "&humidity=" + String(correctHumidity(bme.readHumidity())) + "&pressure=" + String(bme.readPressure()/100.0F) + "";
      }
                          
    } else
    {
      httpRequestData = "api_key=" + apiKeyValue 
                          + "&location=BMEfail_" + sensorLocation + "&temperature=" + String(-100)
                          + "&humidity=" + String(100) + "&pressure=" + String(-100) + "";
      goToSleep(time_to_sleep_error); //quick sleep and try again
    }
    
    
    Serial.print("httpRequestData: ");
    Serial.println(httpRequestData);
    digitalWrite(PINVCC,LOW);
    int httpResponseCode = http.POST(httpRequestData);
    if (httpResponseCode>0) {
      Serial.print("HTTP Response code: ");
      Serial.println(httpResponseCode);
    }
    else {
      Serial.print("Error code: ");
      Serial.println(httpResponseCode);
    }
    
    http.end(); // Free resources
  }
  else {
    Serial.println("WiFi Disconnected");
  }
  Serial.flush(); 
  digitalWrite(LED,LOW);

  
  goToSleep(time_to_sleep);
}

//################################################## supporting functions ########################################

void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}


void goToSleep(unsigned long sleepTime)   {
  esp_sleep_enable_timer_wakeup(sleepTime * uS_TO_S_FACTOR);
  esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); //all RTC Peripherals to be powered down in sleep
  
  WiFi.mode(WIFI_OFF); //https://esp32.com/viewtopic.php?t=6744
 // btStop(); //stop bluetooth -does not need to consume current //delefe if compiles without error
  esp_deep_sleep_start();
}

float correctHumidity(float humMeasured){
  //  corrected = d*x^3 + c*x^2 + b*x + a; x=measured humidity
  float correctedHumidity = CORR_D*pow(humMeasured,3) + CORR_C*pow(humMeasured,2) + CORR_B*pow(humMeasured,1) + CORR_A;

  return correctedHumidity;
}
