Blynk loses connection after a few hours. Need to restart arduino

I’m having issues with connectivity. My Blynk connection is lost after random periods longer than several hours, requiring me to restart my Arduino almost every day. (The code is for an automated watering system that sense dry soil and actives relays to water the respective plant.)

On restart connections to Blynk are faultless, but something is causing Blynk to lose connection. I believe I am not flooding the Blynk server as I send a just a few data points every 15 seconds and the restarts are always happening during while the short 30 second watering periods kick in with more frequent updates.

In my network monitoring of my Wifi, I do not see any indications that the WiFi network itself might be dropping the Arduino. I have stable Wifi and don’t see issues with any other devices.

Any suggestions or ideas what might be causing these regular connection losses would be appreciated!

• Hardware model + communication type: Arduino NANO 33 IoT, WiFi communication. I have a Relay board attached that is being controlled by the digital outputs.
• Smartphone OS (iOS or Android) + version: Apple iPhone 11, iOS version 14.4.2
• Blynk server or local server. Using Blynk server
• Blynk Library version: 0.6.1

//#include <MemoryFree.h>
#define BLYNK_PRINT Serial
#define BLYNK_ON   // Should blynk be updated with data?

#include <SPI.h>
#include <WiFiNINA.h>
#include <BlynkSimpleWiFiNINA.h>
#include <TimeLib.h>
#include <WidgetRTC.h>

// Define moisture sensor pins.
const int sensorNum=8; // How many analog pins exist
int SensorPins[sensorNum]={14,15,0,0,0,0,0,0};   // 0 = no sensor
int plantStartWateringLevel[sensorNum]={0,0,0,0,0,0,0,0};   // Level at which if sensor is below this level, plant should start to be watered.
int plantStopWateringLevel[sensorNum]={70,70,70,70,70,70,70,70};   // Level at which if sensor is above this level, plant should stop being watered.
String plantName[sensorNum]={"Bonsai", "Bonsai","","","","","",""};
int valvePin[sensorNum]={3,3,0,0,0,0,0,0};  // Which Arduino digital pin to trigger when sensor i is above / below trigger level.
float moistureScore[sensorNum];
float pumpDuration;  // How many minutes have passed since water storage container was refilled.

// Defining digital and analog pins
#define PUMP_RELAY 2
#define VALVE_RELAY 3
#define BLYNK_FAST_UPDATE 3000
#define BLYNK_SLOW_UPDATE 15000

// Define the extreme values for the sensor output. 
int waterValue=340;  // Measured raw reading when sensor is in water
int dryValue=900;    // Measured raw reading when sensor is in air
const int dryBuffer=5;  // dryValue is dryBuffer % above the value of dryValue
const int wetBuffer=3;   // waterValue is wetBuffer % below the value of waterValue
const int maxMoistureScore=100;
float wetValue=waterValue;  // Assumed raw value to equal 100% water
float zeroValue=dryValue;   // Assumed raw value to equal 0% water
int sensorValueOrig = 0;
int countValvesOpen=0;    // How many plant valves are open. Only run pump if at least 1 valve is open.
double valveStartTime=0;  // To hold millis when start watering.
double valveDuration=0;   // To hold watering duration in millis.

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "MyAuthToken";

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "MYSSID";
char pass[] = "MyPassword";

// Attach virtual serial terminal to Virtual Pin V1
WidgetTerminal terminal(V0);
BlynkTimer timer;
WidgetRTC rtc;

float lastMoistureAverage[sensorNum]={maxMoistureScore/2, maxMoistureScore/2, maxMoistureScore/2,maxMoistureScore/2,maxMoistureScore/2,maxMoistureScore/2,maxMoistureScore/2,maxMoistureScore/2};
float trimValue=0.2;
double lastBlynkUpdate=0;
double blynkUpdateGap=BLYNK_SLOW_UPDATE;   // How many millis should pass before sensor values are sent to Blynk.

// Digital clock display of the time
void clockDisplay()
{
  String my_h = MyPrintDigits(hour());
  String my_m = MyPrintDigits(minute());
  String my_s = MyPrintDigits(second());

  String currentTime = my_h+ ":"+my_m +":"+ my_s;
  String currentDate = String(day()) + "." + month() + "." + year();
  Serial.print("Current time: ");
  Serial.print(currentDate);
  Serial.print(" ");
  Serial.print(currentTime);
  Serial.println();

  // Send time to the App
  Blynk.virtualWrite(V9, currentTime);
  // Send date to the App
  Blynk.virtualWrite(V8, currentDate);
}

String timeNow() {
  String my_h = MyPrintDigits(hour());
  String my_m = MyPrintDigits(minute());
  String my_s = MyPrintDigits(second());
  String currentTime = my_h+ ":"+my_m +":"+ my_s;
return currentTime;
}

String dateNow() {
  String currentDate = String(day()) + "." + month() + "." + year();
return currentDate;
}

BLYNK_CONNECTED() {
  // Synchronize time on connection
  rtc.begin();
  Blynk.syncVirtual(V1, V5, V6);
}

void setup()
{
  Serial.begin(9600);
  Blynk.begin(auth, ssid, pass);
  pinMode(PUMP_RELAY, OUTPUT);
  pinMode(VALVE_RELAY, OUTPUT);
  zeroValue=computeZeroValue(dryValue, waterValue, dryBuffer, wetBuffer);
  wetValue=computeWetValue(dryValue, waterValue, dryBuffer, wetBuffer);

  // Clear the terminal content
  printDateTime();
  terminal.println("Blynk restarted.");
  terminal.flush();
}

void printDateTime() {
  terminal.print(dateNow());
  terminal.print(" ");
  terminal.println(timeNow());
}

BLYNK_WRITE(V1)
// Bonsai plant - start watering
{
  int pinValue=param.asInt(); // assigning incoming value from pin V1 to a variable
  plantStartWateringLevel[0]= pinValue;
  plantStartWateringLevel[1]= pinValue; // assigning incoming value from pin V1 to a variable
  // Setting 2 sensors as Bonsai has 2 sensors.
  Serial.print("Bonsai starts watering below ");
  Serial.println(plantStartWateringLevel[0]);
  printDateTime();
  terminal.print("Start watering bonsai if below: ");
  terminal.println(plantStartWateringLevel[0]);
  terminal.flush();
}

BLYNK_WRITE(V6)
// Bonsai plant - stop watering trigger level
{
  int pinValue=param.asInt(); // assigning incoming value from pin V1 to a variable
  plantStopWateringLevel[0]= pinValue; // assigning incoming value from pin V6 to a variable
  plantStopWateringLevel[1]= pinValue; // assigning incoming value from pin V6 to a variable
  Serial.print("Bonsai stops watering above ");
  Serial.println(plantStopWateringLevel[0]);
  printDateTime();
  terminal.print("Stop watering bonsai if above: ");
  terminal.println(plantStopWateringLevel[0]);
  terminal.flush();
}

BLYNK_WRITE(V2)    // Manually start watering Bonsai
{
  int pinValue = param.asInt(); // assigning incoming value from pin V2 to a variable
  if(pinValue==1) {
    startWatering(0);
    countValvesOpen=1;
  }
}

BLYNK_WRITE(V12)    // Emergency stop
{
  int pinValue = param.asInt(); // assigning incoming value from pin V12 to a variable
  if(pinValue==1 && countValvesOpen==1) {  // Only stop if valve actually open.
    stopWatering(0);
    countValvesOpen=0;
  }
}


BLYNK_WRITE(V7)   // Button to signal that water container has been refilled.
{
  int pinValue = param.asInt(); // assigning incoming value from pin V7 to a variable
  if(pinValue==1) {
    Serial.println("*** Water container refilled ***");
    printDateTime();
    terminal.println("Water container refilled. Resetting pumpDuration.");
    terminal.flush();
    pumpDuration=0;
  }
}

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

  // Step through each sensorPin...
  for (int i=0; i<(sensorNum); i++) {
    // If sensor pin present
    if (SensorPins[i]>0) {
      // Read pin and return moisture level.
      sensorValueOrig = analogRead(SensorPins[i]);
      moistureScore[i]=computeMoistureScore(sensorValueOrig);
      float currentMoistureAverage = filter(moistureScore[i], trimValue, lastMoistureAverage[i]);
      lastMoistureAverage[i] = currentMoistureAverage;
      }
    else {
      // No sensor on this pin
    } 
  }

  if (millis()>lastBlynkUpdate+blynkUpdateGap) {
    // Enough time has passed. Send the sensor values to blynk.
    lastBlynkUpdate=millis();
    clockDisplay();
    #ifdef BLYNK_ON
      Blynk.virtualWrite(V5, pumpDuration);  
      for (int i=0; i<(sensorNum); i++) {
        switch (i) {
          case 0:
            Blynk.virtualWrite(V10, (int) moistureScore[i]);  
            break;
          case 1:
            Blynk.virtualWrite(V11, (int) moistureScore[i]);  
            break;
          case 2:
            break;
          case 3:
            break;
          case 4:
            break;
          case 5:
            break;
          case 6:
            break;
          case 7: 
            break;
        }
      }
    #endif
    for (int i=0; i<(sensorNum); i++) {
      if (SensorPins[i]>0) {    // Check if this pin has a sensor. 
        if ((int) moistureScore[i]<plantStartWateringLevel[i] && countValvesOpen == 0) {
          startWatering(i);
        }
        if ((int) moistureScore[i]>plantStopWateringLevel[i] && countValvesOpen==1) { 
          // stopWatering only if you are currently watering.
          stopWatering(i);
        }
      }
    }
  }
}

void startWatering(int i) {
  Serial.println("*** Valve Relay ON ***");
  digitalWrite(valvePin[i], HIGH);
  Serial.println("*** Pump Relay ON ***");
  digitalWrite(PUMP_RELAY, HIGH);          
  valveStartTime=millis();  // Store millis when start watering.
  printDateTime();
  terminal.print("Watering ");
  terminal.print(plantName[i]);
  terminal.println("...");
  terminal.flush();
  blynkUpdateGap=BLYNK_FAST_UPDATE;
  countValvesOpen=1;
}

void stopWatering(int i) {
  Serial.println("*** Valve Relay OFF ***");
  digitalWrite(valvePin[i], LOW);
  Serial.println("*** Pump Relay OFF ***");
  digitalWrite(PUMP_RELAY, LOW);          
  valveDuration=millis()-valveStartTime;   // Store watering duration in millis.
  terminal.print("Stopping watering of ");
  terminal.print(plantName[i]);
  terminal.print(". ");
  terminal.print((valveDuration/1000));
  terminal.println(" secs.");
  pumpDuration+=(float) (valveDuration/60000);
  terminal.print("Watering mins since last refil: ");
  terminal.println(pumpDuration);
  terminal.flush();
  valveDuration=0;
  blynkUpdateGap=BLYNK_SLOW_UPDATE;
}

// filter the current result using a weighted average filter:
float filter(float rawValue, float weight, float lastValue) {
  float result = weight * rawValue + (1.0-weight)*lastValue;
  return result;
}

float computeZeroValue(int dryValue, int waterValue, int dryBuffer, int wetBuffer) {
  float zeroValue=dryValue+((dryBuffer*(dryValue-waterValue))/(100-dryBuffer-wetBuffer));
  return zeroValue;
}

int computeWetValue(int dryValue, int waterValue, int dryBuffer, int wetBuffer) {
  int wetValue=waterValue-((wetBuffer*(dryValue-waterValue))/(100-dryBuffer-wetBuffer));
  return wetValue;
}

float computeMoistureScore(int reading) {
  float b=(float)zeroValue-(float)wetValue;
  float a=((float)reading-(float)wetValue)/b;
  float moistureScore=(1-a)*(float)maxMoistureScore;
  return moistureScore;
}

String MyPrintDigits(int digits) { // -- Utility function for digital clock display: prints preceding colon and leading 0
  String new_digits="";
  if (digits < 10) new_digits += "0";
  new_digits += String(digits);
  return new_digits;
}
1 Like

Hi, it is possible that routine in main loop is too CPU intensive and require too much time. In this case your device cannot contact in time Blynk Server, and you shoud see a sensor disconnected in APP.
You can try to increase the value for BLYNK threshold (HEARTHBEAT and TIMEOUT), for example:

    #define BLYNK_HEARTBEAT 20      // defaults to 10s. 
    #define BLYNK_TIMEOUT_MS 5000UL // defaults to 2000UL

Also it is better to user timers for periodic routines. I see in your code that you declare a timer variable but it seems that you actually not use it.

You should read this:

Pete.

Thank you (and Pete) for your thoughts and suggestions. Much appreciated.

I implemented the BlynkTimer to call the 2 periodic functions and added some code to push the maximum elapsed time for completing the loop() function out to Blynk so that I can monitor the speed of the loop() function. I also made the hearbeat 60 seconds.

I observed that the longest duration that occured for the loop() function to cycle fully was 410 ms. That seems like it should be fast enough for Blynk server not to complain (given 60 seconds heartbeat setting) or for Blynk.run to be processed often enough to be able to perform its tasks. Still I observed that within 90 minutes, the connection had been lost.

I can, of course, split the readSensors() function into 2 functions and call them each separately, but is this really the issue? i.e. is a loop() function running at most every 410 ms too long for Blynk?

So I’m still left wondering what might be causing the disconnection to Blynk. Any new thoughts?

Code snippet below excluding parts of the original code that were not touched:

#define BLYNK_HEARTBEAT  60
#define BLYNK_FAST_UPDATE 3000
#define BLYNK_SLOW_UPDATE 15000
BlynkTimer timer;

void setup()
{
  Serial.begin(9600);

  Blynk.begin(auth, ssid, pass);

  pinMode(PUMP_RELAY, OUTPUT);
  pinMode(VALVE_RELAY, OUTPUT);
  
  zeroValue=computeZeroValue(dryValue, waterValue, dryBuffer, wetBuffer);
  wetValue=computeWetValue(dryValue, waterValue, dryBuffer, wetBuffer);
  printDateTime();
  terminal.println("Blynk restarted.");
  terminal.flush();

  setSyncInterval(10 * 60); // Sync interval in seconds (10 minutes)
  // Display digital clock every 10 seconds
  timer.setInterval(10000L, clockDisplay);
  timer.setInterval(1000L, readSensors);
  timer.setInterval(BLYNK_SLOW_UPDATE, sendBlynkUpdates);
  double lastLoopStart=millis();
}

void loop()
{
  currentTime=millis();
  if ((currentTime-lastLoopStart)>maxLoop and lastLoopStart>0){
    maxLoop=currentTime-lastLoopStart;
    //Serial.print("New max loop duration: ");
    //Serial.println(maxLoop);
  }
  lastLoopStart=currentTime;
  Blynk.run();
  timer.run();
}

void readSensors() {
  for (int i=0; i<(sensorNum); i++) {
  // If sensor pin present
    if (SensorPins[i]>0) {
      // Read pin and return moisture level.
      sensorValueOrig = analogRead(SensorPins[i]);
      //Serial.println("02b - Before call to computeMoistureScore()");
      moistureScore[i]=computeMoistureScore(sensorValueOrig);
      //Serial.println("03 - Before call to filter()");
      float currentMoistureAverage = filter(moistureScore[i], trimValue, lastMoistureAverage[i]);
      //Serial.println("04 - After call to filter()");
      lastMoistureAverage[i] = currentMoistureAverage;
    }
  //Serial.print("moistureScore[0] in readSensors:");
  //Serial.println(moistureScore[0]);
  }
  // Loop through each sensor and check if need to start or stop pump
  for (int i=0; i<(sensorNum); i++) {
    if (SensorPins[i]>0) {    // Check if this pin has a sensor. 
      Serial.print("Checking sensor ");
      Serial.print(i);
      Serial.print(". Current level: ");
      Serial.print(moistureScore[i]);
      Serial.print(". Current level (int): ");
      Serial.print((int) moistureScore[i]);
      Serial.print(". Low trigger level: ");
      Serial.print(plantStartWateringLevel[i]);
      Serial.print(". High trigger level: ");
      Serial.println(plantStopWateringLevel[i]);
      if ((int) moistureScore[i]<plantStartWateringLevel[i] && countValvesOpen == 0) {
        startWatering(i);
      }
      if ((int) moistureScore[i]>plantStopWateringLevel[i] && countValvesOpen==1) { 
        // stopWatering only if you are currently watering.
        stopWatering(i);
      }
    }
  }
}

void sendBlynkUpdates() {
  #ifdef BLYNK_ON
    //Serial.println("Sending updates");
    Blynk.virtualWrite(V13, (int) maxLoop);  
    Blynk.virtualWrite(V5, pumpDuration);  
    //Serial.print("moistureScore[0] in sendBlynkUpdates:");
    //Serial.println(moistureScore[0]);
    for (int i=0; i<(sensorNum); i++) {
      switch (i) {
        case 0:
          Blynk.virtualWrite(V10, (int) moistureScore[i]);  
          break;
        case 1:
          Blynk.virtualWrite(V11, (int) moistureScore[i]);  
          break;
        case 2:
          break;
        case 3:
          break;
        case 4:
          break;
        case 5:
          break;
        case 6:
          break;
        case 7: 
          break;
      }
    }
  #endif
}

Please post your full code, this snippet doesn’t provide enough information.

This shouldn’t be necessary.

Testing with this in your void loop isn’t going to give you good results.

Pete.

@PeteKnight can you elaborate why testing the loop execution time isn’t going to “give good results”? I don’t need it to be highly accurate, but why would this not be indicative of the elapsed time to execute the loop() function?

Full code is as follows:

#define BLYNK_PRINT Serial
#define BLYNK_ON   // Should blynk be updated with data?
#define BLYNK_HEARTBEAT  60

#include <SPI.h>
#include <WiFiNINA.h>
#include <BlynkSimpleWiFiNINA.h>
#include <TimeLib.h>
#include <WidgetRTC.h>

// Define moisture sensor pins.
const int sensorNum=8; // How many analog pins exist
int SensorPins[sensorNum]={14,15,0,0,0,0,0,0};   // 0 = no sensor
int plantStartWateringLevel[sensorNum]={0,0,0,0,0,0,0,0};   // Level at which if sensor is below this level, plant should start to be watered.
int plantStopWateringLevel[sensorNum]={70,70,70,70,70,70,70,70};   // Level at which if sensor is above this level, plant should stop being watered.
String plantName[sensorNum]={"Bonsai", "Bonsai","","","","","",""};
int valvePin[sensorNum]={3,3,0,0,0,0,0,0};  // Which Arduino digital pin to trigger when sensor i is above / below trigger level.
float moistureScore[sensorNum];
float pumpDuration;  // How many minutes have passed since water storage container was refilled.

// Defining digital and analog pins
#define PUMP_RELAY 2
#define VALVE_RELAY 3
#define BLYNK_FAST_UPDATE 3000
#define BLYNK_SLOW_UPDATE 15000
    
// Define the extreme values for the sensor output. 
int waterValue=340;  // Measured raw reading when sensor is in water
int dryValue=900;    // Measured raw reading when sensor is in air
const int dryBuffer=5;  // dryValue is dryBuffer % above the value of dryValue
const int wetBuffer=3;   // waterValue is wetBuffer % below the value of waterValue
const int maxMoistureScore=100;
float wetValue=waterValue;  // Assumed raw value to equal 100% water
float zeroValue=dryValue;   // Assumed raw value to equal 0% water
int sensorValueOrig = 0;
int countValvesOpen=0;    // How many plant valves are open. Only run pump if at least 1 valve is open.
double valveStartTime=0;  // To hold millis when start watering.
double valveDuration=0;   // To hold watering duration in millis.
double maxWatering=5*60*1000;   // How many millis for max watering.

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "MyAuth";

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "MYSSID";
char pass[] = "MyPassword";

// Attach virtual serial terminal to Virtual Pin V1
WidgetTerminal terminal(V0);
BlynkTimer timer;
WidgetRTC rtc;

float lastMoistureAverage[sensorNum]={maxMoistureScore/2, maxMoistureScore/2, maxMoistureScore/2,maxMoistureScore/2,maxMoistureScore/2,maxMoistureScore/2,maxMoistureScore/2,maxMoistureScore/2};
float trimValue=0.2;
double lastLoopStart=0;
double currentTime=0;
double maxLoop=0;

// Digital clock display of the time
void clockDisplay()
{
  String my_h = MyPrintDigits(hour());
  String my_m = MyPrintDigits(minute());
  String my_s = MyPrintDigits(second());

  String currentTime = my_h+ ":"+my_m +":"+ my_s;
  String currentDate = String(day()) + "." + month() + "." + year();
  Serial.print("Current time: ");
  Serial.print(currentDate);
  Serial.print(" ");
  Serial.print(currentTime);
  Serial.println();

  // Send time to the App
  Blynk.virtualWrite(V9, currentTime);
  // Send date to the App
  Blynk.virtualWrite(V8, currentDate);
}

String timeNow() {
  String my_h = MyPrintDigits(hour());
  String my_m = MyPrintDigits(minute());
  String my_s = MyPrintDigits(second());
  String currentTime = my_h+ ":"+my_m +":"+ my_s;
return currentTime;
}

String dateNow() {
  String currentDate = String(day()) + "." + month() + "." + year();
return currentDate;
}

BLYNK_CONNECTED() {
  // Synchronize time on connection
  rtc.begin();
  Blynk.syncVirtual(V1, V5, V6);
}


void setup()
{
  Serial.begin(9600);

  Blynk.begin(auth, ssid, pass);

  pinMode(PUMP_RELAY, OUTPUT);
  pinMode(VALVE_RELAY, OUTPUT);
  
  zeroValue=computeZeroValue(dryValue, waterValue, dryBuffer, wetBuffer);
  wetValue=computeWetValue(dryValue, waterValue, dryBuffer, wetBuffer);
  // Clear the terminal content
  printDateTime();
  terminal.println("Blynk restarted.");
  terminal.flush();

  setSyncInterval(10 * 60); // Sync interval in seconds (10 minutes)
  // Display digital clock every 10 seconds
  timer.setInterval(10000L, clockDisplay);
  timer.setInterval(1000L, readSensors);
  timer.setInterval(BLYNK_SLOW_UPDATE, sendBlynkUpdates);
  double lastLoopStart=millis();
}

void printDateTime() {
  terminal.print(dateNow());
  terminal.print(" ");
  terminal.println(timeNow());
}

BLYNK_WRITE(V1)
// Bonsai plant - start watering
{
  int pinValue=param.asInt(); // assigning incoming value from pin V1 to a variable
  plantStartWateringLevel[0]= pinValue;
  plantStartWateringLevel[1]= pinValue; // assigning incoming value from pin V1 to a variable
  // Setting 2 sensors as Bonsai has 2 sensors.
  Serial.print("Bonsai starts watering below ");
  Serial.println(plantStartWateringLevel[0]);
  printDateTime();
  terminal.print("Start watering bonsai if below: ");
  terminal.println(plantStartWateringLevel[0]);
  //terminal.print(F("\n"));
  terminal.flush();

}

BLYNK_WRITE(V6)
// Bonsai plant - stop watering trigger level
{
  int pinValue=param.asInt(); // assigning incoming value from pin V1 to a variable
  plantStopWateringLevel[0]= pinValue; // assigning incoming value from pin V6 to a variable
  plantStopWateringLevel[1]= pinValue; // assigning incoming value from pin V6 to a variable
  Serial.print("Bonsai stops watering above ");
  Serial.println(plantStopWateringLevel[0]);
  printDateTime();
  terminal.print("Stop watering bonsai if above: ");
  terminal.println(plantStopWateringLevel[0]);
  terminal.flush();
  // Setting 2 sensors as Bonsai has 2 sensors.
}

BLYNK_WRITE(V2)    // Manually start watering Bonsai
{
  int pinValue = param.asInt(); // assigning incoming value from pin V2 to a variable
  if(pinValue==1) {
    startWatering(0);
    countValvesOpen=1;
  }
}

BLYNK_WRITE(V12)    // Emergency stop
{
  int pinValue = param.asInt(); // assigning incoming value from pin V12 to a variable
  if(pinValue==1 && countValvesOpen==1) {  // Only stop if valve actually open.
    stopWatering(0);
    countValvesOpen=0;
  }
}


BLYNK_WRITE(V7)   // Button to signal that water container has been refilled.
{
  int pinValue = param.asInt(); // assigning incoming value from pin V7 to a variable
  if(pinValue==1) {
    Serial.println("*** Water container refilled ***");
    printDateTime();
    terminal.println("Water container refilled. Resetting pumpDuration.");
    terminal.flush();
    pumpDuration=0;
  }
}


void loop()
{
  currentTime=millis();
  if ((currentTime-lastLoopStart)>maxLoop and lastLoopStart>0){
    maxLoop=currentTime-lastLoopStart;
  }
  lastLoopStart=currentTime;
  Blynk.run();
  timer.run();
}

void readSensors() {
  for (int i=0; i<(sensorNum); i++) {
  // If sensor pin present
    if (SensorPins[i]>0) {
      // Read pin and return moisture level.
      sensorValueOrig = analogRead(SensorPins[i]);
      moistureScore[i]=computeMoistureScore(sensorValueOrig);
      float currentMoistureAverage = filter(moistureScore[i], trimValue, lastMoistureAverage[i]);
      lastMoistureAverage[i] = currentMoistureAverage;
    }
  }
  // Loop through each sensor and check if need to start or stop pump
  for (int i=0; i<(sensorNum); i++) {
    if (SensorPins[i]>0) {    // Check if this pin has a sensor. 
      Serial.print("Checking sensor ");
      Serial.print(i);
      Serial.print(". Current level: ");
      Serial.print(moistureScore[i]);
      Serial.print(". Current level (int): ");
      Serial.print((int) moistureScore[i]);
      Serial.print(". Low trigger level: ");
      Serial.print(plantStartWateringLevel[i]);
      Serial.print(". High trigger level: ");
      Serial.println(plantStopWateringLevel[i]);
      if ((int) moistureScore[i]<plantStartWateringLevel[i] && countValvesOpen == 0) {
        startWatering(i);
      }
      if ((int) moistureScore[i]>plantStopWateringLevel[i] && countValvesOpen==1) { 
        // stopWatering only if you are currently watering.
        stopWatering(i);
      }
    }
  }
}

void sendBlynkUpdates() {
  #ifdef BLYNK_ON
    //Serial.println("Sending updates");
    Blynk.virtualWrite(V13, (int) maxLoop);  
    Blynk.virtualWrite(V5, pumpDuration);  
    //Serial.print("moistureScore[0] in sendBlynkUpdates:");
    //Serial.println(moistureScore[0]);
    for (int i=0; i<(sensorNum); i++) {
      switch (i) {
        case 0:
          Blynk.virtualWrite(V10, (int) moistureScore[i]);  
          break;
        case 1:
          Blynk.virtualWrite(V11, (int) moistureScore[i]);  
          break;
        case 2:
          break;
        case 3:
          break;
        case 4:
          break;
        case 5:
          break;
        case 6:
          break;
        case 7: 
          break;
      }
    }
  #endif
}

void startWatering(int i) {
  Serial.println("*** Valve Relay ON ***");
  digitalWrite(valvePin[i], HIGH);
  Serial.println("*** Pump Relay ON ***");
  digitalWrite(PUMP_RELAY, HIGH);          
  valveStartTime=millis();  // Store millis when start watering.
  countValvesOpen=1;
  printDateTime();
  terminal.print("Watering ");
  terminal.print(plantName[i]);
  terminal.println("...");
  terminal.flush();
  timer.setInterval(BLYNK_FAST_UPDATE, sendBlynkUpdates);
}

void stopWatering(int i) {
  Serial.println("*** Valve Relay OFF ***");
  digitalWrite(valvePin[i], LOW);
  Serial.println("*** Pump Relay OFF ***");
  digitalWrite(PUMP_RELAY, LOW);          
  valveDuration=millis()-valveStartTime;   // Store watering duration in millis.
  terminal.print("Stopping watering of ");
  terminal.print(plantName[i]);
  terminal.print(". ");
  terminal.print((valveDuration/1000));
  terminal.println(" secs.");
  pumpDuration+=(float) (valveDuration/60000);
  terminal.print("Watering mins since last refil: ");
  terminal.println(pumpDuration);
  terminal.flush();
  valveDuration=0;
  countValvesOpen=0;
  timer.setInterval(BLYNK_SLOW_UPDATE, sendBlynkUpdates);
}

// filter the current result using a weighted average filter:
float filter(float rawValue, float weight, float lastValue) {
  float result = weight * rawValue + (1.0-weight)*lastValue;
  return result;
}

float computeZeroValue(int dryValue, int waterValue, int dryBuffer, int wetBuffer) {
  float zeroValue=dryValue+((dryBuffer*(dryValue-waterValue))/(100-dryBuffer-wetBuffer));
  return zeroValue;
}

int computeWetValue(int dryValue, int waterValue, int dryBuffer, int wetBuffer) {
  int wetValue=waterValue-((wetBuffer*(dryValue-waterValue))/(100-dryBuffer-wetBuffer));
  return wetValue;
}

float computeMoistureScore(int reading) {
  float b=(float)zeroValue-(float)wetValue;
  float a=((float)reading-(float)wetValue)/b;
  float moistureScore=(1-a)*(float)maxMoistureScore;
  return moistureScore;
}

String MyPrintDigits(int digits) { // -- Utility function for digital clock display: prints preceding colon and leading 0
  String new_digits="";
  if (digits < 10) new_digits += "0";
  new_digits += String(digits);
  return new_digits;
}

Read the “keep your void loop clean” document I linked to earlier.

Pete.

Pete, sorry, I thought you were suggesting that that time calculation would, for some reason, not be a reliable calculation. Regarding the article, I have read that document multiple times. It encourages keeping loop() as lean as possible. It gives an example of how you can use timers to reduce the size of loop(). It does not say that loop MUST have only 2 lines in it for Blynk to work, just to keep it as short as possible.
The code that I currently have in loop(), on it’s own executes in 1-2 ms, so I think this would clearly meet the criteria in that article to be lean.
Is Blynk really unusable if loop() has more than 2 lines of code? Is that the conclusion you have from the article that you refer to? That would mean that there are no ways to sequentially process tasks, only to have them run after a given timer triggers them. Not all programming logic can be triggered sole after a predefined time period. This would seem to severely limit the usability of Blynk. I somehow find this hard to believe because it really is an appealing, well functioning solution.

Thanks for engaging on this topic, Pete, but I’m still wondering…

You’re trying to resolve an issue with disconnections, and its a proven fact that having unnecessary code in the void loop is likely to cause disconnections. Therefore, it makes sense to clean-up your void loop and do your testing that way, and if you solve the disconnection issue then you can (if necessary) add code back in to your void loop.
If you want to know how long your readSensors() function takes to execute then the best way of doing that is to grab the current millis() value at the start and end of the function, rather than forcing a mills calculation each time the void loop executes.

I don’t follow your logic on this one. All Arduino code execution is sequential, whether its run from the void loop, via a timed function, or via some other trigger.

No, but the use-case for Blynk is usually one where you use hardware interrupts or BLYNK_WRITE(vPin) callbacks, which are effectively software interrupts.
If you want to monitor a serial port for incoming data, or look for an RFID tag to be presented to a reader then it is sometimes necessary to have additional code in the void loop, but in most other situations you can poll sensors, actuators etc via a timer.

But Blynk is what it is, an IoT system aimed at providing device control and showing sensor data via a mobile app. For that use-case it works very well, provided you area careful with your coding.

I personally don’t use Blynk in the same way as most people, as I don’t run any Blynk code on my devices so I don’t have to be as careful with my coding; but I still keep my void loop clean because it’s good practice.

Have you considered that there may be other reasons for you disconnections, such as low WiFi signal strength , internet dropouts etc? Adding some re-connection code into your sketch might be worthwhile.

Pete.

Hi, disconnection in a Wireless environment can happen for different things.
At least it may depend on a defective hardware, or a noised environment.

I suggest you, to check if your device really lost Wifi connectivity, using a disconnection handler.

You can for example to start blynking a led (use timers) or, if you keep the serial connection, use Serial object to print info. You can also use the disconnection handler to clean things or at least reset the ESP if for example the disconnection time is high than a threshold.

Is the opposite of BLYNK_CONNECTED macro.

# globals
int networkStatus = WL_DISCONNECTED;
bool isFirstConnection = true;

# disconnection event
WiFiEventHandler mDisConnectHandler;

# forward declaration of disconnection function
void onDisconnect(const WiFiEventStationModeDisconnected& event);

# In blynk connected macro
BLYNK_CONNECTED() {

  ...
  networkStatus = WL_CONNECTED;
  
  if (!isFirstConnection) {
     /* things to do on first connection */
    isFirstConnection = false:
 }
 /* things to do on every connection */
}

# Then in setup add following code after the connection routine
void setup() {
...
  mDisConnectHandler = WiFi.onStationModeDisconnected(onDisconnect);
...
}

# Then define the disconnection handler
void onDisconnect(const WiFiEventStationModeDisconnected& event) {
  (void)event;
   networkStatus = WL_DISCONNECTED;
   # task to do when disconnected
  Serial.println("Wifi lost");
  # reboot();
}

Not sure if this may be the case here, but in some programing languages it matters where you put certain commands.
You have Blynk.run at the beginning of the loop, would it make a difference if you put it at the end?
If I am way out in left field with this, its because I am, but you got to love me for trying. :smiling_face_with_three_hearts:

Hi
To use Blynk effectively you have to change the way you think of programming as a sequential flow. Blynk uses a what is termed a Publish and Subscribe programming model the mobile Blynk app publishes messages and your Arduino application subscribes to them. The Blynk server is a Brooker that receives messages from the mobile app and forwards them to the appropriate subscriber in this case your Arduino application. What this means is that under the hood the Blynk library is looking for any messages that it has subscribed to and you need to process that message and act on it accordingly.

I do not know how this Publish/Brooker/Subscriber model has been implement, I suspect that is what is termed a publish and forget, i.e. it sends a message and does not care if it has been received. This means that if you are doing other tasks in your code and are not ready to receive a message then you Blynk.write will not be processed hence the need to keep the loop empty and put all your code in the Blynk.write routine and ensure that you are not publishing messages quicker than your Arduino code can handle.

The best way of thinking about this is lots of sperate routines (Blink.write) each of which is initiated by the mobile app. However what makes life more difficult is that the Blynk mobile app does not provide the way to control the order and when the commands to initiate these ‘routines’ should be published so that you have to use the timer function instead and the Blynk command just starts or stops the timer actions.

As noted the above is not exactly how Blynk works, as I do not know the details, but if you think along these lines then this will help building a robust application.