Virtual pins stopped syncing

I am using an ESP32 Dev Module with the code below, which worked absolutely fine until the last week (beginning of September 2023).

I am using the four virtual PINS V0, V2, V1 and V9. The device stopped reading the V0 and V2 values, but it can still read V1 and V9.

#include <HardwareSerial.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <esp_bt.h>
#include <esp_wifi.h>
#include <BlynkSimpleEsp32.h>
#include <DHT.h>
#include <MAX44009.h>
#include <TimeLib.h>                                    /* Program code related to Real Time Clock (RTC). */
#include <WidgetRTC.h>                                  /* Communication code with Blynk Real Time Clock Widget */
#include <SPI.h>
#include "driver/adc.h"

#define DHT_I_PIN 27
#define PIN_I_VOLTAGE 34
#define PIN_I_RELAY 33
#define POWER_O_PIN1 32
#define POWER_O_PIN2 14
#define SOIL_I_PIN 35
#define V_PIN_I_LIGHT V1
#define V_PIN_O_WATER V2 //This is input data from Blynk for the watering modes: 0 is no watering, 1 is auto mode, 2 always water
#define V_PIN_O_SOIL_DRY V0 //This is to define the level at which to start watering automatically: 50 to 70, recommended is 60.
#define V_PIN_I_TEMP V3
#define V_PIN_I_HUMID V4
#define V_PIN_I_SOIL V5
#define V_PIN_I_VOLTAGE V6
#define V_PIN_I_RELAY V7
#define V_PIN_IO_TERMINAL V8
#define V_PIN_O_WAKE_TIME V9
#define DRY_O_PIN 26 //DRY means the soil is dry and needs watering
#define WET_O_PIN 25 //WET means the soil is wet and does not need watering
#define LED_PIN 22
#define SERIAL_BAUD 9600
#define AIR_VALUE 3500 //DF ROBOT cool sensor last calibrated on 29.10.2021 this is a value from a dry soil top
#define WATER_VALUE 370 //this is value of water, but equal to soil from our garden watered well
#define DELAY 20 //Delay of timer in sec per hour
#define DHTTYPE DHT22
#define I2C_SDA 23
#define I2C_SCL 19
#define LOW_VOLTAGE 2750
#define SLEEP_INT 1800
#define SOIL_DRY_VAL 60
#define BLYNK_PRINT Serial

BlynkTimer timer;                                       /* Define parameter for Blynk Timer */
WidgetRTC rtc;                                          /* Define parameter for RTC Widget */
DHT dht(DHT_I_PIN, DHTTYPE);                            /* Define the DHT, MAX and Blynk terminal environment */
MAX44009 Lux(0x4A);
WidgetTerminal terminal(V_PIN_IO_TERMINAL);
RTC_DATA_ATTR int wake_count = 0;                       /* Initialise wake count in the slow memory */

uint8_t water_on = 1, soil_dry = SOIL_DRY_VAL;          /* The water_on is parameter set up in Blynk manually, 0 - no watering, 1 - auto watering, 2 - always on; 
                                                           soil_dry is at what default value watering starts: default is 60*/
uint16_t checkst = 0;                                   /* This parameter and CheckTime used to check if the sent value is correctly reflected in Blynk, if not communcation repeated*/
String CheckTime;

class SoilMoist{
  struct SensorsInput{
    uint16_t s_moist, voltage, relay, error_dht;
    float t = 0, h = 0, lux = 0;
  } Sensors;
  
  public: 
  SoilMoist();
  void Sensors_Read(void);
  void Blynk_IO(void);
  void Switch_Relay(void);
  int SleepPause(void); 
  int get_Relay(void);
};

SoilMoist::SoilMoist(){
  Sensors.s_moist = 0;
  Sensors.voltage = 0;
  Sensors.relay = 0;
  Sensors.error_dht = 0;
  Sensors.t = 0;
  Sensors.h = 0;
  Sensors.lux = 0;
}

int SoilMoist::get_Relay(void){
  return(Sensors.relay);
}

void callback(){
}
    
void setup() {
    int SleepInterval = SLEEP_INT;
    static SoilMoist SoilM;
   
    btStop();
    esp_bt_controller_disable();
    adc_power_on();
    
    Serial.begin(SERIAL_BAUD);
    pinMode(DHT_I_PIN, INPUT);
    pinMode(SOIL_I_PIN, INPUT);
    pinMode(PIN_I_VOLTAGE, INPUT);
    pinMode(PIN_I_RELAY, INPUT);
    pinMode(DRY_O_PIN, OUTPUT);
    pinMode(WET_O_PIN, OUTPUT);
    pinMode(POWER_O_PIN1, OUTPUT);
    pinMode(POWER_O_PIN2, OUTPUT);
    pinMode(V_PIN_I_LIGHT, INPUT);
    pinMode(V_PIN_I_TEMP, INPUT);
    pinMode(V_PIN_I_HUMID, INPUT);
    pinMode(V_PIN_I_SOIL, INPUT);
    pinMode(LED_PIN, OUTPUT);
    Wire.begin(I2C_SDA, I2C_SCL);
    
    //Activate LED, power pins, light sensor and the DHT sensor (temperature and humidity)
    digitalWrite(LED_PIN, LOW); //Strangely LOW means light up the LED
    digitalWrite(POWER_O_PIN1, HIGH); //Power on sensors DHT and Soil
    digitalWrite(POWER_O_PIN2, HIGH);
    Lux.Begin(0, 188000);
    dht.begin();
    //Wait 200 milisconds until sensors are activated
    delay(200);
    
    SoilM.Sensors_Read();
        
    digitalWrite(POWER_O_PIN1, LOW);  //Power off sensors
    digitalWrite(POWER_O_PIN2, LOW);
    
    Connect_Blynk();

    if(Blynk.connected()){
       SoilM.Blynk_IO(); //If connected transfer data to Blynk and read the 'Watering override' option       
    }
    else{
       water_on = 1; //If not connected just rely on automatic watering
       soil_dry = SOIL_DRY_VAL; //If not connected use this default level of soil dryness
    }
    
    SoilM.Switch_Relay(); //Switch relay according to sensor data and Blynk 'Watering override' option
    
    if(Blynk.connected()){
       SleepInterval = SoilM.SleepPause();
       //Serial.println("\nNext time wake up in"+ (String)(SleepInterval/60) + " min");
       terminal.flush();
       if(SoilM.get_Relay() < 1000) {
          Blynk.virtualWrite(V_PIN_I_RELAY, 0);   //Write the state of relay, relay => 1000 means the water is ON       
       }
       else {
          Blynk.virtualWrite(V_PIN_I_RELAY, 1);
       }
       delay(250);   //This delay is to let the terminal to flush
    }
        
    wake_count++; //increment wake counter
            
    WiFi.disconnect();
    WiFi.mode(WIFI_OFF);
    adc_power_off();
    esp_wifi_stop();
    digitalWrite(LED_PIN, HIGH);
    
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_12,1);
    esp_sleep_enable_timer_wakeup(1000000ULL * SleepInterval);
    esp_sleep_pd_config(ESP_PD_DOMAIN_MAX, ESP_PD_OPTION_OFF);
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_AUTO);
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_AUTO);
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
    esp_deep_sleep_start();
}

void SoilMoist::Sensors_Read() {
  int loops = 1;
  int soil_moisture = 0, voltage = 0, relay = 0, read_att = 0, error_dht = 0;
  float temp = 0, humidity = 0, lux = 0;
        for (loops = 1; loops <= 2; loops++){
          soil_moisture = constrain(map(analogRead(SOIL_I_PIN), AIR_VALUE, WATER_VALUE, 0, 100), 0, 100);
          temp = dht.readTemperature();
          humidity = dht.readHumidity();
          voltage = analogRead(PIN_I_VOLTAGE);
          relay = analogRead(PIN_I_RELAY);
          lux = Lux.GetLux();
          digitalWrite(LED_PIN, HIGH);
          delay(1000);
          digitalWrite(LED_PIN, LOW);
          delay(1000);   
          read_att = 0;
          while(isnan(temp)){
                error_dht = 1;
                if((read_att++) > 10) {
                   temp = 20;
                   break;
                }
                temp = dht.readTemperature();
                delay(200);
          }
          read_att = 0;
          while(isnan(humidity)){
                error_dht = 1;
                if((read_att++) > 10) {
                   humidity = 50;
                   break;
                }
                humidity = dht.readHumidity();
                delay(200);
          }
          Sensors.s_moist += soil_moisture;
          Sensors.t += temp;
          Sensors.h += humidity;
          Sensors.voltage += voltage;
          Sensors.lux += lux;
          Sensors.error_dht += error_dht;
          } 
   Sensors.s_moist = Sensors.s_moist/(loops - 1);
   Sensors.t = Sensors.t/(loops - 1);
   Sensors.h = Sensors.h/(loops - 1);
   Sensors.voltage = Sensors.voltage/(loops - 1);
   Sensors.relay = relay;
   Sensors.lux = Sensors.lux/(loops - 1);
}

void SoilMoist::Switch_Relay() {
   if(Sensors.s_moist > soil_dry && Sensors.relay >= 1000 && water_on == 1){
          dacWrite(WET_O_PIN, 255);
          delay(200);
          dacWrite(WET_O_PIN, 0);
   }
   else if(Sensors.s_moist <= soil_dry && Sensors.relay < 1000 && water_on == 1){
          dacWrite(DRY_O_PIN, 255);
          delay(200);
          dacWrite(DRY_O_PIN, 0);            
   }
   else if(water_on == 0 && Sensors.relay >= 1000){
          dacWrite(WET_O_PIN, 255);
          delay(200);
          dacWrite(WET_O_PIN, 0);
   }
   else if(water_on == 2 && Sensors.relay < 1000){
          dacWrite(DRY_O_PIN, 255);
          delay(200);
          dacWrite(DRY_O_PIN, 0);  
   }
   Sensors.relay = analogRead(PIN_I_RELAY);
}

void Connect_Blynk() {
   int connect_at = 0;
   char ssid[] = "xxxxx";
   char pass[] = "xxxxx";
   //Initialisation of WiFi and connection to Blynk
   WiFi.mode(WIFI_AP_STA);
   Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
   while (!Blynk.connected()) {
          delay(500);
          if(connect_at++ > 15){ // try for less than 16 attempts to connect to Blynk
             break;
   }}
   if(Blynk.connected()){
    rtc.begin();
   } 
   else{Serial.println("Was not able to connect to Blynk");}
}

void SoilMoist::Blynk_IO() {
    int att_num = 0;
    unsigned long TimerStart = 0;
        
    if(wake_count % 10 == 0) {terminal.clear();} //clearing terminal if the cycle is a multiple of 10
    //transferring the data to the terminal
    terminal.print("\nWake count = "+ (String)wake_count);
    terminal.print("\nTemp = "+ (String)Sensors.t + "C");
    terminal.print("\nHumidity = "+ (String)Sensors.h + "%");
    if(Sensors.error_dht) terminal.print("\n!!WARNING!! There were " + (String)Sensors.error_dht + " error(s) reading DHT sensor!");
    terminal.print("\nSoil moisture: " + (String)Sensors.s_moist + "%");
    terminal.print("\nLux = "+ (String)Sensors.lux + "L");
    terminal.print("\nVoltage = "+ (String)Sensors.voltage);
    terminal.print("\nRelay = "+ (String)Sensors.relay);
    
    //the loop tries to send data to Blynk 20 times with interval of 200 ms
    while(floor(Sensors.lux) != checkst){
          if((att_num++)>20) {break;}
          Blynk.syncVirtual(V_PIN_O_WATER);
          Blynk.syncVirtual(V_PIN_O_SOIL_DRY);
          Blynk.virtualWrite(V_PIN_I_LIGHT, Sensors.lux);
          Blynk.virtualWrite(V_PIN_I_VOLTAGE, Sensors.voltage);
          Blynk.virtualWrite(V_PIN_I_SOIL, Sensors.s_moist);
          Blynk.virtualWrite(V_PIN_I_TEMP, Sensors.t);
          Blynk.virtualWrite(V_PIN_I_HUMID, Sensors.h);
          Blynk.syncVirtual(V_PIN_I_LIGHT);
          TimerStart = millis();
          while(millis() - TimerStart < 200) {Blynk.run();}
    }
    if(att_num > 1) terminal.print("\n!!WARNING!! There were " + (String)att_num + " attempts to send data to Blynk!");
}    

int SoilMoist::SleepPause() {
  int att_num = 0;
  int SleepAdd = 0, WakeUp = 0;
  unsigned long TimerStart = 0;
  char WakeUpTime[60], LastLogin[9];
       /* Define "currentTime" by combining hour, minute and second */
       float GetHour = hour() + (float)minute()/60 + (float)second()/3600;
       if ((GetHour >= 21 && GetHour < 24)||(GetHour >=0 && GetHour <4.75)) {
          SleepAdd = 3 * 3600;}
       else if(GetHour >=4.75 && GetHour < 7.63) {
          SleepAdd = (7.75 - GetHour) * 2 * SLEEP_INT;
       }
       else if(GetHour >=7.63 && GetHour < 9) {
          SleepAdd = SLEEP_INT;
       }
       else if(GetHour >=9 && GetHour < 17.75) {
          SleepAdd = 4 * SLEEP_INT;
       }
       else if(GetHour >=17.75 && GetHour < 19.63) {
          SleepAdd = (19.75 - GetHour) * 2 * SLEEP_INT;
       }
       else if(GetHour >=19.63 && GetHour < 21) {
          SleepAdd = SLEEP_INT;
       }
  if ((GetHour + SleepAdd/3600) >= 24) {GetHour -= 24;}
  WakeUp = GetHour * 3600 + SleepAdd;
  sprintf(WakeUpTime, "Wake %02d:%02d:%02d T=%.1fC H=%.0f%% S=%d%% L=%.1fL", WakeUp / 3600, WakeUp / 60 - (WakeUp / 3600) * 60, WakeUp % 60, Sensors.t, Sensors.h, Sensors.s_moist, Sensors.lux);
  sprintf(LastLogin, "%02d:%02d:%02d\0", hour(), minute(), second());
  WakeUp += DELAY * SleepAdd / 3600;
  while(CheckTime!=(String)WakeUpTime){
          if((att_num++)>20) {break;}
          Blynk.virtualWrite(V_PIN_O_WAKE_TIME, WakeUpTime);
          Blynk.syncVirtual(V_PIN_O_WAKE_TIME);
          TimerStart = millis();
          while(millis() - TimerStart < 100) {Blynk.run();}
  }
  if(att_num > 1) terminal.print("\n!!WARNING!! There were " + (String)att_num + " attempts to send timestamp to Blynk!");
  terminal.print("\nWatering starts at = " + (String)soil_dry);
  terminal.print("\nTimestamp " + (String)LastLogin);
  return(SleepAdd);
}

BLYNK_WRITE(V_PIN_O_WATER)
{
  water_on = param.asInt(); // Get value as integer
}

BLYNK_WRITE(V_PIN_O_SOIL_DRY)
{
  soil_dry = param.asInt(); // Get value as integer
}

BLYNK_WRITE(V_PIN_I_LIGHT)
{
  checkst = param.asInt(); // Get value as integer
}

BLYNK_WRITE(V_PIN_O_WAKE_TIME)
{
  CheckTime = param.asStr(); // Get value as integer
}

BLYNK_CONNECTED()
{
}

void loop() {
while (Blynk.connected()) 
      {
        Blynk.run();
        timer.run();        
      }

Your code is very odd!

Why are you doing this…

Your code uses DeepSleep, but it’s structured very badly for a DeepSleep situation with Blynk. If you want to retrieve values from the Blynk server during a wake period then it’s usually best to put the majority of your code in BLYNK_CONNECTED().

Also, none of your connection management code makes any sense, because you’re using Blynk.begin() rather than managing your own WiFi connection and using Blynk.config() and Blynk.connect().
Blynk.begin is a blocking function, so if it can’t connect to WiFi or the Blynk server then all code execution will stop here:

And your device will never enter Deep Sleep, and you’ll simply drain your battery.
This code:

is totally redundant, because it will only execute if Blynk.begin() succeeds in creating a connection to WiFi and Blynk (Blynk.connected() == true).

The same applies to the other code that checks Blynk.connected()

I suspect that the syncing of some virtual pins stopped working recently because of some server-side optimisation changes that were made. My guess is that the server doesn’t like your repeated sync/write requests.

Pete.

Dear PeteKnight, thank you for your reply!

  1. The code started to get data today in the morning, which means Blynk server had problems

  2. My code worked for more than 2 years with no issues. That of course does not mean it is perfect, and I am ready to amend it. I must admit the code does not look optimal, but the oddity of the code is more an indication of issues with the Blynk service reliability. I had to use the loop since Blynk server very frequently is not available, or somehow ignores data sent and the code pushes data several times until it reaches the server. To check if the data reached Blynk the code sends a random integer value to the V_PIN_I_LIGHT virtual pin and then recalls it and compares with the value it has sent. If I do not use this loop in many cases Blynk does not get the data. I tested this approach while the device was constantly connected to internet. It is not an issue of WiFi hence, but an issue of Blynk. If you know better choice to get around this problem let me know. In my opinion Blynk service should have thought about error controlling / data packages check sums and should have implemented this in the Blynk library. Let me know if this is the case and what operators to use to check for errors.

  3. Will it make a real difference if I put most of the code into Blynk_connected()? A good programming practice is that you combine functional sections of a code into specific functions, rather than heap everything into a one common function. As I understand any virtualWrite and syncVirtual operators I use inside separate functions is equivalent to putting it in the Blynk_connected(). The problem with Blynk_connected() is I am not able to regulate timings. After the device wakes up it collects data from sensors, gets the data from Blynk, tries to act according to the data, then reports back to Blynk. If I put most of code into Blynk_connected() how do I control the sequence and timing?

  4. WiFi connection is not an issue. I already tested it multiple times. If the device is not able to reach Blynk for 15 times (whatever reason is), the device goes into sleep mode and wakes again in 1.5 hours and then tries again. Of course I will explore Blynk.config() and Blynk.connect() and will try to rewrite the code accordingly. I have never had problems with the device not going into sleep or draining battery with the code.

Could we please nevertheless concentrate on the main question: How is it possible that the device gets data from Blynk for some virtual pins with VirtualSync, but for some not and then the next morning everything works again. The code is able to send text to Blynk terminal and also gets time stamp from the first attempt, but not the data from the two virtual pins. So it is not a problem of the connection per se, but a problem of the reliability of the data exchange. I checked the project online: both app and the web interface are synchronized and are able to set the virtual pins. If necessary I am ready to write a simplest code to avoid your confusion. The fact is: Blynk works instable. To solve the problem a user should be able to get a protocol of data transmission to a Blynk server with a checksum. How do I do this? Are there any other ways to get signals about the errors?

I think it’s more about your lack of understanding about how the Blynk library works - which is quite confusing, but once you understand these peculiarities it’s easy to work with and get consistent results.

If it wasn’t available then code execution would halt at Blynk.begin()
I think the issue is the fact that you don’t use BLYNK_CONNECTED() and probably don’t understand the issues around the difference between Blynk.begin() completing and the Blynk server responding to Sync requests and virtual writes - which is where BLYNK_CONNECETD() comes into its own.

Use BLYNK_CONNECTED()

The rest of your comments are talking about situations where you refuse to use BLYNK_CONNECTED() or refuse to recognise that Blynk.begin() is a blocking function.

As far as your final paragraph is concerned, I suspect that your code is extremely marginal, and will work some of the time, with the new Blynk server configuration, but not all of the time.
Blynk has always been very clear that you should never send more than 10 commands per second, and recent changes seem to be enforcing that rule.

If you’d like to provide a list of the high-level steps that your code needs to perform then I’d be happy to provide some input about the best way to structure your code to achieve this.

Pete.

ok, sounds good, I will rewrite the code and won’t use Blynk.begin but will pack some of the code into the BLYNK_CONNECTED(). Interestingly my code continues to work well now as for the last two years. It is clearly logical not to change something which has been working for long time well.

Dear PeteKnight,

I created a simple code (see below) to simplify our discussion and let you spare words on the quality of the first code I provided to you.

First, I am using only Blynk.config() but not the Blynk.begin() anymore. Strangely many still use Blynk.begin(). If it is ‘blocking’ why Blynk has not deprecated it? Anyways, I followed your advise.

Second, I send the data not more frequently than ten commands per second since I separate all the data IO cycles with delay of at least 500ms and run only two commands in the new code.

Third, I tried to put the Blynk.virtualWrite() as well as Blynk.syncVirtual inside Blynk_connected(), but it only wrote the data once and not in the second cycle. I assume your comment was relevant only for the first code, where the data exchange should only run once per wake cycle. Hence I put the code in the void() function.

Forth, my new code controls for errors and creates a protocol if Blynk server wasn’t able to synchronize the value. The first error appeared in 3 min 20 s. See the protocol below.

Why do you think the data transfer error appeared so soon (after I sent only 400 short integer values) and how do I avoid such errors? If you look into my old code I intentionally created loops with delays to send data until Blynk gets and resends the data correctly. You ostensibly didn’t like my first code, so I would appreciate if you may give me an alternative solution for the error correcting problem.

My new code is:

#define BLYNK_TEMPLATE_ID           "xxx"
#define BLYNK_TEMPLATE_NAME         "xxx"
#define BLYNK_AUTH_TOKEN            "xxx"

#include <WiFi.h>
#include <WiFiClient.h>
#include <esp_wifi.h>
#include <BlynkSimpleEsp32.h>
#include <TimeLib.h>                                    /* Program code related to Real Time Clock (RTC). */

#define V_PIN_I_LIGHT V1
#define SERIAL_BAUD 115200

uint16_t checkst = 0;                                   /* This parameter and CheckTime used to check if the sent value is correctly reflected in Blynk, if not communcation repeated*/
String CheckTime;
long randN;
BlynkTimer timer;                                       /* Define parameter for Blynk Timer */
int error_count = 0;

void setup() {
  unsigned long TimerStart = 0;
  TimerStart = millis();
  Serial.begin(SERIAL_BAUD);
  delay(10);
  char ssid[] = "xxx";
  char pass[] = "xxx";
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, pass);
  int wifi_ctr = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected"); 
  Blynk.config(BLYNK_AUTH_TOKEN); // put your setup code here, to run once:
  while(millis() - TimerStart < 2000) {Blynk.run();}
}

void loop() {
  unsigned long TimerStart = 0;
  char LastLogin[9];
  TimerStart = millis();
  randN = random(5000);
  Serial.println("Sending the random number to the server " + (String)randN);
  Blynk.virtualWrite(V_PIN_I_LIGHT, randN);
  delay(50);
  Blynk.syncVirtual(V_PIN_I_LIGHT);  
  delay(50);
  while(millis() - TimerStart < 500) {
    Blynk.run();
    }
  if(checkst == randN) Serial.println("Sending data was successful! Error count = " + (String)error_count);
  else {
  error_count++;
  sprintf(LastLogin, "%02d:%02d:%02d\0", hour(), minute(), second());
  Serial.println("The was an error writing data: randN = " + (String)randN + " checkst = " + (String)checkst + " at " + (String)LastLogin);
  }
}

BLYNK_WRITE(V_PIN_I_LIGHT)
{
  checkst = param.asInt(); // Get value as integer
}

BLYNK_CONNECTED()
{
}

The terminal output with the error:

18:09:27.760 -> Sending data was successful! Error count = 0
18:09:27.760 -> Sending the random number to the server 641
18:09:28.262 -> Sending data was successful! Error count = 0
18:09:28.262 -> Sending the random number to the server 3265
18:09:28.762 -> Sending data was successful! Error count = 0
18:09:28.762 -> Sending the random number to the server 2944
18:09:29.274 -> Sending data was successful! Error count = 0
18:09:29.274 -> Sending the random number to the server 4140
18:09:29.760 -> Sending data was successful! Error count = 0
18:09:29.760 -> Sending the random number to the server 3385
18:09:30.270 -> The was an error writing data: randN = 3385 checkst = 4140 at 00:03:20
18:09:30.270 -> Sending the random number to the server 2506
18:09:30.764 -> Sending data was successful! Error count = 1
18:09:30.764 -> Sending the random number to the server 3979
18:09:31.247 -> Sending data was successful! Error count = 1
18:09:31.247 -> Sending the random number to the server 3704
18:09:31.760 -> Sending data was successful! Error count = 1
18:09:31.760 -> Sending the random number to the server 810
18:09:32.274 -> Sending data was successful! Error count = 1
18:09:32.274 -> Sending the random number to the server 1969
18:09:32.747 -> Sending data was successful! Error count = 1
18:09:32.747 -> Sending the random number to the server 269
18:09:33.282 -> Sending data was successful! Error count = 1
18:09:33.282 -> Sending the random number to the server 3872
18:09:33.783 -> Sending data was successful! Error count = 1
18:09:33.783 -> Sending the random number to the server 2981
18:09:34.276 -> Sending data was successful! Error count = 1
18:09:34.276 -> Sending the random number to the server 3701
18:09:34.777 -> Sending data was successful! Error count = 1
18:09:34.777 -> Sending the random number to the server 3787
18:09:35.276 -> Sending data was successful! Error count = 1
18:09:35.276 -> Sending the random number to the server 4602
18:09:35.742 -> Sending data was successful! Error count = 1
18:09:35.778 -> Sending the random number to the server 3726
18:09:36.286 -> Sending data was successful! Error count = 1
18:09:36.286 -> Sending the random number to the server 2863
18:09:36.765 -> Sending data was successful! Error count = 1
18:09:36.765 -> Sending the random number to the server 685
18:09:37.282 -> Sending data was successful! Error count = 1
18:09:37.282 -> Sending the random number to the server 3033
18:09:37.765 -> Sending data was successful! Error count = 1

@dmitryphd Please edit your post, using the pencil icon at the bottom, and add triple backticks at the beginning and end of your code, and your serial output, so that they display correctly.
Triple backticks look like this:
```

Copy and paste these if you can’t find the correct symbol on your keyboard.

Pete.

Done, I was looking for the quote but wasn’t able to find them.

If you want your device to be controlled by Blynk then blocking until a connection is established isn’t a bad approach. It’s just that its not the best approach if you’re trying to use a battery powered device that uses deepsleep.

I don’t really understand what you are saying here.
I also don’t understand what you’re trying to achieve with this sketch. I thought you were trying to…

  • have your device wake-up
  • retrieve values from the Blynk server
  • use these values to perform calculations on your data
  • send the results to the Blynk server
  • go back to sleep.

This goes back to the issue of you not understanding how Blynk.suncVirtual works. Simply calling Blynk.syncVirtual(vPin) doesn’t instantly trigger the corresponding BLYNK_WRITE(vPin) callback. You request is sent via your Wifi network to your router, across the internet to the Blynk server, back across the internet, router and WiFi then is received and processed by your device when Blynk Blynk.rin is executed.
Most of the time this will happen very quickly, but the time it takes will vary depending on the traffic on your network, your ISP, every data hop that is made across the internet, the current load on the Blynk server, and the way that your code services the Blynk library to process the incoming data.
This is why I said that your approach is “extremely marginal” - sometimes it will work as you expect, but other times external factors such as those mentioned above will result in your code not working as expected, because all of the required elements didn’t align perfectly on that one occasion.

If you use callback functions that are triggered by incoming data from the Blynk server - such as BLYNK_CONNECTED or BLYNK_WRITE(vPin) to trigger the next part of your code, rather than randomly selected loop[ processes (which use blocking delays and therefore block the Blynk library from executing) then you may get better results. However, it depends exactly what it is that you are trying to achieve with this latest sketch.

Pete.

Dear PeteKnight,
Thank you for your prompt reply. I am surprised that you replied on Sunday’s night.

This point is clear, strangely this approach worked with the device and never blocked it from sleep. I tested it multiple times by turning off my router, or during Blynk server downtimes. It worked as expected and never blocked the sleep mode.

I try to simplify the task and address the issues one by one. With the simple sketch I want to send a random number to Blynk and immediately retrieve it in one cycle, doing the cycle multiple times to test if this works error free. It does not. You provided the partial answer to this. I assume that since sometimes the Blynk server is busy or it takes longer to reach it, instructions of Blynk_VirtualWrite or Blynk.syncVirtual would not execute (not enough time or data transmission errors) even though I try to apply Blynk.run multiple times in a cycle.

I understand why you mean you do not understand. Now I understood that the Blynk_Connected is the callback function and when it is triggered.

As described above I want to make sure that the data gets to Blynk and back correctly in every cycle (without using sleep mode). How would you achieve this? With Blynk_write()? What would you put in it to make sure that the data retrieved? Is there any function that returns an error code if Blynk.run wasn’t able to send and/or retrieve data? I would do without delay() if there was another simple way.

I’m retired, so don’t have to worry about being up in time for work on Monday morning :wink:

I think you’re chasing a non-existent problem here. I understand why you think that this is necessary, but the problems you were experiencing were caused by flawed code, not a flaw in the Blynk server/library functionality.

The solution to your latest goal of performing a looped process with no deepsleep needs to look very different to the approach that should be used with a one-shot deepsleep sketch, so you won’t learn anything from this, or develop any skills or knowledge that will help with your ultimate goal, But, this is how I’d structure a simple looping test (note that tis is written on my iPad , so hasn’t been compiled or tested, so there may be syntax errors etc)…

#define BLYNK_TEMPLATE_ID           "TMPxxxxxx"
#define BLYNK_TEMPLATE_NAME         "Device"
#define BLYNK_AUTH_TOKEN            "YourAuthToken"

#define BLYNK_PRINT Serial
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>

char ssid[] = "YourNetworkName";
char pass[] = "YourPassword";

bool sendCycle = true;  // used to alternate between sending and retrieving data
int randN;

BlynkTimer timer;

void setup()
{
  Serial.begin(115200);
  Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
  timer.setInterval(200L, sendOrRetrieve);
}

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

void sendOrRetrieve()
{
  if(sendCycle)
  {
    sendRandomNumber();
  }
  else
  {
   retrieveServerValue();
  }
}

void sendRandomNumber()
{
  randN = random(5000);
  Serial.println("Sending the random number to the server " + (String)randN);
  Blynk.virtualWrite(V1,randN);
  sendCycle=false;
}

void retrieveServerValue()
{
  Blynk.syncVirtual(V1);
}

BLYNK_WRITE(V1)
{
  int result = param.asInt();
  Serial.println("Value retrieved from the server = " + (String)result);

  if(randN  <> result)
  {
     Serial.println("*********************** ERROR the two values do not match ***********************" );
  }
  
  sendCycle=true;
}

Pete.

1 Like

Dear PeteKnight,

The code works well, I will tested it and compare to another code I made without using delay() function. I understood your idea. The key component of your solution is the timer.setInterval function. Do I understand this correctly that this function only runs on the local processor and if there is no several processors/cores, it will be executed in the linear order with other commands? This means that if instead of this function I use a while (time<interval) cycle and Blynk.run() inside of it, it should have the same result as your solution.

Using while() with Blynk is a very bad idea, as it also blocks code execution until the while argument evaluates to true.

BlynkTimer is a variation on SimpleTimer and they are both an elegant non-blocking way to compare elapsed time (using the millis() function) and executing processes when they next become due.

But, as I said before, none of this timed or looped operation is relevant if you want to use deepsleep, so modifying the code to work differently achieves nothing in terms of your ultimate goal. The code is simply a tool to prove to you that there isn’t a Blynk server issue, and that the issue you believed to exist was caused by badly structured code.

Pete.

Dear PeteKnight,

I only started testing today and after 1.5 hours there was an error:


11:58:00.306 -> Sending the random number to the server 1709

11:58:00.512 -> Value retrieved from the server = 1709 error_count = 0

11:58:00.592 -> Sending the random number to the server 4761

11:58:00.749 -> Value retrieved from the server = 4761 error_count = 0

11:58:00.894 -> Sending the random number to the server 2263

11:58:01.194 -> Value retrieved from the server = 2263 error_count = 0

11:58:01.353 -> Sending the random number to the server 2706

11:58:01.385 -> Value retrieved from the server = 2263 error_count = 0

11:58:01.385 -> *********************** ERROR the two values do not match ******************* error_count = 1

11:58:01.496 -> Sending the random number to the server 1253

11:58:01.688 -> Value retrieved from the server = 1253 error_count = 1

11:58:01.813 -> Sending the random number to the server 2933

11:58:01.974 -> Value retrieved from the server = 2933 error_count = 1

11:58:02.101 -> Sending the random number to the server 2505

11:58:02.246 -> Value retrieved from the server = 2505 error_count = 1

11:58:02.389 -> Sending the random number to the server 1754

11:58:02.549 -> Value retrieved from the server = 1754 error_count = 1

This means exactly what I meant in the beginning: Blynk service is not reliable.
I see two possible ways to control for transmission errors: 1) use a similar algorithm you labeled ‘marginal’ but improve it 2) use a solution proposed by Blynk, e.g. a specific function from Blynk library, which reliably controls data transmission and returns an error if transmission is not successful.

I cannot imagine the other way as a ‘while’ loop or similar that SimpleTimer uses. There should be a condition checking if it is time to execute something and an infinite loop, while the function is running. If this is the case, then my ‘alternative’ solution is better, since it does not require an additional library. Let me know if I’m missing something.

I look forward to any proposals from your side to control for data transmission errors.

As I said before, Simple/BlynkTimer uses a millis() comparison, triggered by timer.run, to see which timed functions have become due to be executed since the last time it ran (normally a fraction of a second go on a modern MCU with well written code and non-blocking processes).

Your ‘solution’ is badly conceived, as it relies on processes being executed during the time periods that you specify, rather than waiting until the callbacks indicate that the process has actually executed.
Rather like observing a set of traffic lights and concluding that they stay on green for 20 seconds, then assuming that it’s okay to proceed 18 seconds after they stopped being red, rather than only proceeding if the lights are on green. This approach may work most of the time, but on occasions it won’t because the lights will change back to red after a different period of time. This is also a ‘marginal’ strategy, like the strategy you adopted in your original ‘solution’.

My example code could be improved/refined by using a flag that’s set in the BLYNK_WRITE() function, or using a looping process to repeat the sending of data if the sent and retrieved values are different.
However, as I said before, none of this code is relevant when use deepsleep. In that situation you need to write a very differently structured sketch which doesn’t use timers, millis comparisons, while loops or delays. In this type of sketch the majority of code is likely to live in BLYNK_CONNECTED() and BLYNK_WRITE().

I’m getting bored with this conversation, so I’ll be taking a step back from it unless you start focusing on code that relates to your original stated aims.

Pete.

Dear PeteKnight,

Thank you for the quick feedback. Not to provoke your boredom in my next message I am going to provide a sample of code based on what I learned from you. This code will include the sleep cycle and you will be able to amend it.

I would like to underscore that my assumption of Blynk not being reliable and not providing a standard solution to control for errors has been confirmed and indeed this changes the approach of writing the code. Had I believed your statement that it is just my code leading to the errors but not Blynk issues we would have probably waisted even more time on figuring out what is wrong with more complex code. Nevertheless I appreciate your input.

I doubt strongly that the timer.setInterval(200L, sendOrRetrieve) somehow waits until the callback function is executed. To make it possible there should be some feedback from the callback function, e.g. data indicating that the process has been finished, since the process is not executed on the same processor as the main code (it is a daemon process in a way). Which means your code basically do the same as the loop below (not only my opinion, see also here Timer.setinterval - Programming Questions - Arduino Forum):

void loop() {
  unsigned long TimerStart = 0;
  randN = random(5000);
  ..
  Blynk.virtualWrite(V1, randN);
  TimerStart = millis();
  
  while(millis() - TimerStart < 150) {
    Blynk.run();
  }
 
 Blynk.syncVirtual(V1);  
  TimerStart = millis();
  while(millis() - TimerStart < 150) {
    Blynk.run();
  }...

You’re welcome to form that assumption, based on your knowledge and understanding. My assumptions, based on my knowledge, understanding and experiences are different, as you’re aware.

It does not. But, what it does do is to place the code that prints the value received from Blynk in a callback function which is only triggered when that data arrives. Your code waited a set amount of time, assumed that the data had arrived from Blynk, and printed the result.
With my approach, if no value is received during that 200ms period then nothing will be printed. With your approach the existing value was printed, implying it had just come from the Blynk server, when this wasn’t necessarily the case, leading to incorrect assumptions.

That makes no sense. The callback BLYNK_WRITE() callback is only triggered when data is received.

A more sensible “wish list” approach would be for the Blynk.virtualWrite() function to return a response indicating that the server had acknowledged receipt of the data that has been sent. I assume that this approach hasn’t been implemented because of the impact it would have on performance, but you’d need to ask Blynk about that. Either way, it’s not a tool that’s available to you at present, so isn’t something worth discussing here.

The way that Simple/BlynkTimer works is nothing like the code you’ve written below, and your code is nothing like the code that is used un the discussion you linked to.
The code in the link bears a resemblance to the way that Simple/BlynkTimer works, but isn’t very elegant.

I don’t know why you think that restricting the conditions under which Blynk.run() should be executed within the void loop is a sensible idea, or is in any way relevant to the task in hand.

But, as I said before, a deepsleep sketch wouldn’t be using timers, millis comparisons, while loops or delays, so educating you on how Simple/Blynk timer works is irrelevant anyway.

I will await your code, but whether choose to suggest any amendments will depend on a variety of factors.

Pete.

Dear Pete,

I found at last some time to create a sample code for the sleep mode. To simplify task I started with clean code, which executes the following cycle:

  1. wakes up
  2. connects to Blynk and reads saved value from it
  3. checks if the value is below a certain threshold, if yes it switches a relay on, if not switches the relay off
  4. reads a sensor value
  5. sends the sensor value to Blynk
  6. goes to sleep
  7. cycle starts from 1)
...

#include <WiFi.h>
#include <WiFiClient.h>
#include <esp_wifi.h>
#include <BlynkSimpleEsp32.h>
#include <TimeLib.h>                               

#define SERIAL_BAUD 115200

int checkst = 0;                                   
String CheckTime;
long randN;
BlynkTimer timer;                                  
int error_count = 0;

void setup() 
{
  int SleepInterval = 10; //Set sleep cycle interval in seconds
  int sens = 0;
  sens = Sensors_Read();  //This function simulates a sensor reading, it generates a random integer value from 1 to 100
  Serial.begin(SERIAL_BAUD);
  Serial.println("The device woke up, trying to connect to Blynk..");
  if(Connect_Blynk())
  {
    Send_data(sens);
  }
  else
  {
    Switch_Relay(60);  //If connection to Blynk failed the relay should be 'on'.
    Serial.println("Connection failed. Set sensor value to ON as default.");
  }
  //Disconnect WiFi and go to sleep
  Serial.println("Going to sleep..");
  WiFi.disconnect();
  esp_sleep_enable_timer_wakeup(1000000ULL * SleepInterval);
  esp_deep_sleep_start();
}

void Switch_Relay(int sw)
{
  if(sw > 50){
    Serial.println("The relay is ON!");
  }
  else{
    Serial.println("The relay is OFF!");
  }
}

void Send_data(int dat)
{
  unsigned long TimerStart = 0;
  Blynk.virtualWrite(V1, dat);
  TimerStart = millis();
  //This cycle waits 0.5s for the data to reach Blynk, this is not the best solution, an alternative would be to use timer.setInterval and check if the value has been received
  while (!Blynk.connected() && (millis() - TimerStart < 500)) {Blynk.run();}
  Serial.println("Sent sensor data to Blynk! Sensor's new value = " + String(dat));
}

int Sensors_Read()
{
  return(random(99)+1);
}

bool Connect_Blynk() 
{
  char ssid[] = "xxx";
  char pass[] = "xxx";
  unsigned long TimerStart = 0;
  WiFi.begin(ssid, pass);
  Blynk.config(BLYNK_AUTH_TOKEN);
  TimerStart = millis();
  while (!Blynk.connected() && (millis() - TimerStart < 15000)) {Blynk.run();}
  if(Blynk.connected()){
    Serial.println("Connected to Blynk!");
    return(true);
  } 
  else{Serial.println("Was not able to connect to Blynk");}
  return(false);
}

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

BLYNK_WRITE(V1)
{
  checkst = param.asInt(); // Get value as integer
    if(checkst == 0)
    {
      //If the data is not received checkst should be equal to 0
      Serial.println("The data was not recieved correctly! The relay stays as was.");
    }
    else
    {
      Serial.println("Received data from Blynk! Sensor's old value = " + String(checkst));
      Switch_Relay(checkst);  //This function switches relay 'on' if the value read from Blynk > 50 and switches it 'off' if the value is < 50;
    }
}

BLYNK_CONNECTED()
{
  Blynk.syncVirtual(V1);
}

The code works well, I tested it for 30 or so cycles. I would appreciate any suggestions for an improvement.

Dmitry

It’s a totally different code structure to the one I’d use and I’m not convinced that it will always trigger the BLYNK_WRITE() function before going to sleep.

I prefer to have a function called “go_to_sleep” that triggers the deep sleep and can be called in one of three scenarios…

  1. the device fails to connect to WiFi
  2. the device fails to connect to Blynk
  3. the device has connected to Blynk and BLYNK_WRITE() has been executed and a reading has ben taken and uploaded

I’d also use the optional timeout parameter in Blynk.config() rather than doing the while loop and timer.

Pete.

Dear Pete,

Here is the new code. I would be grateful if you could improve, and advise… I checked the code and it worked with no errors for more than 15 min.

#define BLYNK_TEMPLATE_ID           "xxxx"
#define BLYNK_TEMPLATE_NAME         "xxxx"
#define BLYNK_AUTH_TOKEN            "xxxx"

#include <WiFi.h>
#include <WiFiClient.h>
#include <esp_wifi.h>
#include <BlynkSimpleEsp32.h>
#include <TimeLib.h>                               

#define SERIAL_BAUD 115200

int checkst = 0; 
int sens = 0;                                  
String CheckTime;
long randN;
BlynkTimer timer;                                  
int error_count = 0;
int SleepInterval = 10; //Set sleep cycle interval in seconds

void setup() 
{
  sens = Sensors_Read();  //This function simulates a sensor reading, it generates a random integer value from 1 to 100
  Serial.begin(SERIAL_BAUD);
  Serial.println("The device woke up, trying to connect to Blynk..");
  if(!Connect_Blynk()) {
    Switch_Relay(60);  //If connection to Blynk failed the relay should be 'on'.
    go_to_sleep();     //Go to sleep if Blynk is not connected
  }
}

void Switch_Relay(int sw)
{
  if(sw > 50){
    Serial.println("The relay is ON!");
  }
  else{
    Serial.println("The relay is OFF!");
  }
}

void Send_data(int dat)
{
  unsigned long TimerStart = 0;
  Blynk.virtualWrite(V1, dat);
  TimerStart = millis();
  //This cycle waits 0.5s for the data to reach Blynk, this is not the best solution, an alternative would be to use timer.setInterval and check if the value has been received
  while (!Blynk.connected() && (millis() - TimerStart < 500)) {Blynk.run();}
  Serial.println("Sent sensor data to Blynk! Sensor's new value = " + String(dat));
  //Data sent, the device goes to sleep
  if(WiFi.status() == WL_CONNECTED) WiFi.disconnect();
  go_to_sleep();
}

int Sensors_Read()
{
  return(random(99)+1);
}

bool Connect_Blynk() 
{
  char ssid[] = "xxxx";
  char pass[] = "xxxx";
  unsigned long TimerStart = 0;
  WiFi.begin(ssid, pass);
  TimerStart = millis();
  //15s to try to connect to WiFi
  while (WiFi.status() != WL_CONNECTED && (millis() - TimerStart < 15000)) timer.run();
  //if successful attempt to connect to Blynk
  if(WiFi.status() == WL_CONNECTED){
    Blynk.config(BLYNK_AUTH_TOKEN);
    TimerStart = millis();
    while (!Blynk.connected() && (millis() - TimerStart < 15000)) {Blynk.connect(1000);}
    if(Blynk.connected()){
      Serial.println("Connected to Blynk!");
      return(true);
    } 
    else{
      Serial.println("Was not able to connect to Blynk");
      if(WiFi.status() == WL_CONNECTED) WiFi.disconnect();
      return(false);
    }
  }
  //if not successful print the message and go to sleep
  else{
    Serial.println("Was not able to connect to WiFi");
    return(false);
  }
}

void go_to_sleep()
{
  //Go to sleep
  Serial.println("Going to sleep..");
  esp_sleep_enable_timer_wakeup(1000000ULL * SleepInterval);
  esp_deep_sleep_start();
}

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

BLYNK_WRITE(V1)
{
  checkst = param.asInt(); // Get value as integer
  if(checkst == 0)
  {
    //If the data is not received checkst should be equal to 0
    Serial.println("The data was not recieved correctly! The relay stays as was.");
  }
  else
  {
    Serial.println("Received data from Blynk! Sensor's old value = " + String(checkst));
    Switch_Relay(checkst);  //This function switches relay 'on' if the value read from Blynk > 50 and switches it 'off' if the value is < 50;
  }
  Send_data(sens);
}

BLYNK_CONNECTED()
{
  Blynk.syncVirtual(V1);
}