//=========================================Blynk setup =======================================================================
#define BLYNK_TEMPLATE_ID "TMPL6yEZFxTY8"
#define BLYNK_TEMPLATE_NAME "test1"
#define BLYNK_AUTH_TOKEN "2fxSsboW8-1-G2fbsjlmgG7dyU7U3mkk"
#define BLYNK_PRINT Serial
char ssid[] = "Wokwi-GUEST";
char pass[] = "";
char auth[] = BLYNK_AUTH_TOKEN;


//===========================================Libraries =========================================================================
#include <WiFi.h>
#include <BlynkSimpleEsp32.h>
#include <WiFiClient.h>
#include "DHT.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//==================================== Hardwre setup =======================================================================
#define pHSensor      35
#define TPHSensor     2 
#define DHTPIN        26 //Connect Out pin to D2 in NODE MCU
#define DHTTYPE       DHT22
#define pin_tds_pumpA 27
#define pin_tds_pumpB 14
#define pin_ph_plus 13
#define pin_ph_minus 12
#define pin_watering  15
#define TdsSensorPin  34
#define VREF          3.3              // analog reference voltage(Volt) of the ADC
#define samples       15            // Number of sample point
#define SCREEN_WIDTH  128 // OLED width,  in pixels
#define SCREEN_HEIGHT 64 // OLED height, in pixels
//================================== Others setup ==========================================================================
float temperature = 25;
unsigned long overFlow = 4294967295 ;
float adc_resolution = 4095.0; //ESP 32 ADC Resolution

//=================================Initializing variables =================================================================
//pump
bool watering_flag = false ;
///=============================== Sensor Variables ========================================
float averageVoltage = 0;

//tds
int analogBuffertds[samples];     // store the analog value in the array, read from ADC (tds)
int Indextds = 0;
float tdsValue = 0;
int value_overFlow_td=0 ;
int tds_lampDuration_overflow = 0;
long  tds_pumpDuration = 3000;  // 3 seconds in milliseconds
long tds_pumpOffTime = 1000;  // 1 minute in milliseconds
bool tds_pump_state = 0 ;
int pwm_tds_pumpA = 150;
int pwm_tds_pumpB = 150;
unsigned long tds_currentMillis = 0;
unsigned long tds_pumpStartTime=0 ;
unsigned long tds_overFlowCompensation = 0;
int waiting_time_tds= 0 ;
int watering_duration_tds = 0 ;
unsigned long tds_previousMillisReading = 0;
unsigned long tds_currentMillisReading = 0;
bool tds_currentReading =0;
  

//PH
float voltage = 0.0 ;
int analogBufferph[samples];     // store the analog value in the array, read from ADC (ph)
int Indexph = 0;
int value_overFlow_p=0 ;
int ph_lampDuration_overflow = 0;
long  ph_pumpDuration = 3000;  // 3 seconds in milliseconds
long ph_pumpOffTime = 1000;  // 1 minute in milliseconds
bool ph_pump_state = 0 ;
int pwm_tds_pumpMinus = 150;
int pwm_tds_pumpPlus = 150;
unsigned long ph_pumpStartTime=0 ;
unsigned long ph_overFlowCompensation = 0;
unsigned long ph_previousMillisReading = 0;
unsigned long ph_currentMillisReading = 0;

/// DHT
float h = 0.0;
float t = 0.0;
float temp_low =0 ;
float temp_high =0 ;


/// =========================== Pumps variables ==========================================

/// watering pump
unsigned long pumpDuration_overflow = 0;
unsigned long pump_time =0 ;
unsigned long pumpStartTime = 0;
unsigned long waitStartTime = 0;
unsigned long pumpDuration = 0;  
unsigned long waitDuration = 0; 
unsigned long overFlowCompensation = 0;
bool pump_state= 0 ;
int pump_waiting_time = 0 ;
int watering_duration = 0 ;
int counter = 0 ;


/// Calibration pumps 
bool ph_plus = 0, ph_minus = 0, tds_pumpON = 0, tds_pumpOFF = 0 ;

//calibration
bool start=0 ;
unsigned long calb_OffTime = 15000;
unsigned long calb_overFlow=0;
unsigned long calb_StartTime=0 ;
bool Suction=1;
bool Suction_pump_state =1 ;
unsigned long Suction_pumpDuration=10000;
unsigned long Suction_pumpOffTime=10000;
unsigned long Suction_overFlowCompensation=0;
unsigned long Suction_pumpStartTime=0;
unsigned long Suction_currentMillis=0;

// ==========================================Expert System Knowledge base =======================================

int plants_PH[5][3] = {
  {0, 5.5, 6},
  {1, 5.0, 5.5},
  {2, 6,  6.5},
  {3, 5.2, 6.4},
  {4, 5.5, 6}
};


int plants_tds[5][3] = {
  {0, 550, 1100},
  {1, 850, 1000},
  {2, 1000,  2000},
  {3, 1000, 2000},
  {4, 500, 800}
};

int value = 0 ; // plants  Slider

//====================================== initializing hardware =======================================================

// create an OLED display object connected to I2C
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
WiFiClient espClient;
BlynkTimer timer;

DHT dht(DHTPIN, DHTTYPE);

//======================================= Feunction definition  ===================================================== 

/// initialize on connection 
BLYNK_CONNECTED() {
  Blynk.syncVirtual(V0);
  Blynk.syncVirtual(V1);
  Blynk.syncVirtual(V2);
  Blynk.syncVirtual(V3);
  Blynk.syncVirtual(V4);  
  Blynk.syncVirtual(V5);

}

/// median filtering algorithm
int getMedianNum(int bArray[], int iFilterLen) {
  
  int bTab[iFilterLen];
  for (byte i = 0; i < iFilterLen; i++)
   { bTab[i] = bArray[i];
   }
  int i, j, bTemp;
  for (j = 0; j < iFilterLen - 1; j++) {
    for (i = 0; i < iFilterLen - j - 1; i++) {
      if (bTab[i] > bTab[i + 1]) {
        bTemp = bTab[i];
        bTab[i] = bTab[i + 1];
        bTab[i + 1] = bTemp;
      }
    }
  }
  if ((iFilterLen & 1) > 0) {
    bTemp = bTab[(iFilterLen - 1) / 2];
  }
  else {
    bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
  }
  return bTemp;
}

///  Initialize the wifi connection 
void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

//Declaration of virtual ports
BLYNK_WRITE(V0)
{
  tds_pumpOffTime = param.asInt() * 1000 ; // Get value as integer
  Serial.print("Waiting Time TDS: ");
  Serial.println(tds_pumpOffTime);

}

BLYNK_WRITE(V1)
{
  ph_pumpOffTime  = param.asInt() * 1000 ; // Get value as integer
  Serial.print("Watering TDS: ");
  Serial.println(watering_duration_tds);

}

BLYNK_WRITE(V2)
{
  pump_waiting_time = param.asInt(); // Get value as integer
  Serial.print("Waitting Time: ");
  Serial.println(pump_waiting_time);

}
BLYNK_WRITE(V3)
{
  pump_time = param.asInt(); // Get value as integer
  Serial.print("Pump Time: ");
  Serial.println(pump_time);

}


//get value from slider
BLYNK_WRITE(V4)
{
  value = param.asInt(); // Get value as integer
  Serial.println(value);

}

void TDS_sensor()
{

  Suction_currentMillis = millis();

  if (Suction_currentMillis < Suction_pumpStartTime) {  // Check for overflow condition
      // Overflow occurred, compensate and  reset the start time 
      Suction_overFlowCompensation = overFlow - Suction_pumpStartTime;
      Suction_pumpStartTime = Suction_currentMillis;

  }
if (Suction_pump_state)
{
  if ((Suction_currentMillis - Suction_pumpStartTime + Suction_overFlowCompensation) >= Suction_pumpOffTime)
  {  Suction=0;
    Suction_pump_state = false;
    Serial.println("------------------------------------------------------------");
  }
}

if (Suction)
    Serial.println("hhhhhhhhhhhhheeeeeeeeeeeeeelllllllllllllooooooooooooooooooo");
  else
      Serial.println("rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr");


  if (!Suction)
  {
  tds_currentMillisReading = millis();
  if (tds_currentMillisReading - tds_previousMillisReading >= 1000) {
        Serial.println(analogRead(TdsSensorPin));
    tds_previousMillisReading = tds_currentMillisReading;
    analogBuffertds[Indextds] = analogRead(TdsSensorPin);    //read the analog value and store into the buffer
    Indextds++;
    if (Indextds == samples) {
      Indextds = 0;
    }
  }

  // read the analog value more stable by the median filtering algorithm, and convert to voltage value
  averageVoltage = getMedianNum(analogBuffertds, samples) * (float)VREF / adc_resolution;

  //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
  float compensationCoefficient = 1.0 + 0.02 * (temperature - 25.0);
  //temperature compensation
  float compensationVoltage = averageVoltage / compensationCoefficient;

  //convert voltage value to tds value
  tdsValue = (133.42 * compensationVoltage * compensationVoltage * compensationVoltage - 255.86 * compensationVoltage * compensationVoltage + 857.39 * compensationVoltage) * 0.5;
  Serial.print("TDS Value:");
  Serial.print(tdsValue, 0);
  Serial.println("ppm");
  //Blynk.virtualWrite(V3, tdsValue);

  tds_currentMillis = millis();

  if (tds_currentMillis < tds_pumpStartTime) {  // Check for overflow condition
      // Overflow occurred, compensate and  reset the start time 
      tds_overFlowCompensation = overFlow - tds_pumpStartTime;
      tds_pumpStartTime = tds_currentMillis;
      Serial.print("Overflow compensated:");
      Serial.println(tds_overFlowCompensation);
  }



  if (!tds_pump_state)
   {  // If pump is currently off
    if ((tds_currentMillis - tds_pumpStartTime + tds_overFlowCompensation) >= tds_pumpOffTime) 
    {  // Check if pump off time has elapsed
      if (tdsValue < (plants_tds[value][1]))
        {
          Serial.println("tds");
          tds_pumpON=1;// Turn on the pump
          tds_pumpOFF=0;
          
        }
        else
        {
          tds_pumpON=0;// Turn off the pump
          tds_pumpOFF=1; 
        }
        tds_pump_state = true;// Update pump state
        tds_pumpStartTime = tds_currentMillis;  // Reset start time for pump operation
        Serial.println("On");
        tds_overFlowCompensation=0;
    }
  }
   else {  // If pump is currently on
    if ((tds_currentMillis - tds_pumpStartTime +tds_overFlowCompensation)>= tds_pumpDuration) 
    {  // Check if pump on time has elapsed
        tds_pumpOFF=1 ; // Turn off the pump
        tds_pumpON = 0;
        tds_pump_state = false;  // Update pump state
        tds_pumpStartTime = tds_currentMillis;  // Reset start time for pump off period
        Serial.println("Off");
        tds_overFlowCompensation=0;
         
    }
        }
}
}

void DHT_sensor()
{
   h = dht.readHumidity();
   t = dht.readTemperature(); // or dht.readTemperature(true) for Fahrenheit

  if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }
   
   if(t < temp_low || t> temp_high)
   {
       Blynk.logEvent("test1");
   }
    //Blynk.virtualWrite(V0, t);
    //Blynk.virtualWrite(V1, h);
    Serial.print("Temperature : ");
    Serial.print(t);
    Serial.print("    Humidity : ");
    Serial.println(h);


}


float ph (float voltage) {
  return 7 + ((2.50 - voltage) / 0.18);
}


void PH_sensor() {

  ph_currentMillisReading = millis();
  if (ph_currentMillisReading - ph_previousMillisReading >= 1000) {
        ph_previousMillisReading = ph_currentMillisReading;
    analogBufferph[Indexph] = analogRead(pHSensor);    //read the analog value and store into the buffer
    Indexph++;
    if (Indexph == samples) {
      Indexph = 0;
    }
  }
    delay(10);
  voltage = 3.3 * getMedianNum(analogBufferph, samples) / 4095.0; //adc
  Serial.print("pH= ");
  Serial.println(ph(voltage));
  //Blynk.virtualWrite(V2, ph(voltage));
  unsigned long ph_currentMillis = millis();
  Serial.print("m=");
  Serial.println(ph_currentMillis);

  Serial.print("pump_state=");
  Serial.println(ph_pump_state);
  // Check pH conditions

if (ph_currentMillis < ph_pumpStartTime) {  // Check for overflow condition
    // Overflow occurred, compensate and  reset the start time 
    ph_overFlowCompensation = overFlow - ph_pumpStartTime;
    ph_pumpStartTime = ph_currentMillis;
    Serial.print("Overflow compensated:");
    Serial.println(ph_overFlowCompensation);
  }


    if (!ph_pump_state) {  // If pump is currently off
    if ((ph_currentMillis - ph_pumpStartTime + ph_overFlowCompensation) >= ph_pumpOffTime) {  // Check if pump off time has elapsed
    if (ph(voltage) < (plants_PH[value][1])) {
          ph_minus = 0 ;
          ph_plus = 1 ;
        }
    else if (ph(voltage) > (plants_PH[value][2])) {
          ph_minus = 1 ;
          ph_plus = 0 ;
    
       }
    else
    {
          ph_minus = 0 ;
          ph_plus = 0 ;
 
    }

      ph_pump_state = true;// Update pump state
      ph_pumpStartTime = ph_currentMillis;  // Reset start time for pump operation
      ph_overFlowCompensation=0;
    }
  } else {  // If pump is currently on
    if ((ph_currentMillis - ph_pumpStartTime +ph_overFlowCompensation)>= ph_pumpDuration) {  // Check if pump on time has elapsed
        ph_minus = 0 ;
        ph_plus = 0 ;
        ph_pump_state = false;  // Update pump state
        ph_pumpStartTime = ph_currentMillis;  // Reset start time for pump off period
        ph_overFlowCompensation=0;

    }
  }
}

void Actuation()
{
  if (ph_plus)
  {
    //digitalWrite(pin_ph_plus, HIGH);   // move the actuation to a seperat part at the end of the code and use marker to store the decesions such as in PLC
    //digitalWrite(pin_ph_minus, LOW);
    analogWrite(pin_ph_plus,255);
    analogWrite(pin_ph_minus,0);
    // ledcWrite(1, pwm_tds_pumpPlus);
    // ledcWrite(2, 0);
  }
  if (ph_minus)
  {
    //digitalWrite(pin_ph_minus, HIGH);
    //digitalWrite(pin_ph_plus, LOW);
    analogWrite(pin_ph_minus,255);
    analogWrite(pin_ph_plus,0);
    //  ledcWrite(2, pwm_tds_pumpMinus);
    //  ledcWrite(1, 0);
  }

  if (ph_plus == 0)
  {
    //digitalWrite(pin_ph_plus, LOW);
    analogWrite(pin_ph_plus,0);
    //ledcWrite(1, 0);
  }

  if (ph_minus == 0)
  {
    //digitalWrite(pin_ph_minus, LOW);
    analogWrite(pin_ph_minus,0);
  
    //ledcWrite(2,0);
  }
  if (tds_pumpON)
  {
    // ledcWrite(0, pwm_tds_pumpA);
    // ledcWrite(0, pwm_tds_pumpB);
    analogWrite(pin_tds_pumpA,255);
    analogWrite(pin_tds_pumpB,255);
  }

  if (tds_pumpOFF)
  {

    // ledcWrite(0,0);
    // ledcWrite(0,0);
    analogWrite(pin_tds_pumpA,0);
    analogWrite(pin_tds_pumpB,0);

  }

}

void watering()
{
  counter = 6  ; 
  pump_waiting_time = 24/counter ;
  waitDuration = pump_waiting_time*1000 ; //*60*60;      // modified to speedup the code should be *1000 instead of *10
  pumpDuration = pump_time*1000;//*60 ;            // modified to speedup the code should be *1000 instead of *10
  unsigned long currentMillis = millis();

  if (currentMillis < pumpStartTime) {  // Check for overflow condition
      // Overflow occurred, compensate and  reset the start time 
      overFlowCompensation = overFlow - pumpStartTime;
      tds_pumpStartTime = tds_currentMillis;
      Serial.print("Overflow compensated:");
      Serial.println(overFlowCompensation);
  }


    if (!pump_state) {  // If pump is currently off

    if ((currentMillis - pumpStartTime + overFlowCompensation) >= waitDuration ) {  // Check if pump off time has elapsed 
      digitalWrite(pin_watering,HIGH);
      pump_state = true;// Update pump state
      pumpStartTime = currentMillis;  // Reset start time for pump operation
      Serial.println("On");
      overFlowCompensation=0;
    }
    }
    else {  // If pump is currently on
    if ((currentMillis - pumpStartTime +overFlowCompensation)>= pumpDuration) {  // Check if pump on time has elapsed
        digitalWrite(pin_watering,LOW);
        pump_state = false;  // Update pump state
        pumpStartTime = currentMillis;  // Reset start time for pump off period
        Serial.println("Off");
        overFlowCompensation=0;

    }
  }

}

void display()
{

  oled.clearDisplay();
  oled.setTextSize(1);         // set text size
  oled.setTextColor(WHITE);    // set text color
  oled.setCursor(0, 10);       // set position to display
  oled.println("tds = "); 
  oled.setCursor(40, 10); 
  oled.println(tdsValue); // set tds value
  oled.setCursor(0, 25);       // set position to display
  oled.println("temp = "); 
  oled.setCursor(40, 25);
  oled.println(t); // set temp value
  oled.setCursor(0, 40);       // set position to display
  oled.println("hum = "); // set text
  oled.setCursor(40, 40);
  oled.println(h); // set hum value
  oled.setCursor(0, 50);       // set position to display
  oled.println("ph = "); // set text
  oled.setCursor(40, 50);
  oled.println(ph(voltage)); // set ph value
  oled.display(); 


 

}

void calb()
{
  Serial.println("start==========");
  Serial.print(start);
  unsigned long calb_currentMillis = millis();
      if (calb_currentMillis < calb_StartTime) {  // Check for overflow condition
      // Overflow occurred, compensate and  reset the start time 
      calb_overFlow = overFlow - calb_StartTime;
      calb_StartTime = calb_currentMillis;
      Serial.print("Overflow compensated:");
  }
     if ((calb_currentMillis - calb_StartTime + calb_overFlow) >= calb_OffTime) 
    {  start =1 ;
        Suction_pumpStartTime = millis();
        timer.setInterval(0L, PH_sensor);
        timer.setInterval(0L, TDS_sensor);
       Serial.println("l am hereeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee ");
      calb_StartTime = calb_currentMillis;  // Reset start time for pump operation
      calb_overFlow=0;
    }
}


void setup()
{
  pinMode(pin_ph_plus, OUTPUT);
  pinMode(pin_ph_minus, OUTPUT);
  pinMode(pin_tds_pumpA, OUTPUT);
  pinMode(pin_tds_pumpB, OUTPUT);
  pinMode(pin_watering, OUTPUT);
  setup_wifi();
  Serial.begin(115200);
  Serial.println("helllo") ;
  WiFi.begin(ssid, pass);
  Blynk.config(auth);
  Blynk.begin(auth, ssid, pass, "blynk.cloud", 80);
  dht.begin();

  timer.setInterval(0L, DHT_sensor);
  timer.setInterval(0L, Actuation);
  timer.setInterval(0L, watering);
  timer.setInterval(0L,display);
  timer.setInterval(0L,calb);
    if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("failed to start SSD1306 OLED"));
    while (1);
  }

}

void loop()
{
  Blynk.run();
  timer.run();


}