Configuring Time and Data Transmission for Fuzzy System to ThingSpeak

Is it possible to configure the fuzzy system to only operate at specific times, namely 00:00:00, 04:00:00, 08:00:00, 12:00:00, 16:00:00, and 20:00:00 (GMT+7)? Additionally, I would like to send these eight data points to my first ThingSpeak account every minute, precisely at the 00th second (e.g., at 14:30:00). Can this be done?

#include <ModbusMaster.h>
#include <Fuzzy.h>
#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <HTTPClient.h>

// WiFi credentials
const char* ssid = "R3";       
const char* password = "samsungwifi"; 

// ThingSpeak credentials first account
const char* apiKey = "JM9M79MC4KUXXXXX"; // Write API Key
const char* channelID = "2572XXX"; 

// ThingSpeak credentials second account
const char* apiKey2 = "UDBQZQ7GOODXXXXX"; // Write API Key for second account
const char* channelID2 = "2765XXX"; 

// NTP client setup
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 7 * 3600); // Offset GMT+7

// Initialize ModbusMaster for sensors and relay
ModbusMaster THCPH;
ModbusMaster relay;
ModbusMaster NPK; // Sensor NPK

// Variables to store sensor values
float soilMoistureValue = 0.0;
float soilTemperatureValue = 0.0;
float conductivityValue = 0.0;
float pHValue = 0.0;

// NPK values
int nitrogenValue = 0;
int phosphorusValue = 0;
int potassiumValue = 0;

// Fuzzy logic setup
Fuzzy *fuzzy = new Fuzzy();

// FuzzyInput for Soil Moisture
FuzzyInput *soilMoistureInput = new FuzzyInput(1);
FuzzySet *dry = new FuzzySet(0, 0, 50, 60);
FuzzySet *moist = new FuzzySet(50, 60, 80, 90);
FuzzySet *wet = new FuzzySet(80, 90, 100, 100);

// FuzzyInput for Soil Temperature
FuzzyInput *soilTemperatureInput = new FuzzyInput(2);
FuzzySet *cold = new FuzzySet(0, 0, 21, 24);
FuzzySet *slightlyCold = new FuzzySet(21, 24, 24, 27);
FuzzySet *normal = new FuzzySet(24, 27, 27, 30);
FuzzySet *hot = new FuzzySet(27, 30, 50, 50);

// FuzzyOutput for Watering Duration
FuzzyOutput *wateringDurationOutput = new FuzzyOutput(1);
FuzzySet *veryFast = new FuzzySet(0, 17, 17, 34);
FuzzySet *fast = new FuzzySet(17, 34, 34, 51);
FuzzySet *medium = new FuzzySet(34, 51, 51, 68);
FuzzySet *longDuration = new FuzzySet(51, 68, 68, 85);
FuzzySet *veryLong = new FuzzySet(68, 85, 85, 102);

// Variables for time-based control
unsigned long nextActivationTime = 0;
unsigned long previousMillis = 0;
const long interval = 500; // Interval for checking

// Function to send data to ThingSpeak first account
void sendToThingSpeak(float moisture, float temperature, float conductivity, float pH, float waterDuration, int nitrogen, int phosphorus, int potassium) {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    String url = "http://api.thingspeak.com/update?api_key=" + String(apiKey) +
                  "&field1=" + String(moisture) +
                  "&field2=" + String(temperature) +
                  "&field3=" + String(conductivity) +
                  "&field4=" + String(pH) +
                  "&field5=" + String(nitrogen) +
                  "&field6=" + String(phosphorus) +
                  "&field7=" + String(potassium) +
                  "&field8=" + String(waterDuration);
    
    http.begin(url);
    int httpResponseCode = http.GET(); // Send the request

    if (httpResponseCode > 0) {
      String response = http.getString(); // Get the response
    } else {
      Serial.print("Error on sending POST: ");
      Serial.println(httpResponseCode);
    }
    http.end(); // Free resources
  } else {
    Serial.println("WiFi not connected");
  }
}

// Function to send updated data to ThingSpeak second account
void sendToThingSpeakSecondAccount(float updatedMoisture, float updatedTemperature, float updatedConductivity, float updatedPH) {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    String url = "http://api.thingspeak.com/update?api_key=" + String(apiKey2) + "&field1=" + String(updatedMoisture) + "&field2=" + String(updatedTemperature) + "&field3=" + String(updatedConductivity) + "&field4=" + String(updatedPH);
    
    http.begin(url);
    int httpResponseCode = http.GET(); // Send the request

    if (httpResponseCode > 0) {
      String response = http.getString(); // Get the response
    } else {
      Serial.print("Error on sending POST to second account: ");
      Serial.println(httpResponseCode);
    }
    http.end(); // Free resources
  } else {
    Serial.println("WiFi not connected");
  }
}

void setup() {
  Serial.begin(9600);
  Serial.println("Fuzzy Logic with Sensor Data and Relay Control");

  // Connect to WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  // Initialize NTP client
  timeClient.begin();
  timeClient.update();

  // Set next activation time to the next :00 second
  nextActivationTime = timeClient.getEpochTime();
  nextActivationTime = nextActivationTime - (nextActivationTime % 60) + 60;

  // Initialize Modbus
  Serial2.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17
  THCPH.begin(2, Serial2); // Slave ID for sensors
  relay.begin(1, Serial2); // Slave ID for relay
  NPK.begin(3, Serial2); // Slave ID for NPK sensor

// Fuzzy logic setup
  fuzzy->addFuzzyInput(soilMoistureInput);
  soilMoistureInput->addFuzzySet(dry);
  soilMoistureInput->addFuzzySet(moist);
  soilMoistureInput->addFuzzySet(wet);

  fuzzy->addFuzzyInput(soilTemperatureInput);
  soilTemperatureInput->addFuzzySet(cold);
  soilTemperatureInput->addFuzzySet(slightlyCold);
  soilTemperatureInput->addFuzzySet(normal);
  soilTemperatureInput->addFuzzySet(hot);

  fuzzy->addFuzzyOutput(wateringDurationOutput);
  wateringDurationOutput->addFuzzySet(veryFast);
  wateringDurationOutput->addFuzzySet(fast);
  wateringDurationOutput->addFuzzySet(medium);
  wateringDurationOutput->addFuzzySet(longDuration);
  wateringDurationOutput->addFuzzySet(veryLong);

  // Add fuzzy rules
  FuzzyRuleAntecedent *rule1Antecedent = new FuzzyRuleAntecedent();
  rule1Antecedent->joinWithAND(wet, cold);
  FuzzyRuleConsequent *rule1Consequent = new FuzzyRuleConsequent();
  rule1Consequent->addOutput(veryFast);
  fuzzy->addFuzzyRule(new FuzzyRule(1, rule1Antecedent, rule1Consequent));

  FuzzyRuleAntecedent *rule2Antecedent = new FuzzyRuleAntecedent();
  rule2Antecedent->joinWithAND(moist, cold);
  FuzzyRuleConsequent *rule2Consequent = new FuzzyRuleConsequent();
  rule2Consequent->addOutput(fast);
  fuzzy->addFuzzyRule(new FuzzyRule(2, rule2Antecedent, rule2Consequent));

  FuzzyRuleAntecedent *rule3Antecedent = new FuzzyRuleAntecedent();
  rule3Antecedent->joinWithAND(dry, cold);
  FuzzyRuleConsequent *rule3Consequent = new FuzzyRuleConsequent();
  rule3Consequent->addOutput(longDuration);
  fuzzy->addFuzzyRule(new FuzzyRule(3, rule3Antecedent, rule3Consequent));

  FuzzyRuleAntecedent *rule4Antecedent = new FuzzyRuleAntecedent();
  rule4Antecedent->joinWithAND(wet, slightlyCold);
  FuzzyRuleConsequent *rule4Consequent = new FuzzyRuleConsequent();
  rule4Consequent->addOutput(veryFast);
  fuzzy->addFuzzyRule(new FuzzyRule(4, rule4Antecedent, rule4Consequent));

  FuzzyRuleAntecedent *rule5Antecedent = new FuzzyRuleAntecedent();
  rule5Antecedent->joinWithAND(moist, slightlyCold);
  FuzzyRuleConsequent *rule5Consequent = new FuzzyRuleConsequent();
  rule5Consequent->addOutput(fast);
  fuzzy->addFuzzyRule(new FuzzyRule(5, rule5Antecedent, rule5Consequent));

  FuzzyRuleAntecedent *rule6Antecedent = new FuzzyRuleAntecedent();
  rule6Antecedent->joinWithAND(dry, slightlyCold);
  FuzzyRuleConsequent *rule6Consequent = new FuzzyRuleConsequent();
  rule6Consequent->addOutput(longDuration);
  fuzzy->addFuzzyRule(new FuzzyRule(6, rule6Antecedent, rule6Consequent));

  FuzzyRuleAntecedent *rule7Antecedent = new FuzzyRuleAntecedent();
  rule7Antecedent->joinWithAND(wet, normal);
  FuzzyRuleConsequent *rule7Consequent = new FuzzyRuleConsequent();
  rule7Consequent->addOutput(fast);
  fuzzy->addFuzzyRule(new FuzzyRule(7, rule7Antecedent, rule7Consequent));

  FuzzyRuleAntecedent *rule8Antecedent = new FuzzyRuleAntecedent();
  rule8Antecedent->joinWithAND(moist, normal);
  FuzzyRuleConsequent *rule8Consequent = new FuzzyRuleConsequent();
  rule8Consequent->addOutput(medium);
  fuzzy->addFuzzyRule(new FuzzyRule(8, rule8Antecedent, rule8Consequent));

  FuzzyRuleAntecedent *rule9Antecedent = new FuzzyRuleAntecedent();
  rule9Antecedent->joinWithAND(dry, normal);
  FuzzyRuleConsequent *rule9Consequent = new FuzzyRuleConsequent();
  rule9Consequent->addOutput(veryLong);
  fuzzy->addFuzzyRule(new FuzzyRule(9, rule9Antecedent, rule9Consequent));

  FuzzyRuleAntecedent *rule10Antecedent = new FuzzyRuleAntecedent();
  rule10Antecedent->joinWithAND(wet, hot);
  FuzzyRuleConsequent *rule10Consequent = new FuzzyRuleConsequent();
  rule10Consequent->addOutput(fast);
  fuzzy->addFuzzyRule(new FuzzyRule(10, rule10Antecedent, rule10Consequent));

  FuzzyRuleAntecedent *rule11Antecedent = new FuzzyRuleAntecedent();
  rule11Antecedent->joinWithAND(moist, hot);
  FuzzyRuleConsequent *rule11Consequent = new FuzzyRuleConsequent();
  rule11Consequent->addOutput(medium);
  fuzzy->addFuzzyRule(new FuzzyRule(11, rule11Antecedent, rule11Consequent));

  FuzzyRuleAntecedent *rule12Antecedent = new FuzzyRuleAntecedent();
  rule12Antecedent->joinWithAND(dry, hot);
  FuzzyRuleConsequent *rule12Consequent = new FuzzyRuleConsequent();
  rule12Consequent->addOutput(veryLong);
  fuzzy->addFuzzyRule(new FuzzyRule(12, rule12Antecedent, rule12Consequent));

}

void loop() {
  unsigned long currentMillis = millis();
  
  // Update NTP time periodically (non-blocking)
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    timeClient.update();
  }

  // Check if it's time for the next activation
  unsigned long currentTime = timeClient.getEpochTime();
  if (currentTime >= nextActivationTime) {
    Serial.println("Activating sensors and fuzzy logic...");

    // Read soil moisture and temperature
    uint8_t result = THCPH.readHoldingRegisters(0x0000, 1);
    if (result == THCPH.ku8MBSuccess) {
      soilMoistureValue = THCPH.getResponseBuffer(0x00) / 10.0f;
    } else {
      Serial.print("Error reading soil moisture: ");
      Serial.println(result);
    }

    result = THCPH.readHoldingRegisters(0x0001, 1);
    if (result == THCPH.ku8MBSuccess) {
      soilTemperatureValue = THCPH.getResponseBuffer(0x00) / 10.0f;
    } else {
      Serial.print("Error reading soil temperature: ");
      Serial.println(result);
    }

    result = THCPH.readHoldingRegisters(0x0002, 1);
    if (result == THCPH.ku8MBSuccess) {
      conductivityValue = THCPH.getResponseBuffer(0x00) / 10.0f;
    } else {
      Serial.print("Error reading conductivity: ");
      Serial.println(result);
    }

    result = THCPH.readHoldingRegisters(0x0003, 1);
    if (result == THCPH.ku8MBSuccess) {
      pHValue = THCPH.getResponseBuffer(0x00) / 10.0f;
    } else {
      Serial.print("Error reading pH: ");
      Serial.println(result);
    }

        // Read NPK sensor
    result = NPK.readHoldingRegisters(0x001E, 1);
    if (result == NPK.ku8MBSuccess) nitrogenValue = NPK.getResponseBuffer(0x00);
    result = NPK.readHoldingRegisters(0x001F, 1);
    if (result == NPK.ku8MBSuccess) phosphorusValue = NPK.getResponseBuffer(0x00);
    result = NPK.readHoldingRegisters(0x0020, 1);
    if (result == NPK.ku8MBSuccess) potassiumValue = NPK.getResponseBuffer(0x00);

    // Apply fuzzy logic
    fuzzy->setInput(1, soilMoistureValue);
    fuzzy->setInput(2, soilTemperatureValue);
    fuzzy->fuzzify();

    float wateringDurationValue = fuzzy->defuzzify(1);

    // Display sensor values
    Serial.print("Soil Moisture: ");
    Serial.print(soilMoistureValue);
    Serial.print("% | Soil Temperature: ");
    Serial.print(soilTemperatureValue);
    Serial.print("°C | Water Duration: ");
    Serial.print(wateringDurationValue);
    Serial.print("seconds | Conductivity: ");
    Serial.print(conductivityValue);
    Serial.print("µS/cm | pH: ");
    Serial.println(pHValue);

    Serial.print("Nitrogen: ");
    Serial.print(nitrogenValue);
    Serial.print(" | Phosphorus: ");
    Serial.print(phosphorusValue);
    Serial.print(" | Potassium: ");
    Serial.println(potassiumValue);

    // Send data to ThingSpeak first account
    sendToThingSpeak(soilMoistureValue, soilTemperatureValue, conductivityValue, pHValue, wateringDurationValue, nitrogenValue, phosphorusValue, potassiumValue);

    // Activate relay
    if (wateringDurationValue > 0) {
      String formattedTime = timeClient.getFormattedTime();
      result = relay.writeSingleCoil(0x0000, 1);
      if (result == relay.ku8MBSuccess) {
        Serial.print("Relay activated: ");
        Serial.print(formattedTime);
        Serial.println(" WIB");
      }

      // Wait for watering duration using millis() (non-blocking)
      unsigned long wateringStartTime = millis();
      while (millis() - wateringStartTime < (unsigned long)(wateringDurationValue * 1000)) {
        // Allow NTP updates and other background tasks
        timeClient.update();
      }

      // Deactivate relay
      formattedTime = timeClient.getFormattedTime();
      result = relay.writeSingleCoil(0x0000, 0);
      if (result == relay.ku8MBSuccess) {
        Serial.print("Relay deactivated: ");
        Serial.print(formattedTime);
        Serial.println(" WIB");

        // Add delay for stabilization
        delay(1000);

        // Read updated soil moisture and temperature after watering
        result = THCPH.readHoldingRegisters(0x0000, 1);
        if (result == THCPH.ku8MBSuccess) {
          soilMoistureValue = THCPH.getResponseBuffer(0x00) / 10.0f;
        } else {
          Serial.print("Error reading updated soil moisture: ");
          Serial.println(result);
        }

        result = THCPH.readHoldingRegisters(0x0001, 1);
        if (result == THCPH.ku8MBSuccess) {
          soilTemperatureValue = THCPH.getResponseBuffer(0x00) / 10.0f;
        } else {
          Serial.print("Error reading updated soil temperature: ");
          Serial.println(result);
        }

        // Send updated data to ThingSpeak second account
        sendToThingSpeakSecondAccount(soilMoistureValue, soilTemperatureValue, conductivityValue, pHValue);

        // Display updated sensor values
        Serial.print("Updated Soil Moisture: ");
        Serial.print(soilMoistureValue);
        Serial.print(" % | Updated Soil Temperature: ");
        Serial.print(soilTemperatureValue);
        Serial.println(" °C");
        Serial.print("---------------------------------------------------------------------------------------");
        Serial.println(" ");
      }
    }

    // Set next activation time to the next minute's :00 second
    nextActivationTime = currentTime - (currentTime % 60) + 60;
    if (wateringDurationValue > 60) {
      nextActivationTime += 60; // Add time if duration is more than 1 minute
    }
  }
}

This is the output of the code

Are the times you mention the times of some clock in your computer or are they times continuously returned from national time clock standard that you interrogate every second?

Overall, the time used in this code is obtained from the NTP server and adjusted for the (WIB = Waktu Indonesia Barat) or Western Indonesian Time Zone (GMT+7). This approach ensures accurate timekeeping without relying on the device's local time, which may not be synchronized.

Actually the accuracy is gone by the time of the next clock tick. If time accuracy is needed, then that must be the goal of your project. How do you know the next minute tick still matches the national time clock. You don't.
You need to cushion your expectations for an Arduino to keep accurate time over any span of time.

For accuracy, it’s not too important to me if the error is ± five seconds, because what I see from 'relay activated' to 'relay deactivated' is the same as 'water duration.' Originally, the timing was accurate from second :00, but after adding a soil sensor (modbusmaster NPK) to the ESP32, the workload on the ESP32 might have increased.

When everything is working, it might be interesting to monitor the accuracy over time.