#define BLYNK_TEMPLATE_ID "TMPL64-PTQxbQ"
#define BLYNK_TEMPLATE_NAME "water smart systems"
#define BLYNK_AUTH_TOKEN "obfhr6knwgcSdUoKjFZEivQb14PXcg1b"

#include <BlynkSimpleEsp32.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <FlowMeter.h>

int relayPins[] = {23, 22, 21, 5};
int pushButtonPin = 18;
FlowMeter meters[] = {FlowMeter(1), FlowMeter(2), FlowMeter(3), FlowMeter(4), FlowMeter(5)};
const unsigned long period = 1000;

int relayStates[] = {HIGH, HIGH, HIGH, HIGH};  // Initial state: OFF
int pushButtonState = HIGH;
int currentState = 0;
unsigned long startTime = 0;

#define FLOW_CALIBRATION 7.5
#define VPIN_RESET V9
#define VPIN_REBOOT V10
#define VPIN_TERMINAL V23
volatile long pulseCounts[5];
float flowRates[5];
unsigned long oldTimes[5];
BlynkTimer timer;

// Function to update Blynk virtual pins with flow rate and total volume
void updateBlynkValues(int index) {
  Blynk.virtualWrite(V11 + index, meters[index].getCurrentFlowrate());
  Blynk.virtualWrite(V16 + index, meters[index].getTotalVolume());
}

// BLYNK_WRITE functions

BLYNK_WRITE(VPIN_REBOOT) {
  int rebootValue = param.asInt();

  if (rebootValue == 1) {
    Serial.println("Rebooting ESP32...");
    delay(1000);  // Delay for 1 second to allow Blynk to update
    ESP.restart();
  }
}

BLYNK_WRITE(V0) { toggleRelay(0); }
BLYNK_WRITE(V1) { toggleRelay(1); }
BLYNK_WRITE(V2) { toggleRelay(2); }
BLYNK_WRITE(V3) { toggleRelay(3); }
BLYNK_WRITE(V4) { handleButtonPress(); }

void toggleRelay(int index) {
  relayStates[index] = relayStates[index] == HIGH ? LOW : HIGH;
  digitalWrite(relayPins[index], relayStates[index]);

  // Update corresponding virtual pins
  switch (index) {
    case 0:
      Blynk.virtualWrite(V0, relayStates[index] == HIGH ? 0 : 1);
      Blynk.virtualWrite(V3, relayStates[index == 0 ? 3 : index - 1] == HIGH ? 0 : 1);
      break;
    case 1:
      Blynk.virtualWrite(V1, relayStates[index] == HIGH ? 0 : 1);
      break;
    case 2:
      Blynk.virtualWrite(V2, relayStates[index] == HIGH ? 0 : 1);
      break;
    case 3:
      Blynk.virtualWrite(V3, relayStates[index] == HIGH ? 0 : 1);
      Blynk.virtualWrite(V0, relayStates[index == 3 ? 0 : index + 1] == HIGH ? 0 : 1);
      break;
  }
}

void handleButtonPress() {
  Blynk.virtualWrite(V4, 1);
  int i = pushButtonState != LOW ? (toggleRelay(0), toggleRelay(1), currentState = 1, startTime = millis(), 0) : HIGH;
  pushButtonState = i == 0 ? LOW : HIGH;

  if (currentState > 0 && millis() - startTime >= 7000) {
    toggleRelay(currentState);
    toggleRelay((currentState + 1) % 4);
    currentState = (currentState + 1) % 4;
    startTime = millis();
  }
}

void checkPhysicalButton() {
  pushButtonState = digitalRead(pushButtonPin) == LOW ? (toggleRelay(0), toggleRelay(1), currentState = 1, startTime = millis(), LOW) : HIGH;

  if (currentState > 0 && millis() - startTime >= 7000) {
    toggleRelay(currentState);
    toggleRelay((currentState + 1) % 4);
    currentState = (currentState + 1) % 4;
    startTime = millis();
  }
}

void Meter1ISR() {
  meters[0].count();
}

void Meter2ISR() {
  meters[1].count();
}

void Meter3ISR() {
  meters[2].count();
}

void Meter4ISR() {
  meters[3].count();
}

void Meter5ISR() {
  meters[4].count();
}

void setup() {
  Serial.begin(115200);
  WiFi.begin("Wokwi-GUEST");

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting...");
  }

  Serial.println("Connected");
  Blynk.config(BLYNK_AUTH_TOKEN);

  // Initialize values of V0, V1, V2, and V3 to zero
  Blynk.virtualWrite(V0, 0);
  Blynk.virtualWrite(V1, 0);
  Blynk.virtualWrite(V2, 0);
  Blynk.virtualWrite(V3, 0);

  attachInterrupt(digitalPinToInterrupt(34), Meter1ISR, RISING);
  attachInterrupt(digitalPinToInterrupt(35), Meter2ISR, RISING);
  attachInterrupt(digitalPinToInterrupt(33), Meter3ISR, RISING);
  attachInterrupt(digitalPinToInterrupt(32), Meter4ISR, RISING);
  attachInterrupt(digitalPinToInterrupt(25), Meter5ISR, RISING);

  for (int i = 0; i < 5; ++i) {
    meters[i].reset();
  }

  for (int i = 0; i < 4; ++i) {
    pinMode(relayPins[i], OUTPUT);
    digitalWrite(relayPins[i], HIGH);  // Set initial state to OFF
  }

  pinMode(pushButtonPin, INPUT_PULLUP);

  // Set up the timer for regular updates to V4
  timer.setInterval(100L, []() { Blynk.virtualWrite(V4, 1); });
}

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

  static unsigned long previousMillis = 0;
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= period) {
    for (int i = 0; i < 5; ++i) {
      meters[i].tick(period);

      // Update flow rate values on virtual pins V11 to V15
      updateBlynkValues(i);

      Serial.println("M" + String(i + 1) + " " + String(meters[i].getCurrentFlowrate()) + " l/min, " + String(meters[i].getTotalVolume()) + " l total.");
    }

    previousMillis = currentMillis;
  }
}

BLYNK_WRITE(VPIN_RESET) {
  int resetValue = param.asInt();

  if (resetValue == 1) {
    Serial.println("Clearing Data");

    // Manually set total volume to 0
    for (int i = 0; i < 5; ++i) {
      meters[i].setTotalVolume(0);
      Blynk.virtualWrite(V16 + i, 0);  // Update total volume to 0 on the app
    }
  }
}

#define VPIN_TERMINAL V23
#define FLOW_CALIBRATION 7.5

BLYNK_WRITE(VPIN_TERMINAL) {
  String command = param.asStr();
  Serial.println("Received command: " + command);

  // Reset total volumes for all flow meters and update Blynk virtual pins to 0
  for (int i = 0; i < 5; ++i) {
    meters[i].setTotalVolume(0);
    updateBlynkValues(i);
  }

  // Parse the command
  int spaceIndex = command.indexOf(' ');
  if (spaceIndex != -1) {
    // Extract volume and relayID from the command
    String volumeStr = command.substring(0, spaceIndex);
    String relayID = command.substring(spaceIndex + 1);

    // Convert volume string to float
    float targetVolume = volumeStr.toFloat();

    // Determine which relay to control based on the specified relayID
    int relayIndex = -1;

    if (relayID.equalsIgnoreCase("A")) {
      relayIndex = 1; // V1 controls FlowMeter(2), and V0 is not used
      Blynk.virtualWrite(V0, 1); // Turn on V0
      Blynk.virtualWrite(V1, 1); // Turn on V1
    } else if (relayID.equalsIgnoreCase("B")) {
      relayIndex = 2; // V2 controls FlowMeter(3), and V0 is not used
      Blynk.virtualWrite(V0, 1); // Turn on V0
      Blynk.virtualWrite(V2, 1); // Turn on V2
    } else if (relayID.equalsIgnoreCase("C")) {
      relayIndex = 3; // V3 controls FlowMeter(4), and V0 is not used
      Blynk.virtualWrite(V0, 1); // Turn on V0
      Blynk.virtualWrite(V3, 1); // Turn on V3
    }

    if (relayIndex != -1) {
      // Activate the specified relay
      digitalWrite(relayPins[0], LOW);
      digitalWrite(relayPins[relayIndex], LOW);

      // Monitor the corresponding flow sensor until the desired volume is reached
      Serial.println("Dispensing " + String(targetVolume) + " liters...");

      unsigned long dispenseStartTime = millis();
      bool timeout = false;

      // No need for a loop, just check the flow of the specified sensor using 'relayIndex'
      while (meters[relayIndex - 1].getTotalVolume() < targetVolume) {
      meters[relayIndex - 1].tick(period);
      
        updateBlynkValues(relayIndex - 1);
        Serial.println("M" + String(relayIndex) + " " + String(meters[relayIndex - 1].getCurrentFlowrate()) + " l/min, " + String(meters[relayIndex - 1].getTotalVolume()) + " l total.");

        // Add a timeout condition if needed

      }

      // Deactivate the relay
      digitalWrite(relayPins[0], HIGH);
      digitalWrite(relayPins[relayIndex], HIGH);

      // Turn off corresponding virtual pin
      Blynk.virtualWrite(V0, 0);
      Blynk.virtualWrite(V1, 0);
      Blynk.virtualWrite(V2, 0);
      Blynk.virtualWrite(V3, 0);

      if (!timeout) {
        Serial.println("Dispensing complete.");
      }
    } else {
      Serial.println("Invalid relayID. Use A, B, or C.");
    }
  } else {
    Serial.println("Invalid command format. Use <volume>L,<relayID>");
  }
}

Pulse GeneratorBreakout
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module