// Marek Sedláček Gas Sensor TGS26XX - ESP32 DEVKIT V1 - Blynk app
// VUT Fakulta elektrotechniky a komunikašných technológií
// 2021/2022
// Source: https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/
// Template ID, Device Name and Auth Token are provided by the Blynk.Cloud
// See the Device Info tab, or Template settings
#define BLYNK_TEMPLATE_ID "TMPLPYpfCdIZ"
#define BLYNK_DEVICE_NAME "Gas Sesnor"
#define BLYNK_AUTH_TOKEN "xvtVkzfeqllqK7YpY1He3uPpdBS1N2cs"

// Comment this out to disable prints and save space
#define BLYNK_PRINT Serial

#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32_SSL.h>
#include <Adafruit_ADS1X15.h>

char auth[] = BLYNK_AUTH_TOKEN;

float scale = 2 / (2 + 1.1); // 2k + 1k1
float scale_capacity = 100.f / (100.f + 27.f); // 100k + 27k
float perc;

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "S2";
char pass[] = "54168855";


#define uS_TO_S_FACTOR 1000000  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP  5 * 60 // Sleep for 5 minutes
#define BUTTON_PIN_BITMASK 0x200000000

RTC_DATA_ATTR int bootCount = 0;

gpio_num_t interruptPin = GPIO_NUM_39;

int buzzerPin = GPIO_NUM_25;

// Sensor 1
const int TGS26XX_1_CHANNEL = 0;

// Metan TGS2611 senzor 1
const float METHAN_LOG10_M_1 = -0.455;
const float METHAN_LOG10_B_1 = 1.683;
const float MAX_PPM_1 = 754;
const float TGS2611_1_R0 = 2.65f;

// Uncomment when changing the gas sensor
// Amoniak TGS2602 senzor 2
//const float AMONIAK_LOG10_M_2 = -0.3056;
//const float AMONIAK_LOG10_B_2 = 0.00458;
//const float MAX_PPM_2 = 52;
//const float TGS2602_2_R0 = 53.f;

// CO TGS2620 senzor 3
//const float CO_LOG10_M_3 = -0.549;
//const float CO_LOG10_B_3 = 1.698;
//const float MAX_PPM_3 = 131;
//const float TGS2620_3_R0 = 1.53f;

Adafruit_ADS1115 ads;  /* Use this for the 16-bit version */

//Method to print the reason by which ESP32 has been awaken from sleep
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 print_wakeup_touchpad() {
  int touchPin = esp_sleep_get_touchpad_wakeup_status();

  switch (touchPin)
  {
    case 0  : Serial.println("Touch detected on GPIO 4"); break;
    case 1  : Serial.println("Touch detected on GPIO 0"); break;
    case 2  : Serial.println("Touch detected on GPIO 2"); break;
    case 3  : Serial.println("Touch detected on GPIO 15"); break;
    case 4  : Serial.println("Touch detected on GPIO 13"); break;
    case 5  : Serial.println("Touch detected on GPIO 12"); break;
    case 6  : Serial.println("Touch detected on GPIO 14"); break;
    case 7  : Serial.println("Touch detected on GPIO 27"); break;
    case 8  : Serial.println("Touch detected on GPIO 33"); break;
    case 9  : Serial.println("Touch detected on GPIO 32"); break;
    default : Serial.println("Wakeup not by touchpad"); break;
  }
}

void tone(byte pin, int freq)
{
  ledcSetup(0, 2000, 8); // setup beeper
  ledcAttachPin(pin, 0); // attach beeper
  ledcWriteTone(0, freq); // play tone
}

void makeSound()
{
  tone(buzzerPin, 500);
}

void silence()
{
  tone(buzzerPin, 0);
}

float ppm_1;
//float ppm_2;
//float ppm_3;

// Battery mapFloat
float mapFloat(float x, float in_min, float in_max, float out_min, float out_max)
{
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

// Measure routine
void measure()
{
  int16_t adc0, adc1, adc2, adc3;
  float volts0, volts1, volts2, volts3;
  Serial.println("stage0");
  adc0 = ads.readADC_SingleEnded(0);
  adc1 = ads.readADC_SingleEnded(1);
  adc2 = ads.readADC_SingleEnded(2);
  adc3 = ads.readADC_SingleEnded(3);

  Serial.println("stage1");
  volts0 = ads.computeVolts(adc0);
  volts1 = ads.computeVolts(adc1);
  volts2 = ads.computeVolts(adc2);
  volts3 = ads.computeVolts(adc3);
  Serial.println("stage2");
  silence();

  // Methan TGS2611 sensor
  float RS_1 = ((5.0 * scale) - volts1) / volts1; // Calibration
  float ratio_1 = RS_1 / TGS2611_1_R0;
  ppm_1 = pow(10, (log10(ratio_1) - METHAN_LOG10_B_1) / METHAN_LOG10_M_1);

  // Amoniak TGS2602 sensor
  //float RS_2 = ((5.0 * scale) - volts2) / volts2; // Calibration
  //float ratio_2 = RS_2 / TGS2602_2_R0;
  //ppm_2 = pow(10, (log10(ratio_2) - AMONIAK_LOG10_B_2) / AMONIAK_LOG10_M_2);

  // CO TGS2620 sensor
  //float RS_3 = ((5.0 * scale) - volts3) / volts3; // Calibration
  //float ratio_3 = RS_3 / TGS2620_3_R0;
  //ppm_3 = pow(10, (log10(ratio_3) - CO_LOG10_B_3) / CO_LOG10_M_3);

  // Battery level
  float battery_voltage = volts0 / scale_capacity;
  perc = mapFloat(battery_voltage, 3.6, 4.2, 0.0, 100.0);



  // Print measured PPM value and battery level
  Serial.print("PPM 1: ");
  Serial.println(ppm_1);
  Serial.print("RS/R0: ");
  Serial.println(ratio_1);
  Serial.print("Battery level= ");
  Serial.print(perc);
  Serial.println(" %");

  if (ppm_1 > MAX_PPM_1)
  {
    makeSound();
  }
}


void setup()
{

  pinMode(buzzerPin, OUTPUT);

  Serial.begin(115200);
  delay(1000); //Take some time to open up the Serial Monitor

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();
  // Print wakeup pin
  print_wakeup_touchpad();

  // Wake up timer
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

  // Wake on single pin high
  esp_sleep_enable_ext0_wakeup(interruptPin, 0); //1 = High, 0 = Low

  //Wake on masked pins going high
  //esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);

  // Init blynk
  Blynk.begin(auth, ssid, pass);
  Blynk.run();
  ads.setGain(GAIN_TWOTHIRDS);

  if (!ads.begin())
  {
    Serial.println("Failed to initialize ADS.");
  }
  else
  {
    // Measure routine
    measure();

    // Send value to virtual pin
    Blynk.virtualWrite(V1, ppm_1);
    Blynk.virtualWrite(V0, perc);
  }
  // Going to sleep mode
  Serial.println("Going to sleep now");
  delay(1000);
  Serial.flush();
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop() {
  // Empty
}
