// 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 "TMPLewjGkpy4"
#define BLYNK_DEVICE_NAME "Aquaphonics System"
#define BLYNK_AUTH_TOKEN "iCTbn2Bu3UxsCyqRdx2TPFFIOhKGrQQR"

//#define BLYNK_PRINT Serial

// Libraries
#include <WiFi.h> ;         //used to access the ESP 32 Wi-Fi functionality
#include <WiFiClient.h>     //“WiFiClient.h” library was put for WiFi shield
#include <BlynkSimpleEsp32.h> // to use Blynk app with our ESP32 board

#include <DS1307RTC.h>   // REal time clock
#include <TimeLib.h>     //“TimeLib.h” provides timekeeping functionality on the Arduino.
#include <Wire.h>        //“Wire.h” library allows user to communicate with I2C device.
#include <Servo.h>       //“Servo.h” library was used to access servo motor.

// Blynk Connection Setup.
char auth[] = BLYNK_AUTH_TOKEN;

// Your Wifi Credentials.
// Set password to "" for open networks.
char ssid[] = "YourNetworkName"; // Fill in your WIFI SSID
char pass[] = "YourPassword";    // Fill in SSID Password

// ===================== Feeding Schedule ======================
int HourSchedule1 = 8;    // Set the hour of feeding schedule (24 Hour System).
int HourSchedule2 = 19;   // Set the hour of 2nd feeding schedule comment line if not used.
int MinuteSchedule = 44;  // Set minute of feeding schedule (24 hour system).

// Variable and Pin Defined
// ======================== Ph Sensor ======================
const int PhPin = 34;
const int Acidic = 5;
const int Neutral = 2;
const int AnodeLed = 4;

int phval = 0;
float PhValue;
int buffer_arr[10], temp;
unsigned long int avgval;
const float MAX = 4095.00;
float calibration_value = 21.34;

// ======================== Water Level ====================
int WaterLevel, PumpOverride;
const int WaterPin = 39;
const int WaterPump = 19;

// ======================== Plant Watering =====================
int SprinklerOverride, moistureValue;
const int SoilPin = 36;
const int Sprinkler = 18;

// ======================== Food Dispenser ======================
int Hour, Minute, FoodLevel, FoodDispIndOverride, FoodDispStatus;
const int Open = 180;  //servo rotates 18 degree to relase food
const int Close = 0;   //servo closes the container
const int FoodPin = 35;
const int FoodDisp = 23;
const int FoodInd = 15;
const int tempfood = 32;
bool feedFlag;
Servo food;

const long foodOpenIntervalRTC = 60000;
const long foodOpenIntervalManual = 2000;
const long foodCloseInterval = 2000;
unsigned long previousTime = 0;

// =================== BLYNK SETUP ===================
//defining the virtual pin of widget LED
WidgetLED FoodLevelB(V4); 
WidgetLED AcidicB(V5);
WidgetLED NeutralB(V6);
WidgetLED PumpB(V7);
WidgetLED SprinklerB(V8);
WidgetLED FoodDispB(V9);

// Widget Color Defines
#define BLYNK_BLUE    "#04C0F8"
#define BLYNK_YELLOW  "#ED9D00"
#define BLYNK_RED     "#D3435C"

BlynkTimer timer; //

BLYNK_CONNECTED() //to run certain routine when hardware connects 
{                 //-to Blynk Cloud or private server. 
  Blynk.syncAll(); //restores all the Widget’s values based on 
}                  //-the last saved values on the server

// Blynk Food Dispenser Button
BLYNK_WRITE(V0) //Read the data from blynk to manually cotrol the foodDispenser
{
  int btnState = param.asInt();
  if (btnState == HIGH)
  {
    FoodDispIndOverride = 1;
    FoodDispB.on();  //close the cointainer after 2 secs
    timer.setTimeout(2000L, FoodDispOFF); 

  }
  else
    FoodDispIndOverride = 0;
    food.write(Close);

}

void FoodDispOFF()
{
  FoodDispIndOverride = 0;
  food.write(Close);
  FoodDispB.off();

}

// Blynk Water Pump Button
BLYNK_WRITE(V10)  
{   //Read the data from blynk to manually control the water pump
  int btnState1 = param.asInt();
  if (btnState1 == HIGH)
  {
    PumpB.on();
    PumpOverride = 1;

  }

  else
    PumpOverride = 0;
  digitalWrite(WaterPump, LOW);

}

// Blynk Sprinkler Button
BLYNK_WRITE(V11) 
{//Read the data from blynk to manually control the sprinkler
  int btnState2 = param.asInt();
  if (btnState2 == HIGH)
  {
    SprinklerOverride = 1;
    SprinklerB.on();

  }

  else
    SprinklerOverride = 0;
    SprinklerB.off();
    digitalWrite(Sprinkler, LOW);

}

// Sprinkler Function
void SprinklerMode() // ================ SPRINKLER @ SOIL MOISTURE =====================
{
  moistureValue = (100 - ((analogRead(SoilPin) / MAX) * 100)); //read and calculated value 
  if (SprinklerOverride == 1)                                  //-from sensor(slider in replace)
  {
    digitalWrite(Sprinkler, HIGH);
  }
  else if (SprinklerOverride == 0)
  {
    if (moistureValue < 40) //Sprinkler turns on if the moisture value below 40%
    {
      digitalWrite(Sprinkler, HIGH); 

    }

    else if (moistureValue > 40) //Sprinkler turns if the moisture value below 40%
    {
      digitalWrite(Sprinkler, LOW);
      
    }
  }

  else
  {
    digitalWrite(Sprinkler, LOW);

  }
}

// Food Dispenser Function
void FoodDispenserMode() // ================== FOOD DISPENSER =============================
{
  FoodLevel = digitalRead(FoodPin); //read the the input(FoodPin ), HIGH OR LOW
  Hour = hour();                    
  Minute = minute();              
  if (FoodLevel == LOW) //LED will turn on if FoodLevel is low(no food)
  {
    digitalWrite(FoodInd, HIGH);

  }

  else if (FoodLevel == HIGH)  //LED will not light up if FoodLevel is high
  {
    digitalWrite(FoodInd, LOW);  

  }

  if (FoodDispIndOverride == 1)  //Servo rotates(food released) when there is manual control
  {
    food.write(Open);

  }

  else if (FoodDispIndOverride == 0) 
  {        //Food released at 0844 and 1944 everyday
    if (Hour == HourSchedule1 || Hour == HourSchedule2)
    {
      if (Minute == MinuteSchedule)
      {
        if (feedFlag == false)
        {
          previousTime = millis();
          food.write(Open);
          feedFlag = true;
          
        }
      }
    }

    // if (digitalRead(tempfood) == HIGH)
    // {
    //   if (feedFlag == false)
    //   {
    //     previousTime = millis();
    //     food.write(Open);
    //     feedFlag = true;

    //   }
    // }

    if (feedFlag == true)
    {   //Serco rotates to 0 degree  and the food container closed
      if (millis() - previousTime >= foodOpenIntervalRTC)
      {
        food.write(Close);
        feedFlag = false;

      }
    }
  }
  else
  {
    food.write(Close);
    digitalWrite(FoodInd, LOW);

  }
}

// Water Level Function
void WaterLevelMode() // =============================== WATER PUMP @ WATER LEVEL =============================
{
  WaterLevel = (100 - ((analogRead(WaterPin) / MAX) * 100));  //Read and calculate the value read from sensor
  if (PumpOverride == 1) //Water pumped out if user control manually
  {     
    digitalWrite(WaterPump, HIGH);

  }

  else if (PumpOverride == 0)
  {
    if (WaterLevel < 40)  //Water pumped out if water level below 40% of sensor
    {
      digitalWrite(WaterPump, HIGH);

    }

    else if (WaterLevel > 60) //No Water pumped out if water level above 60% of sensor
    {
      digitalWrite(WaterPump, LOW);

    }
  }

  else
  {
    digitalWrite(WaterPump, LOW);

  }
}

// Water PH Level Function
void WaterQualityMode() // =============================== WATER QUALITY @ PH SENSOR =============================
{

  for (int i = 0; i < 10; i++)
  {
    buffer_arr[i] = analogRead(PhPin); //Read and store the value of pH in an array

  }
  //Sorting the array from small to big
  for (int i = 0; i < 9; i++)
  {
    for (int j = i + 1; j < 10; j++)
    {
      if (buffer_arr[i] > buffer_arr[j])
      {
        temp = buffer_arr[i];
        buffer_arr[i] = buffer_arr[j];
        buffer_arr[j] = temp;

      }
    }
  }

  avgval = 0;
  for (int i = 2; i < 8; i++)
  {  //Calculation to get the pH value 
    avgval += buffer_arr[i];
    float volt = (float)avgval * 5.0 / MAX / 6;
    float ph_act = -5.70 * volt + calibration_value;
    PhValue = ph_act;

  }

  if (PhValue < 6.5) 
  {
    digitalWrite(Acidic, LOW);
    digitalWrite(Neutral, HIGH); //rgb LED turns RED

  }

  else if (PhValue > 6.6)
  {
    digitalWrite(Acidic, HIGH);
    digitalWrite(Neutral, LOW); //RGB LED turns BLUE

  }

  else
  {
    digitalWrite(Acidic, HIGH);
    digitalWrite(Neutral, HIGH);  //RGB LED turns oFF 

  }
}

// Blynk Sensor Gauge Function
void Status()  //Write data to blynk
{
  Blynk.virtualWrite(V1, PhValue);
  Blynk.virtualWrite(V2, WaterLevel);
  Blynk.virtualWrite(V3, moistureValue);
}

// Blynk LED Widget Indicators Function
void ledStatus()
{
  if (SprinklerOverride == 1)
  {
    SprinklerB.on();

  }

  else if (SprinklerOverride == 0)
  {
    if (moistureValue > 60)
    {  //Gauge widget will become blue when moisture level is above 60%
      Blynk.setProperty(V3, "color", BLYNK_BLUE);
      SprinklerB.off();

    }

    else if ((moistureValue < 60) && (moistureValue > 40))
    { //Gauge widget will become yellowwhen moisture level is between 40% and  60%
      Blynk.setProperty(V3, "color", BLYNK_YELLOW);
      SprinklerB.off();

    }

    else if (moistureValue < 40)
    {  //Gauge widget will become red whne mositure levek is below 40%
      Blynk.setProperty(V3, "color", BLYNK_RED);
      SprinklerB.on();

    }
  }

  if (FoodLevel == LOW)
  {
    FoodLevelB.on();
    Blynk.logEvent("food_dispenser"); //notification will be sent on blynk to notify user

  }

  else if (FoodLevel == HIGH)
  {
    FoodLevelB.off();

  }

  if (FoodDispIndOverride == 1)
  {
    FoodDispB.on();

  }

  else if (FoodDispIndOverride == 0)
  {
    if (feedFlag == true)
    {
      FoodDispB.on();

    }

    else if (feedFlag == false)
    {
      FoodDispB.off();

    }
  }

  if (PumpOverride == 0)
  {
    if (WaterLevel < 30)
    {  //Gauge widget turns red when water level is below 30%
      Blynk.setProperty(V2, "color", BLYNK_RED);
      PumpB.on();

    }

    else if ((WaterLevel < 60) && (30 < WaterLevel))
    {  //Gauge widget turns yellow when water level is between 30% and 60%
      Blynk.setProperty(V2, "color", BLYNK_YELLOW);
      PumpB.off();

    }

    else if (WaterLevel > 60)
    { //Gauge widget turns blue when water level is aobve 60%
      Blynk.setProperty(V2, "color", BLYNK_BLUE);
      PumpB.off();

    }
  }

  if (PhValue < 6.5)
  { //Gauge widget turns red when pH level is below 6.5
    Blynk.setProperty(V1, "color", BLYNK_RED);
    NeutralB.off();
    AcidicB.on();

  }

  else if (PhValue > 6.6)
  {  //Gauge widget turns blue when pH level is above 6.6
    Blynk.setProperty(V1, "color", BLYNK_BLUE);
    AcidicB.off();
    NeutralB.on();

  }

  else
  {
    //Turn offa ll the widget LEDs
    PumpB.off();
    AcidicB.off();
    NeutralB.off();
    FoodDispB.off();
    FoodLevelB.off();
    SprinklerB.off();

  }
}

// Function for Serial Data Monitoring
void SerialData()
{
  Serial.print("Moisture Level: ");
  Serial.println(moistureValue);
  Serial.print("Water Level: ");
  Serial.println(WaterLevel);
  Serial.print("Ph Level: ");
  Serial.println(PhValue);
  Serial.print("Food Level: ");
  Serial.println(FoodLevel);
  Serial.print("Time: ");
  Serial.print(Hour);
  Serial.print(" : ");
  Serial.println(Minute);
  Serial.println("\n");
}

// ================================ SETUP AND LOOP ================================
void setup()
{
  Serial.begin(115200);
  Serial.print("Connecting to WiFi"); //Displaying internet connection status

  WiFi.begin("Wokwi-GUEST", "", 6); // Uncomment line if simulating using WOKWI
  //Blynk.connectWiFi(ssid, pass); // Uncomment line if using WIFI (SSID and Password needs to be defined)

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

  }
  Serial.println(" Connected!");

  Blynk.config(auth);
  while (Blynk.connect() == false)
  {
    Serial.println("Connection Failed");
    
  }
   //Determne if thepins are I/ODCQ2
  pinMode(PhPin,    INPUT);
  pinMode(SoilPin,  INPUT);
  pinMode(FoodPin,  INPUT);
  pinMode(tempfood, INPUT);
  pinMode(WaterPin, INPUT);

  pinMode(Acidic,   OUTPUT);
  pinMode(Neutral,  OUTPUT);
  pinMode(AnodeLed, OUTPUT);
  pinMode(FoodInd,  OUTPUT);
  pinMode(WaterPump,OUTPUT);
  pinMode(Sprinkler,OUTPUT);

  food.attach(FoodDisp);
  food.write(Close);  //no any degree of rotation initially
  digitalWrite(AnodeLed, HIGH); 
  setSyncProvider(RTC.get); //Get the real time

  timer.setInterval(1000L, Status);   // Run Blynk Status Function
  timer.setInterval(500L, ledStatus); // Run Blynk ledStatus Function
}

void loop()
{
  // SerialData(); //Uncomment if you want to monitor the outputs of sensors
  //calls the data to execute their function
  SprinklerMode();
  FoodDispenserMode();
  WaterLevelMode();
  WaterQualityMode();

  Blynk.run(); //Run the blynk
  timer.run(); //Run the timer
}
GND5VSDASCLSQWRTCDS1307+
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module