#include <WiFi.h>
#include <ThingSpeak.h>
#include <PZEM004Tv30.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// ================= WIFI CONFIGURATION =================
// Adjust according to your own WiFi network
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// ================= THINGSPEAK CONFIGURATION =================
// Adjust according to your own ThingSpeak channel
unsigned long myChannelNumber = YOUR_CHANNEL_ID;
const char *myWriteAPIKey = "YOUR_WRITE_API_KEY";

// ================= PZEM CONFIGURATION =================
// PZEM RX -> ESP32 GPIO16
// PZEM TX -> ESP32 GPIO17
PZEM004Tv30 pzem(Serial2, 16, 17);

// ================= LCD CONFIGURATION =================
#define USE_LCD true
LiquidCrystal_I2C lcd(0x27, 16, 2);

WiFiClient client;

// ================= SENSOR DATA =================
float voltage = 0;
float current = 0;
float power = 0;
float energy = 0;
float frequency = 0;
float pf = 0;

// ================= TIMERS =================
unsigned long lastSensorRead = 0;
unsigned long lastLCDUpdate = 0;
unsigned long lastThingSpeak = 0;
unsigned long lastWifiCheck = 0;

const unsigned long SENSOR_INTERVAL = 1000;    // 1 second
const unsigned long LCD_INTERVAL = 2000;       // 2 seconds
const unsigned long TS_INTERVAL = 20000;       // 20 seconds
const unsigned long WIFI_INTERVAL = 10000;     // 10 seconds

int lcdPage = 0;

// ==================================================
void connectWiFi() {
  if (WiFi.status() == WL_CONNECTED) return;

  Serial.println("Connecting to WiFi...");
  WiFi.disconnect();
  WiFi.begin(ssid, password);
}

// ==================================================
void readSensor() {
  voltage = pzem.voltage();
  current = pzem.current();
  power = pzem.power();
  energy = pzem.energy();
  frequency = pzem.frequency();
  pf = pzem.pf();

  if (isnan(voltage)) {
    Serial.println("Failed to read data from PZEM");
    return;
  }

  Serial.println("========================");
  Serial.print("Voltage   : ");
  Serial.print(voltage);
  Serial.println(" V");

  Serial.print("Current   : ");
  Serial.print(current);
  Serial.println(" A");

  Serial.print("Power     : ");
  Serial.print(power);
  Serial.println(" W");

  Serial.print("Energy    : ");
  Serial.print(energy, 3);
  Serial.println(" kWh");

  Serial.print("Frequency : ");
  Serial.print(frequency);
  Serial.println(" Hz");

  Serial.print("Power Factor : ");
  Serial.println(pf);
}

// ==================================================
void updateLCD() {
#if USE_LCD
  lcd.clear();

  switch (lcdPage) {
    case 0:
      lcd.setCursor(0, 0);
      lcd.print("V:");
      lcd.print(voltage, 1);
      lcd.print("V");

      lcd.setCursor(0, 1);
      lcd.print("I:");
      lcd.print(current, 2);
      lcd.print("A");
      break;

    case 1:
      lcd.setCursor(0, 0);
      lcd.print("P:");
      lcd.print(power, 1);
      lcd.print("W");

      lcd.setCursor(0, 1);
      lcd.print("E:");
      lcd.print(energy, 3);
      lcd.print("kWh");
      break;

    case 2:
      lcd.setCursor(0, 0);
      lcd.print("Freq:");
      lcd.print(frequency, 1);

      lcd.setCursor(0, 1);
      lcd.print("PF:");
      lcd.print(pf, 2);
      break;
  }

  lcdPage++;
  if (lcdPage > 2) {
    lcdPage = 0;
  }
#endif
}

// ==================================================
void sendThingSpeak() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi is not connected");
    return;
  }

  if (isnan(voltage)) {
    Serial.println("Invalid sensor data");
    return;
  }

  ThingSpeak.setField(1, voltage);
  ThingSpeak.setField(2, current);
  ThingSpeak.setField(3, power);
  ThingSpeak.setField(4, energy);
  ThingSpeak.setField(5, frequency);
  ThingSpeak.setField(6, pf);

  int statusCode = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);

  if (statusCode == 200) {
    Serial.println("Data uploaded successfully");
  } else {
    Serial.print("Upload failed. HTTP Error: ");
    Serial.println(statusCode);
  }
}

// ==================================================
void setup() {
  Serial.begin(115200);

#if USE_LCD
    Wire.begin(32, 33);

  lcd.begin();
  lcd.backlight();

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Energy");
  lcd.setCursor(0, 1);
  lcd.print("Monitoring");
#endif

  connectWiFi();
  ThingSpeak.begin(client);
}

// ==================================================
void loop() {
  unsigned long now = millis();

  // ===== CHECK WIFI CONNECTION =====
  if (now - lastWifiCheck >= WIFI_INTERVAL) {
    lastWifiCheck = now;

    if (WiFi.status() != WL_CONNECTED) {
      Serial.println("WiFi disconnected, reconnecting...");
      connectWiFi();
    } else {
      Serial.print("IP Address: ");
      Serial.println(WiFi.localIP());
    }
  }

  // ===== READ SENSOR DATA =====
  if (now - lastSensorRead >= SENSOR_INTERVAL) {
    lastSensorRead = now;
    readSensor();
  }

  // ===== UPDATE LCD DISPLAY =====
  if (now - lastLCDUpdate >= LCD_INTERVAL) {
    lastLCDUpdate = now;
    updateLCD();
  }

  // ===== SEND DATA TO THINGSPEAK =====
  if (now - lastThingSpeak >= TS_INTERVAL) {
    lastThingSpeak = now;
    sendThingSpeak();
  }
}