/* Comment this out to disable prints and save space */
#define BLYNK_PRINT Serial

/* Fill in information from Blynk Device Info here */
#define BLYNK_TEMPLATE_ID "TMPL6IUcBQsZM"
#define BLYNK_TEMPLATE_NAME "Power monitor"
#define BLYNK_AUTH_TOKEN "WD-xAKuudBF6rwLhK5T23JMBvoyGtMi-"

#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <PZEM004Tv30.h>

/********************************
****** WiFi Credentials *********
********************************/
// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "Wokwi-GUEST";
char pass[] = "";

#define DEBUG 1  // Set Debug level 0-1 to prints result on serial monitor (Set it to 0 when you don't need serial prints)

#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif


int lastConnectionAttempt = millis();
int connectionDelay = 5000; // try to reconnect every 5 seconds


BlynkTimer timer;

// Defining Virtual pins for each reading
#define voltage_virtual_pin V0    // Virtual pin for voltage
#define current_virtual_pin V1    // Virtual pin for Current
#define power_virtual_pin V2      // Virtual pin for Power
#define energy_virtual_pin V3     // Virtual pin for Energy
#define frequency_virtual_pin V4  // Virtual pin for Frequency
#define pf_virtual_pin V5         // Virtual pin for Power factor
#define wire1LED_virtual_pin V6   // Virtual pin for LED to indicate Wire 1 status
#define wire2LED_virtual_pin V7   // Virtual pin for LED to indicate Wire 2 status
#define electo_virtual_pin V8     // Electromagnet virtual pin


bool wire1InContact = true;    // Flag to change if wire 1 was cut
bool wire2InContact = true;    // Flag to change if wire 2 was cut
bool electroUnlocked = false;  // Flag to be set when electro magnet was unlocked

unsigned long unlockTime = 0;          // Variable to hold unlock time when electro magnet was unlocked
unsigned long totalUnlockTime = 1000;  // Total Signal duration for electro magent unlock in milliseconds


WidgetLED ledWire1(wire1LED_virtual_pin);  // Creating an LED widget for wire 1
WidgetLED ledWire2(wire2LED_virtual_pin);  // Creating an LED widget for wire 1

bool relayLogic = false;  // Relay working logic you can set it to true/false


// Defining Wire Pins and relay pins for siren, light and electromagnet
const byte sirenRelay = 15;   // Siren Relay pin on esp32
const byte lightRelay = 2;    // Light Relay pin on esp32
const byte electroRelay = 4;  // Electromagnet relay pin
const byte wire1Pin = 18;     // Wire 1 pin needs to be in loop with Ground
const byte wire2Pin = 19;     // Wire 2 pin needs to be in loop with Ground


PZEM004Tv30 pzem(Serial2, 16, 17);

// This function will be called when you press the open electromagnet button
BLYNK_WRITE(electo_virtual_pin) {
  int pinValue = param.asInt();            // assigning incoming value from pin V1 to a variable
  digitalWrite(electroRelay, relayLogic);  // Unlock Electromagnet
  unlockTime = millis();                   // Note current time when electro magnet was unlocked
  electroUnlocked = true;                  // Set the electromagnet unlock flag
}
void resetElectroMagnet() {
  if ((millis() - unlockTime) > totalUnlockTime && electroUnlocked) {
    electroUnlocked = false;                  // Reset the flag
    digitalWrite(electroRelay, !relayLogic);  // Reset Electromagnet relay signal
    Blynk.virtualWrite(electo_virtual_pin, 0); // Reset Button state
  }
}
void checkWireCut() {
  if (digitalRead(wire1Pin) == HIGH && wire1InContact) {  // Check if wire 1 signal get's high
    debugln("Wire 1 Cut");
    wire1InContact = false;                               // Reset wire 1 in contact flag to indicate wire cut
    ledWire1.off();                                        // Turn on Wire 1 led ON
    digitalWrite(sirenRelay, relayLogic);                 // Turn on siren
    digitalWrite(lightRelay, relayLogic);                 // Turn on Light
  }
  if (digitalRead(wire2Pin) == HIGH && wire2InContact) {  // Check if wire 2 signal get's high
    debugln("Wire 2 Cut");
    wire2InContact = false;                               // Reset wire 2 in contact flag to indicate wire cut
    ledWire2.off();                                        // Turn on Wire 2 led ON
    digitalWrite(sirenRelay, relayLogic);                 // Turn on siren
    digitalWrite(lightRelay, relayLogic);                 // Turn on Light
  }
}

// This function sends Arduino's up time every second to Virtual Pin (5)
void readSendPower() {  // Read and send power values
  // Read the data from the sensor
  float voltage = pzem.voltage();
  float current = pzem.current();
  float power = pzem.power();
  float energy = pzem.energy();
  float frequency = pzem.frequency();
  float pf = pzem.pf();

  // Check if the data is valid
  if (isnan(voltage)) {
    voltage = 0;  // Set voltage to 0
  }
  if (isnan(current)) {
    current = 0;  // Set current to 0
  }
  if (isnan(power)) {
    power = 0;  // Set power to 0
  }
  if (isnan(energy)) {
    energy = 0;  // Set energy to 0
  }
  if (isnan(frequency)) {
    frequency = 0;  // Set frequency to 0
  }
  if (isnan(pf)) {
    pf = 0;  // Set power factor to 0
  }
  // Print the values to the Serial console if debug is enabled
  debug("Voltage: ");
  debug(voltage);
  debugln("V");
  debug("Current: ");
  debug(current);
  debugln("A");
  debug("Power: ");
  debug(power);
  debugln("W");
  debug("Energy: ");
  debug(energy);
  debugln("kWh");
  debug("Frequency: ");
  debug(frequency);
  debugln("Hz");
  debug("PF: ");
  debugln(pf);
  debugln();

  // You can send any value at any time.
  // Please don't send more that 10 values per second.
  Blynk.virtualWrite(voltage_virtual_pin, voltage);
  Blynk.virtualWrite(current_virtual_pin, current);
  Blynk.virtualWrite(power_virtual_pin, power);
  Blynk.virtualWrite(energy_virtual_pin, energy);
  Blynk.virtualWrite(frequency_virtual_pin, frequency);
  Blynk.virtualWrite(pf_virtual_pin, pf);
}
BLYNK_CONNECTED() {
  // Synchronize time on connection
  ledWire1.on();                                        // Turn on Wire 2 led ON
  ledWire2.on();                                        // Turn on Wire 2 led ON
  Blynk.virtualWrite(electo_virtual_pin, 0); // Reset Button state
}
void setup() {
  /* Debugging serial */
  Serial.begin(115200);

  Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);

  // Initially set relay pins as output and turn them off
  pinMode(sirenRelay, OUTPUT);              // Set pin as output
  pinMode(lightRelay, OUTPUT);              // Set pin as output
  pinMode(electroRelay, OUTPUT);            // Set pin as output
  digitalWrite(sirenRelay, !relayLogic);    // Initially turn off relay
  digitalWrite(lightRelay, !relayLogic);    // Initially turn off relay
  digitalWrite(electroRelay, !relayLogic);  // Initially turn off relay

  // Set wire pins as input with pull up
  pinMode(wire1Pin, INPUT_PULLUP);  // Set wire pin as input with pull up
  pinMode(wire2Pin, INPUT_PULLUP);  // Set wire pin as input with pull up


  // pzem.resetEnergy(); // Uncomment this line to reset energy consumption
  // Setup a function to be called every second
  timer.setInterval(2000L, readSendPower);       // Call Read Energy Meter Every 2 second
  timer.setInterval(200L, checkWireCut);         // Call Check for Wire Cut
  timer.setInterval(1000L, resetElectroMagnet);  // Check for electromagnet to rest it back if unlocked
}

void loop() {
  // check WiFi connection:
  if (WiFi.status() != WL_CONNECTED)
  {
    // (optional) "offline" part of code

    // check delay:
    if (millis() - lastConnectionAttempt >= connectionDelay)
    {
      lastConnectionAttempt = millis();

      // attempt to connect to Wifi network:
      if (pass && strlen(pass))
      {
        WiFi.begin((char*)ssid, (char*)pass);
      }
      else
      {
        WiFi.begin((char*)ssid);
      }
    }
  }
  else
  {
    Blynk.run();
    timer.run();
  }
}