//Include library of temperature sensor
#include <Temperature_LM75_Derived.h>
Generic_LM75 temperature; //Creating object for library

#define CONVERT_US 1000000 //Conversion microseconds to seconds
#define DEEP_SLEEP_TIME  60 //Deep Sleep time in seconds

RTC_DATA_ATTR bool isModemConfiguration = false; //Variable to storage information about modem configuration

//Defines the RX and TX pins for the cellular modem
#define RXD2 16
#define TXD2 17

//Define variables for Google Sheet script
String URL = "https://script.google.com/macros/s/AKfycbxNgg50yw9BFUv2CP464vh1-IawVx9Km2JSQ7fC6lSAdk1WtRSR19PxPHaeVrZQRPOa/exec"; //Define URL prefix for upload data
String Sensor_ID = "1"; //Define ID of sensor

//Define variables for the APN address and SIM card pin number
String APN_ADDRESS = "iot.1nce.net"; //Enter APN of network You use
String SIM_CARD_PIN_NUMBER = ""; //Enter the PIN code for your SIM card if required

//Creating variables for operation data
String check_data = "";
String download_data = "";

bool connection_established = false; //Variable for checking modem connection

const int MODEM_PWRKEY = 27; //Define pin for modem PWR_KEY pin
const int MODEM_DTR = 26; //Define pin for modem DTR pin

//Function to configuration the modem
void configurationModem() 
{
  Serial2.println("ATV1"); //Set verbose mode on
  Serial2.println("AT+CMEE=2"); //Enable extended error reporting
  Serial2.println("AT+IPR=115200"); //Set baud rate to 115200
  Serial2.println("ATI"); //Get modem info
  Serial2.println("AT+QNITZ=1"); //Synchronize time from GSM network
  Serial2.println("AT+CTZU=3"); //Update network synchronized time to RTC
  if(SIM_CARD_PIN_NUMBER != "") 
  {   //If SIM card requires a PIN, enter it
    Serial2.println((String) "AT+CPIN=" + SIM_CARD_PIN_NUMBER);
  }
  Serial2.println("AT+GSN"); //Get IMEI number
  Serial2.println("AT+CIMI"); //Get IMSI number
  Serial2.println("AT+QCCID"); //Get ICCID number
  Serial2.println((String) "AT+QICSGP=1,\"" + APN_ADDRESS + "\""); //Set APN address
  delay(100); //100 ms pause
  download_data = Serial2.readString(); //Reading data from modem UART
  Serial.println(); //Print blank line in Serial Monitor
  Serial.println(download_data); //Print data from modem UART
  Serial.println(); //Print blank line in Serial Monitor
  Serial.println("MODEM HAS BEEN CONFIGURED"); //Printing information in Serial Monitor
  download_data = ""; //Clearing variable
}

//Function to wakeup modem from sleep mode
void modemWakeup()
{
  digitalWrite(MODEM_DTR, HIGH); //Turning on MAIN_DTR pin for modem wakeup
  delay(20); //20 ms pause
  digitalWrite(MODEM_DTR, LOW); //Turning off MAIN_DTR pin for modem wakeup
  delay(100); //100 ms pause
  Serial2.println("AT+QSCLK=0"); //Disable sleep mode
}

//Function to turn on a modem
void setupModem()
{
  //Read any available data from the modem's serial interface
  download_data = Serial2.readString(); //Reading data from modem UART

  //Check if the modem is already on or not
  Serial2.println("AT");  //Send command to check if modem is on
  download_data = Serial2.readString(); //Reading data from modem UART
  if(download_data.indexOf("OK") > -1)  //Waiting for "OK" response from modem
  {
    // Modem is already on
    Serial.println(); //Print blank line in Serial Monitor
    Serial.println(download_data); //Print data from modem UART
    Serial.println(); //Print blank line in Serial Monitor
    Serial.println("Set modem startup:"); //Printing information in Serial Monitor
    Serial.println("Modem is aleady on"); //Printing information in Serial Monitor
    Serial.println(); //Print blank line in Serial Monitor
    download_data = ""; //Clearing variable
  } 
  else 
  {
    //Modem was off, turn it on
    Serial.println(); //Print blank line in Serial Monitor
    Serial.println(download_data); //Print data from modem UART
    Serial.println("Set modem startup:"); //Printing information in Serial Monitor
    Serial.println("The modem has been switched on"); //Printing information in Serial Monitor
    Serial.println(); //Print blank line in Serial Monitor
    digitalWrite(MODEM_PWRKEY, HIGH); //Turning ON modem by set power on PWR_KEY pin through 1000 ms
    delay(1000); //1000 ms pause
    digitalWrite(MODEM_PWRKEY, LOW); //Turning OFF PWR_KEY pin
    Serial2.println("ATE1"); //Set echo text mode on modem
    download_data = ""; //Clearing variable
    download_data = Serial2.readString(); //Reading data from modem UART
    int initial_counter = 0; //Blank initial counter
    delay(1000); //1000 ms pause
    while(download_data.indexOf("Call Ready") == -1) //Waiting for "Call Ready" response from modem
    {
      Serial.println("Waiting for modem init"); //Printing information in Serial Monitor
      delay(500); //500 ms pause
      download_data = Serial2.readString(); //Reading data from modem UART
      Serial.println(download_data); //Print data from modem UART
      initial_counter++; //Increase the counter value by 1
      if(initial_counter > 10) //Trying to turn up modem again
      {
        digitalWrite(MODEM_PWRKEY, HIGH); //Turning ON modem by set power on PWR_KEY pin through 1000 ms
        delay(1000); //1000 ms pause
        digitalWrite(MODEM_PWRKEY, LOW); //Turning OFF PWR_KEY pin
        initial_counter = 0; //Blank initial counter
      }
    }
  }
}

//Function to check status of cellular connection
void networkCheck() 
{
  download_data = ""; //Clearing variable
  Serial.println("Network test"); //Printing information in Serial Monitor
  int connecting_count = 0; //Blank initial counter
  while (download_data == "") //While download_data variable is empty
  {
    Serial2.println("AT+CREG?");  //Send command to get network registration status
    download_data = Serial2.readString(); //Reading data from modem UART
    if(download_data.indexOf("+CREG: 0,1") != -1) //Check if device is registered on home network
    {
      Serial.println("Network registered - home"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      delay(50); //50 ms pause
      break; //Quit from while loop
    } 
    else if(download_data.indexOf("+CREG: 0,5") != -1) //Check if device is registered on roaming network
    {
      Serial.println("Network registered - roaming"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      delay(50); //50 ms pause
      break; //Quit from while loop
    } 
    else if(download_data.indexOf("+CME ERROR: SIM failure") != -1) //Check if there is a SIM failure error
    {
      Serial.println("SIM card error"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      download_data = Serial2.readString(); //Reading data from modem UART
      download_data = ""; //Clearing variable
      delay(50); //50 ms pause
    } 
    else if(download_data.indexOf("+CME ERROR: 13") != -1) //Check if there is a SIM failure error in CME numeric form
    {
      Serial.println("SIM card error"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      download_data = Serial2.readString(); //Reading data from modem UART
      download_data = ""; //Clearing variable
      delay(50); //50 ms pause
    }
    else
    {
      Serial.println("Connecting to network"); //Printing information in Serial Monitor
      Serial.println(download_data); //Print data from modem UART
      download_data = ""; //Clearing variable
      delay(1000); //1000 ms pause
      connecting_count++; //Increase the counter value by 1
    }
    if(connecting_count > 75)
    {
      setupModem(); //Check that the modem is working properly
      configurationModem(); //Make configuration again
      Serial.println("Some network connectivity issues... trying to connect again"); //Printing information in Serial Monitor
      delay(5000); //5000 ms pause
      connecting_count = 0; //Blank initial counter
    }
  }
}

//Function to get time from GSM network
String getModemTime()
{
  Serial2.println("AT+CCLK?"); //Get local time
  download_data = ""; //Clearing variable
  String date_and_time = ""; //Create variable for storage infomation about date and time
  while(download_data == "") //While download_data variable is empty
  {
    download_data = Serial2.readString(); //Reading data from modem UART
    if(download_data.indexOf("AT+CCLK?") > -1) //Waiting for "AT+CCLK?" response from modem
    {
      download_data.replace("AT+CCLK?"," "); //Replacing unnecessary data from modem response
      download_data.replace("+CCLK: "," "); //Replacing unnecessary data from modem response
      download_data.replace("OK"," "); //Replacing unnecessary data from modem response
      download_data.replace("\""," "); //Replacing unnecessary data from modem response
      download_data.trim(); //Removing whitespace characters
      date_and_time.concat(download_data.substring(0,2)); //Substract year
      date_and_time.concat("/"); //Put / symbol into variable
      date_and_time.concat(download_data.substring(3,5)); //Substract month
      date_and_time.concat("/"); //Put / symbol into variable
      date_and_time.concat(download_data.substring(6,8)); //Substract day
      date_and_time.concat("%20"); //Put URL space symbol into variable
      date_and_time.concat(download_data.substring(9,11)); //Substract hour
      date_and_time.concat(":"); //Put : symbol into variable
      date_and_time.concat(download_data.substring(12,14)); //Substract minutes
      date_and_time.concat(":"); //Put : symbol into variable
      date_and_time.concat(download_data.substring(15,17)); //Substract seconds
    }
  }
  return date_and_time; //Return date and time data
}

//Function to uplad data to Google Sheets endpoint
void sendUpdate(float temperature, String datetime)
{
  connection_established = false; //Set connection_estabilished variable as false
  while (!connection_established)  //While connection_estabilished variable is false
  {
    Serial2.println("AT+QIFGCNT=0"); //Set MUXIP function to GPRS
    delay(50); //50 ms pause
    Serial2.println((String) "AT+QICSGP=1,\"" + APN_ADDRESS + "\""); //Set APN for your cellular provider
    delay(50); //50 ms pause
    Serial2.println("AT+QIREGAPP"); //Start TCPIP task
    delay(1000); //1000 ms pause
    download_data = Serial2.readString(); //Reading data from modem UART
    if(download_data.indexOf("OK") != -1)  //Waiting for "OK" response from modem
    {
      Serial2.println("AT+QIACT"); //Bring up connection with GPRS
      delay(1000); //1000 ms pause
      download_data = Serial2.readString(); //Reading data from modem UART
      Serial.println(download_data); //Print data from modem UART
      if(download_data.indexOf("OK") != -1)  //Waiting for "OK" response from modem
      {
        Serial.println("Modem is connected to cellular network."); //Printing information in Serial Monitor
        connection_established = true; //Changing variable value to exit from while loop
      }
      else
      {
        Serial.println("Modem not connected to cellular network."); //Printing information in Serial Monitor
        Serial2.println("AT+QIDEACT"); //Deactivate current GPRS context
        delay(5000); //5000 ms pause
      }
    }
  }

  Serial.print("Temperature: "); //Printing information in Serial Monitor
  Serial.println(temperature); //Printing temperature in Serial Monitor
  Serial.print("Datetime: "); //Printing information in Serial Monitor
  Serial.println(datetime); //Printing time and date in Serial Monitor
  String current_URL = String(URL+"?temperature="+temperature+"&datetime="+datetime+"&id="+Sensor_ID); //Creating a valid URL including temperature, date, time and sensor ID
  Serial.print("Google Sheet URL: "); //Printing information in Serial Monitor
  Serial.println(current_URL); //Printing URL for connection in Serial Monitor
  int URL_length = current_URL.length(); //Measure URL length
  Serial2.println("AT+QHTTPCFG=\"requestheader\",0"); //Disable header requesting in HTTP connection
  delay(50); //50 ms pause
  Serial2.println("AT+QHTTPURL=" + String(URL_length) + ",80"); //Send information about URL address and timeout of this connection
  delay(1000); //1000 ms pause
  Serial2.println(current_URL); //Send URL to modem via UART
  delay(1000); //1000 ms pause
  Serial2.println("AT+QHTTPGET=80"); //Create HTTP GET request
  delay(1000); //1000 ms pause
  download_data = Serial2.readString(); //Reading data from modem UART
  download_data = ""; //Clearing variable
  while(download_data.indexOf("CONNECT") == -1) //Waiting for "CONNECT" response from modem
  {
    Serial2.println("AT+QHTTPREAD=80"); //Reading data from server
    download_data = Serial2.readString(); //Reading data from modem UART
    Serial.println(download_data); //Print data from modem UART
    delay(500); //500 ms pause
  }    
  download_data = Serial2.readString(); //Reading data from modem UART
  Serial.println(download_data); //Print data from modem UART
}

void setup() 
{
  Serial.begin(115200); //Start main UART interface
  Serial.setTimeout(100); //Set timeout for main UART interface
  Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); //Configure and start Serial2 UART interface
  pinMode(MODEM_PWRKEY, OUTPUT); //Set MODEM_PWRKEY as output
  Wire.begin(); //Run resources for I2C temperature sensor
  Serial2.setTimeout(100); //Set timeout for Serial2 UART interface
  pinMode(MODEM_DTR, OUTPUT); //Set MODEM_DTR as output
  modemWakeup(); //Wakeup modem from sleep mode
  setupModem(); //Run GSM modem
  if(!isModemConfiguration)
  {
    configurationModem(); //Configuration GSM modem
    isModemConfiguration = true; //The modem has been configured
  }
  networkCheck(); //Check status of cellular connection
  sendUpdate(temperature.readTemperatureC(), getModemTime()); //Send data to Google Sheet endpoint
  Serial2.println("AT+QSCLK=1"); //Turn on sleep mode on modem
  Serial.print("ESP32 Deep Sleep for "); //Printing information in Serial Monitor
  Serial.print(DEEP_SLEEP_TIME); //Printing Deep Sleep time in Serial Monitor
  Serial.println(" seconds"); //Printing information in Serial Monitor
  esp_sleep_enable_timer_wakeup(DEEP_SLEEP_TIME * CONVERT_US); //Set time to wake up
  esp_deep_sleep_start(); //Start Deep Sleep
}

void loop() 
{
}