#include <Arduino.h>
#include <digitalWriteFast.h>
#include <LiquidCrystal_I2C.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <ArduinoOTA.h>
#include <TimeLib.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

// WiFi Credentials:
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "test.mosquitto.org";

// MQTT topics
const char* TANK_PERCENTAGE = "device/mano/data/home/watermonitoringsystem/tank/percentage";
const char* TANK_VOLUME = "device/mano/data/home/watermonitoringsystem/tank/volume";
const char* PUMP = "device/mano/data/home/watermonitoringsystem/tank/pump";

WiFiClient espClient;
PubSubClient client(espClient);

const int triggpin = 18;
const int echopin = 19;
const int pump = 13;
const float SPEED_OF_SOUND = 0.034 / 2;

const float tanksensorheight = 120;
const float tankheight = 95;
const float tankradius = 45;
const float tanktriggerlevel = 109.5;
const float tanktriggeroff = 24;

const double pi = 3.1415926535897932384626433832795;

float lastDistance = -1;
long duration;
float distance;
bool pumpState = false; // Track the pump state

void setup() {
  Serial.begin(115200);
  lcd.init();
  lcd.backlight();
  intro();

  pinMode(triggpin, OUTPUT);
  pinMode(echopin, INPUT);
  pinMode(pump, OUTPUT);

  lcd.clear();
  setupWiFi();

  client.setServer(mqtt_server, 1883);
  client.setKeepAlive(60);

  ArduinoOTA.setHostname("ESP32-WaterMonitoring");
  ArduinoOTA.begin();

  // Set the time manually for testing (adjust according to your local time)
  // This would normally be set by an NTP server
  setTime(7, 0, 0, 14, 7, 2024); // Set the current time to 7:00:00 AM on July 14, 2024
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  ArduinoOTA.handle();

  ULSensor();
  int waterheight = calculateWaterHeight();

  float tankFilledPercentage = (waterheight / (float)tankheight) * 100;
  float tankvolume = (pi * (tankradius * tankradius) * waterheight) * 0.001;

  publishData(tankFilledPercentage, tankvolume);

  if (distance != lastDistance) {
    printToSerial(waterheight, tankFilledPercentage, tankvolume);
    updateLCD(tankFilledPercentage, tankvolume);
    controlPump();
    lastDistance = distance;
  }
  delay(250);
}

void intro() {
  lcd.clear();
  lcd.println("WATER MONITORING");
  lcd.setCursor(3, 1);
  lcd.println("SYSTEM:1.0");
  delay(1500);
  lcd.clear();
  lcd.setCursor(7, 0);
  lcd.println("ALL");
  lcd.setCursor(1, 1);
  lcd.println("RIGHTSRESERVED");
  delay(750);
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.println("MANOMOY MAITY");
  delay(750);
}

void ULSensor() {
  digitalWriteFast(triggpin, LOW);
  delayMicroseconds(2);
  digitalWriteFast(triggpin, HIGH);
  delayMicroseconds(10);
  digitalWriteFast(triggpin, LOW);
  duration = pulseIn(echopin, HIGH);
  distance = duration * SPEED_OF_SOUND + 1;
}

void setupWiFi() {
  delay(1000);
  Serial.print("Connecting to ");
  lcd.setCursor(0, 0);
  lcd.print("Connecting to ");
  Serial.println(ssid);
  lcd.setCursor(0, 1);
  lcd.print(ssid);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    spinner();
    Serial.print(".");
    delay(500);
  }
  Serial.println("\nConnected to WiFi");
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");

    if (client.connect("espClientWaterSystem")) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void spinner() {
  static int8_t counter = 0;
  const char* glyphs = "\xa1\xa5\xdb";
  lcd.setCursor(15, 0);
  lcd.print(glyphs[counter++]);
  if (counter == strlen(glyphs)) {
    counter = 0;
  }
}

int calculateWaterHeight() {
  int waterheight;
  if (distance <= 2) {
    waterheight = tankheight;
  } else if (distance >= tanksensorheight) {
    waterheight = 0;
  } else {
    waterheight = tanksensorheight - distance;
    if (waterheight > tankheight) {
      waterheight = tankheight;
    } else if (waterheight < 0) {
      waterheight = 0;
    }
  }
  return waterheight;
}

void publishData(float tankFilledPercentage, float tankvolume) {
  char tankFilledPercentageStr[10];
  char tankVolumeStr[10];

  dtostrf(tankFilledPercentage, 4, 2, tankFilledPercentageStr);
  dtostrf(tankvolume, 6, 2, tankVolumeStr);

  client.publish(TANK_PERCENTAGE, tankFilledPercentageStr);
  client.publish(TANK_VOLUME, tankVolumeStr);
}

void printToSerial(int waterheight, float tankFilledPercentage, float tankvolume) {
  Serial.println("------------------------------------");
  Serial.print("Tank - Distance: ");
  Serial.print(distance);
  Serial.println(" cm");
  Serial.print("Tank - Water Height: ");
  Serial.print(waterheight);
  Serial.println(" cm");
  Serial.print("Tank - Tank Filled Percentage: ");
  Serial.print(tankFilledPercentage);
  Serial.println("%");
  Serial.print("Tank Volume :");
  Serial.print(tankvolume);
  Serial.println("L");
  Serial.println("------------------------------------");
}

void updateLCD(float tankFilledPercentage, float tankvolume) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("TankLvL: ");
  lcd.print(tankFilledPercentage);
  lcd.print("%");
  lcd.setCursor(3, 1);
  lcd.print("Vol: ");
  lcd.print(tankvolume);
  lcd.print("L");
}

bool isPumpOnTime() {
  int currentHour = hour();
  int currentMinute = minute();

  // Check if the current time is within the specified time ranges
  return ((currentHour == 7 && currentMinute >= 0 && currentMinute <= 59) ||
          (currentHour == 8 && currentMinute == 0) ||
          (currentHour == 12 && currentMinute >= 0 && currentMinute <= 29) ||
          (currentHour == 13 && currentMinute == 0) ||
          (currentHour == 17 && currentMinute >= 0 && currentMinute <= 59) ||
          (currentHour == 18 && currentMinute == 0));
}

void controlPump() {
  if (isPumpOnTime()) {
    // Check if the water level is above the trigger level to turn the pump on
    if (distance >= tanktriggerlevel && !pumpState) {
      digitalWriteFast(pump, HIGH);
      client.publish(PUMP, "on");
      pumpState = true;
    } 
    // Check if the water level is below the trigger level to turn the pump off
    else if (distance <= tanktriggeroff && pumpState) {
      digitalWriteFast(pump, LOW);
      client.publish(PUMP, "off");
      pumpState = false;
    }
  } else {
    // Turn off the pump if it is outside the allowed time range
    if (pumpState) {
      digitalWriteFast(pump, LOW);
      client.publish(PUMP, "off");
      pumpState = false;
    }
  }
}
NOCOMNCVCCGNDINLED1PWRRelay Module