Need to control a Stepper Motor without blocking Blynk

Hi folks.

When stepping a motor for 20s, the server times-out with no hardware connection. I can avoid that by chunking a few hundred steps, then Blink.run(), then some more steps… However, app input during the motor run can then spawn new hardware calls, when I would rather that the motor run and block any new calls without app-side errors.

Q1) I noticed the Blynk.sendCmd(BLYNK_CMD_PING) used as a keepalive and wonder if I could re-use that as a hardware heartbeat within my stepper function (which would block hardware events).

Q2) I tried upping the heartbeat and network timeout config items, but they don’t seem to effect the cloud-side timeout. Could these be helpful for me?

Q3) Is there a way for the hardware to tell the server to “hold on X seconds while I do something else”? I would love if the server could queue-up 20 seconds worth of app output without barking errors to the app screen…

I am working on a solution that flags “motor-running” and ignores/reverts any slider value changes until the flag is cleared, but this just gets messy as I add other widgets.

Thanks for any advice!

-Jamie

You would need to show us some code to work with.

As for timers, SimpleTimer is the choice for non-blocking Blynk operations (it is already in the Blynk Libraries).
http://playground.arduino.cc/Code/SimpleTimer

My main loop only has a simple timer and blink.run, but running a motor for a while (or any other long-running function) will keep us from returning to loop and Blink.run().

Here’s the stepMotor() that works with inserted blink calls… I would like to remove/replace them without causing errors to show up on the app.

void stepMotor(long steps) {
  const int CHUNK = 500;
    
  //break up into chunks so you can connect with Blynk
  while (abs(steps) > CHUNK) {
    Blynk.run();
    if (steps > 0) {
      moveMtr(CHUNK);
      steps = steps - CHUNK;
    } else {
      moveMtr(-CHUNK);
      steps = steps + CHUNK;
    }
  }
  Blynk.run();
  
  moveMtr(steps);  //finish final bit
}

void moveMtr(int i) {
  if (i > 0) {
    digitalWrite(dirPin,HIGH);
  } else {
    digitalWrite(dirPin,LOW);
    i = -i;
  }

  while (i--) {
    digitalWrite(stepPin,HIGH);
    myDelay(5);
    digitalWrite(stepPin,LOW);
    myDelay(5);
  }
}

void myDelay(unsigned long x) {
  while (x--) {
    delay(0);  //non-blocking delay so esp can do WiFi stuff
    delayMicroseconds(100);
  }
}

I think you may want to reconsider the actual stepper control. Try something like this apparently non-blocking control

Also, I changed your post title to better reflect your questions

I do run my motor and squeeze in Bynk and WiFi routines during the run (as you can see in thee code) – not unlike the example you present on how to do it. The motor works fine and Blynk and WiFi are happy clams.

However, if you let Blink run, it might spawn children (launch new function calls) in the midst of a parent function that you may not want to have yield.

In my case: If I move a slider widget that sets the motor position, the motor starts moving. Fine. But, instead of waiting, the app user moves the slider again!!! The hardware will get the call to move to another position before the first ever completed. It might move to the 2nd position, then return back to complete the first movement. Yikes!

If I do not call Blink.run() during that first motor run in the example, then the user can still move the slider. It will not get passed to the hardware, ever. The slider position sits in the new, incorrect location, not in sync with the hardware. And, the app user sees an error saying “HARDWARE-NAME is Offline”.

Blynk is asynchronous. So I wonder if there is a way to increase the app -->server buffer and the timeout variable on the server—hardware link. Maybe the answer here is just “no” and this becomes a feature request.

Another solution would be for the hardware to say “WAIT EVERYONE!” and the server freezes the app and posts a custom message (“Wait. Motor still running.”) until the block is lifted by the hardware.

Without one of these solutions, then how can a program ever be designed that needs to perform a 10 second activity that cannot be interrupted?

I have no idea what you are trying to say here… Blynk constantly runs in the background as does ESP WiFi - if that is what you are using, thus the requirement to keep any blocking function to a minimum… it doesn’t spawn anything.

You can try installing your own Local Server for a bit more control over timeouts and buffers https://github.com/blynkkk/blynk-server/releases

But if you are trying to reinvent how Blynk works so that you can have x second timeout to do other stuff (if I am understanding you correctly) then you are a bit out of luck on that - well not technically, as it is open source :wink:

It gets done all the time… Your best bet is to post your entire code in case someone else wishes to look at it and suggest other solutions, instead of the possible one I already pointed out (non-blocking Servo control).

Thanks, Gunner, for trying to get the question. Apologies that I wasn’t clear enough for you to get the gist.

It would be great to see an example of a widget calling a 10+ second function, with no app error or function interruptions.

Just a single push button call to a delay(10000) function would recreate the issue, I think.

Nonetheless, here is a bunch of code. It drives a wood stove air intake.

/*
STEPPER
=======
D1 = GPIO5 - step
D2 = GPIO4 - direction

K-Type Temp Sensor
==================
D6 = GPIO12 -SO
D7 = GPIO13 -CS
D3 = GPIO0  -CLK

D4 = GPIO2 (doubtful - tied to vcc)
D8 = GPIO15 (doubtful - tied to gnd)
D0 = GPIO16 (iffy if using deepsleep) - motor sleep
*/

#include <max6675.h>
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <SimpleTimer.h>
#include <math.h>


//thermostat setup
int thermoDO = 12;
int thermoCS = 13;
int thermoCLK = 0;
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);

//Timer setup
SimpleTimer timer;

//Blynk setup
char ssid[] = "xxx";  //type your ssid
char pass[] = "xxx";                    //type your password
char auth[] = "xxx";

//my global variables
const int DAYTIME=1;
const int OVERNIGHT=2;
const int HIGH_HEAT=3;
const int MANUAL=4;
const int STOVE_TEMP=5;
const int ROOM_TEMP=6;

const int MIN_TEMP=380;
const int HIGH_TEMP=420;
const int MAX_TEMP=460;

const int stepPin = 5;
const int dirPin = 4;
const int sleepPin = 16; //pin D0

const int motorSpeed = 5;
const int START_POS=75;
const int CAP_POS=65;
const int LOCK_DOWN_POS=5;
const int NO_WOOD_POS=30;

bool myDebug = 0;
bool isFirstConnect = 1;
bool isFirstConnectV2 = 1;
bool isFirstConnectV4 = 1;
bool isFirstConnectV5 = 1;
bool isFirstConnectV6 = 1;
bool isFirstConnectV7 = 1;

float rTempHist[30];
int leverPos = 0;  //temp holder for pos if calibrate touched

long totalSteps = 33730;
long curStep = 1; 
int runState = MANUAL;

unsigned long timeFilled = millis();
unsigned long burnTime = ( (millis() - timeFilled) / 1000) / 60; //minutes
bool brokeMin = 0;
bool brokeMed = 0;
bool brokeMax = 0;
bool overFire = 0;
bool outOfWood = 0;
String myTermMsg = "";
bool nowCalibrating = 0;
bool userPress = 0; //calibration button push

int stoveTgt = 380;
int stoveNow = 0;
int stoveOld = 0;
int stoveDelta = 0;

float roomTgt = 72.00;
float roomNow = 0;
float roomOld = 0;
float roomDelta = 0;

int posTgt = 1;
int posNow = posTgt;
int posOld = posTgt;
int posDelta = posOld - posNow;

WidgetTerminal blynkTerm(V7);

BLYNK_WRITE(V5) {
  int newMode = param.asInt();

  if (isFirstConnectV5) {    
    runState = newMode;
    isFirstConnectV5 = 0;
    return;
  }
  
  switch (newMode) {
    case DAYTIME: {
       myTermMsg = "New burn mode: DAYTIME";
       showTerm();
       runState = newMode;
       break;
    }
    case OVERNIGHT: {
       myTermMsg = "New burn mode: OVERNIGHT";
       showTerm();
       runState = newMode;
       break;
    }
    case HIGH_HEAT: {
       myTermMsg = "New burn mode: HIGH HEAT";
       showTerm();
       runState = newMode;
       break;
    }
    case MANUAL: {
       myTermMsg = "New burn mode: MANUAL";
       showTerm();
       runState = newMode;
       outOfWood = 0;
       break;
    }
    case STOVE_TEMP: {
       myTermMsg = "New burn mode: STOVE TEMP";
       showTerm();
       myTermMsg = "Target Stove Temp: ";
       myTermMsg += stoveTgt;
       myTermMsg += "℉";
       showTerm();
       runState = newMode;
       break;
    }
    case ROOM_TEMP: {
       myTermMsg = "New burn mode: ROOM TEMP";
       showTerm();
       myTermMsg = "Target Room Temp: ";
       myTermMsg += roomTgt;
       myTermMsg += "℉";
       showTerm();
       runState = newMode;
       break;
    }
    default: {
       myTermMsg = "Invalid Mode - Request Ignored";
       showTerm();
       break;
    }
  }
}

BLYNK_WRITE(V7) {
  
  if (isFirstConnectV7) {
    isFirstConnectV7 = 0;
    return;
  }
  
  int termData = param.asInt();
  
  if (termData == 0) {
    myDebug = 0;
    myTermMsg = "Exit DEBUG mode.";
    showTerm();
  } else if (termData == 1) {
     myDebug = 1;           
     myTermMsg = "In DEBUG mode.";
     showTerm();
  } 
}

BLYNK_WRITE(V2) {  
  if (isFirstConnectV2) {    
    posNow = param.asInt();
    posOld = posNow;
    isFirstConnectV2 = 0;
    return;
  }
  
  if (nowCalibrating) {
    leverPos = param.asInt();
    return;
  }

  if (runState != MANUAL) {
      myTermMsg = "Switching to Manual mode.";
      showTerm();
      runState = MANUAL;
      outOfWood = 0;
      Blynk.virtualWrite(V5,runState);
  }
  
  if ((param.asInt() > 0) && (param.asInt() <= 100)) {
    setPos(param.asInt());
  }

}


BLYNK_WRITE(V6) {
  stoveTgt = param.asInt();
  
  if (isFirstConnectV6) {
    isFirstConnectV6 = 0; 
    return;
  }
  
  if (runState != STOVE_TEMP) {
    runState = STOVE_TEMP;
    myTermMsg = "New burn mode: STOVE TEMP";
    showTerm();
    Blynk.virtualWrite(V5,runState);
  }
  
  myTermMsg = "Target Stove Temp: ";
  myTermMsg += stoveTgt;
  myTermMsg += "℉";
  showTerm();
}   


BLYNK_WRITE(V4) {
  roomTgt = param.asInt();
  
  if (isFirstConnectV4) {
    isFirstConnectV4 = 0;
    return;
  }
  
  if (runState != ROOM_TEMP) {
    runState = ROOM_TEMP;
    myTermMsg = "New burn mode: ROOM TEMP";
    showTerm();
    Blynk.virtualWrite(V5,runState);
  }
  
  myTermMsg = "Target Room Temp: ";
  myTermMsg += roomTgt;
  myTermMsg += "℉";
  showTerm();
}      

BLYNK_WRITE(V15) {
 curStep = param.asInt();
}

BLYNK_CONNECTED() {
  if (isFirstConnect) {
    Blynk.syncAll();
    myTermMsg="Hardware Restarted";
    showTerm();
  } else {
    Blynk.virtualWrite(V5,runState);
    Blynk.virtualWrite(V2,posNow);
  }
  
  isFirstConnect = 0;
}

// Button on V11 -- new load of wood
BLYNK_WRITE(V11) {
  if (param.asInt()) {
    timeFilled = millis();
    myTermMsg = "New load of wood.";
    showTerm();
    outOfWood = 0;
  }
}

BLYNK_WRITE(V12) {  //calibrate
  if (param.asInt()) { //if button press
    if (nowCalibrating) { //in the midst of calibration
      userPress = 1;
      return;
    } else {
      nowCalibrating = 1;
      calibrate();
      nowCalibrating = 0;
    }
  }
}

void stepMotor(long steps) {
  const int CHUNK = 500;
    
  //break up into chunks so you can connect with Blynk
  while (abs(steps) > CHUNK) {
    Blynk.run();
    if (steps > 0) {
      moveMtr(CHUNK);
      steps = steps - CHUNK;
    } else {
      moveMtr(-CHUNK);
      steps = steps + CHUNK;
    }
  }
  Blynk.run();
  
  moveMtr(steps);  //finish final bit
}

void moveMtr(int i) {
  if (i > 0) {
    digitalWrite(dirPin,HIGH);
  } else {
    digitalWrite(dirPin,LOW);
    i = -i;
  }

  while (i--) {
    digitalWrite(stepPin,HIGH);
    myDelay(5);
    digitalWrite(stepPin,LOW);
    myDelay(5);
  }
}

void myDelay(unsigned long x) {
  while (x--) {
    delay(0);
    delayMicroseconds(100);
  }
}

void calibrate() {           
      myTermMsg = "Calibration: Set the slider to match";
      showTerm();
      myTermMsg = "the exact location of the stove lever.";
      showTerm();
      myTermMsg = "Then press the Calibrate Button.";
      showTerm();
      
      int x = 3000;
      while ((!userPress) && (--x)) {  //wait for YES for 30 secs
        Blynk.run();
        delay(10);
      }

      if (!userPress) {
        myTermMsg = "No Press. Calibrate Aborted!";
        showTerm();
        Blynk.virtualWrite(V2,posNow);
        return;
      }
      userPress = 0;
      
      posNow = leverPos;
      curStep = (totalSteps * posNow) / 100;      
      
      myTermMsg = "Let's check the closed position.";
      showTerm();
      myTermMsg = "I'll get close (about 20%)...";
      showTerm();
      
      setPos(20);
 
      myTermMsg = "Repeat press to inch to closed.";
      showTerm();
      myTermMsg = "Then wait.";
      showTerm();

      
      digitalWrite(sleepPin,HIGH);
      delay(500);
      
      while (1) {
        x = 100;
        while ((!userPress) && (--x)) {  //wait 10 secs for "inch"
          Blynk.run();
          delay(100);
        }
        if (userPress) {
          stepMotor(-300);
          userPress = 0;
        } else {
          break;
        }
      }
      
      digitalWrite(sleepPin,LOW);
      userPress = 0;

      posNow = 1;
      curStep = 1;

      myTermMsg = "Calibration Complete!";
      showTerm();
      Blynk.virtualWrite(V2,posNow);
}

void showTerm() {
  myTermMsg.trim();
  blynkTerm.println(myTermMsg);
  blynkTerm.flush();
  Blynk.run();
}

void adjustAir() {
  burnTime = ( (millis() - timeFilled) / 1000) / 60; //minutes
  static int visitNum = 0;

  if (++visitNum > 25) {  
    myTermMsg = "Last Loaded: ";
    myTermMsg += burnTime;
    myTermMsg += " mins ago ";
    showTerm();
    visitNum = 0;
  }

  switch (runState) {
    case (MANUAL):
          break;
    case (DAYTIME):
          if (burnTime > 60) {
              setMinMax(3,10,25,50);
          } else if (burnTime > 20) {
              setMinMax(3,15,35,60);
          } else if (burnTime > 5) {
              setMinMax(5,20,45,65);
          } else {
              setMinMax(10,25,50,START_POS);
          }
          break;
    case (OVERNIGHT):
          if (burnTime > 100) {
              setMinMax(1,1,4,4);
          } else if (burnTime > 30) {
              setMinMax(1,2,10,60);
          } else if (burnTime > 20) {
              setMinMax(2,5,15,65);
          } else if (burnTime > 5) {
              setMinMax(2,7,15,70);
          } else {
              setMinMax(2,8,15,START_POS);              
          }
          break;
    case (HIGH_HEAT):
          if (burnTime > 40) {
              setMinMax(20,30,60,60);
          } else if (burnTime > 15) {
              setMinMax(20,35,60,65);
          } else if (burnTime > 10) {
              setMinMax(25,45,60,65);
          } else {
              setMinMax(25,55,60,70);              
          }
          break;
    case (STOVE_TEMP): {
          int moveAmt = (stoveTgt - stoveNow - (stoveDelta * 2)); //change air in direction of desired change, with weighted temp change factor
          
          if (myDebug) {
            myTermMsg = "Stove Target: ";
            myTermMsg += stoveTgt;
            showTerm();
            myTermMsg = "Move Amount: ";
            myTermMsg += moveAmt;
            showTerm();
          }
          
          bumpAir(moveAmt);
          break;
    }

    case (ROOM_TEMP): {
          float roomFactor = roomTgt - roomNow -(roomDelta/4);
          stoveTgt = 19.531*roomFactor + 375.6;
          
          if ((!brokeMin) && (stoveTgt < MIN_TEMP) && (MIN_TEMP - stoveTgt < 40)) { //jump to efficient temp, if close
            stoveTgt = MIN_TEMP;
          }
          Blynk.virtualWrite(V6,stoveTgt);
          int moveAmt = (stoveTgt - stoveNow - (stoveDelta * 2)); //change air in direction of desired change, with weighted temp change factor

          if (myDebug) {
            myTermMsg = "Room Factor: ";
            myTermMsg += roomFactor;
            showTerm();
            myTermMsg = "Stove Target: ";
            myTermMsg += stoveTgt;
            showTerm();
            myTermMsg = "Move Amount: ";
            myTermMsg += moveAmt;
            showTerm();
          }

          bumpAir(moveAmt);
          break;
    }
  }
}

void bumpAir(int up) {
  int airFlowAmt = pos2air(posNow) + up;
  if (airFlowAmt < 2) airFlowAmt=1;  //not neg numbers
  int gotoPos = air2pos(airFlowAmt);  // suppress/increase airflow by a certain percentage
  
  if ((abs(posNow - gotoPos) <= 5) && (gotoPos > 15)) return;  //do not bother with small moves (unless they are in the crank-down zone)

  if (myDebug) {
    myTermMsg = "pos2air(posNow) = ";
    myTermMsg += (int)pos2air(posNow);
    showTerm();
    myTermMsg = " + up = ";
    myTermMsg += airFlowAmt;
    showTerm();
    myTermMsg = "air2pos(airFlowAmt) = ";
    myTermMsg += gotoPos;
    showTerm();
  }

  setPos(gotoPos);
}

int pos2air(double curPos) {
  return( (-0.0097*curPos*curPos) + (1.9463*curPos) + 2 );
}

int air2pos(double air) {
  return( 0.009*air*air + 0.008*air + 1 );
}

void setMinMax(int posMax, int posMed, int posMin, int posDefault) {
    if (brokeMax) {
      setPos(posMax);
    } else if (brokeMed) {
      setPos(posMed);
    } else if (brokeMin) {
      setPos(posMin);
    } else {
      setPos(posDefault);
    }
}

void setPos(int pTgt) {  //percent open (1-100)
  static bool motorRunning = 0;
  posTgt = pTgt;

  if (motorRunning) {
    return;
  }

  motorRunning = 1;
  
  if (overFire) {
    if ((posTgt > NO_WOOD_POS) && (runState != MANUAL)) {  //waiting for overfire condition to reset
      myTermMsg = "In overheat mode. Not increasing.";
      showTerm();
      return;
    } else {
      overFire = 0;
    }
  }
  
  if (outOfWood) {
    if ((posTgt > LOCK_DOWN_POS) && (runState != MANUAL)) {  //waiting for overfire condition to reset
      myTermMsg = "Out of wood. Not increasing.";
      showTerm();
      return;
    } else {
      outOfWood = 0;
    }
  }
  
  if ((posTgt > CAP_POS) && (runState != MANUAL) && (runState != HIGH_HEAT)) {
    posTgt = CAP_POS;
  }

  if ((posTgt < 6) && (!brokeMin) && (runState != MANUAL) && (runState != OVERNIGHT)) {
    posTgt = 6;
  }
  
  if ((posTgt < 3) && (!brokeMed) && (runState != MANUAL) && (runState != OVERNIGHT)) {
    posTgt = 3;
  }
  
  posDelta = posNow - posOld;
  myTermMsg = " ";
  showTerm(); //blank line above header
  
  myTermMsg = "@";
  myTermMsg += burnTime;
  myTermMsg += "m";
  
  if(burnTime>100) myTermMsg += " ";
  else if(burnTime>10) myTermMsg += "  ";
  else myTermMsg += "   ";

  myTermMsg += " OLD    CHANGE   NOW ⇨ TARGET";
  showTerm();
    
  if (runState == ROOM_TEMP) {
    myTermMsg = "Room  ";
    myTermMsg += padIt((String)(round(roomOld*10)/10.00),4,"");
    myTermMsg += "℉   ";
    myTermMsg += padIt((String)abs(round(roomDelta*10)/10.00),3,deltaChar(roomDelta));
    myTermMsg += "℉ ";
    myTermMsg += padIt((String)(round(roomNow*10)/10.00),4,"");
    myTermMsg += "℉⇨ ";
    myTermMsg += padIt((String)(round(roomTgt*10)/10.00),4,"");
    myTermMsg += "℉";
    showTerm();
  }

  if ((runState == ROOM_TEMP) || (runState == STOVE_TEMP)) {
    myTermMsg = "Stove ";
    myTermMsg += padIt((String)stoveOld,4,"");
    myTermMsg += "℉  ";
    myTermMsg += padIt((String)abs(stoveDelta),3,deltaChar(stoveDelta));
    myTermMsg += "℉ ";
    myTermMsg += padIt((String)stoveNow,4,"");
    myTermMsg += "℉⇨ ";
    myTermMsg += padIt((String)stoveTgt,4,"");
    myTermMsg += "℉";
    showTerm();
  }
  
  myTermMsg = "Air   ";
  myTermMsg += padIt((String)pos2air(posOld),4,"");
  myTermMsg += "%  ";
  myTermMsg += padIt((String)abs(pos2air(posNow) - pos2air(posOld)),4,deltaChar(posDelta));
  myTermMsg += "%  ";
  myTermMsg += padIt((String)pos2air(posNow),4,"");
  myTermMsg += "% ⇨ ";
  myTermMsg += padIt((String)pos2air(posTgt),4,"");
  myTermMsg += "%";
  showTerm();
  
  myTermMsg = "Lever ";
  myTermMsg += padIt((String)posOld,4,"");
  myTermMsg += "%  ";
  myTermMsg += padIt((String)abs(posDelta),4,deltaChar(posDelta));
  myTermMsg += "%  ";
  myTermMsg += padIt((String)posNow,4,"");
  myTermMsg += "% ⇨ ";
  myTermMsg += padIt((String)posTgt,4,"");
  myTermMsg += "%";
  showTerm();
  
  float stepsPercent = float(totalSteps) / 100;
  float newStep = (stepsPercent * float(posTgt));
  int mvAmt = int(newStep) - curStep;
  
  digitalWrite(sleepPin,HIGH);
  delay(500);  
  stepMotor(mvAmt);
  curStep = int(newStep);
  digitalWrite(sleepPin,LOW);
  
  posOld = posNow;
  posNow = posTgt;
  Blynk.virtualWrite(V2,posNow);
  Blynk.virtualWrite(V15,curStep);
  motorRunning = 0;
}

String deltaChar(float x) {
  if (x > 0) {
    return("⇧");
  } else if (x < 0) {
    return("⇩");
  } else {
    return(" ");
  }
}

String padIt(const String& S, int spaces, const String& deltaChar) {
  int l=spaces - S.length();
  if (l <= 0) {
    return(String(deltaChar+S.substring(0,spaces)));
  }
  
  String iS = "";
  while (l--) { iS += ' '; }
  iS += deltaChar;
  iS += S;
  return(iS);
}

void getTemps() {
  static int x=0;

  stoveNow = thermocouple.readFahrenheit();
  Blynk.virtualWrite(V0,stoveNow);
  if (stoveOld < 1) stoveOld = stoveNow;
  showStoveMilestones();

  roomNow = roomTemp();
  Blynk.virtualWrite(V1,roomNow); 
  if (roomOld < 1) roomOld = roomNow;

  rTempHist[29-x] = roomNow;  //fill from top down

  if (x++ >= 29) {  //after 2 min and 30 temp reads
    combSort11(rTempHist,5);
    roomNow = (rTempHist[1] + rTempHist[2] + rTempHist[3]) / 3;  //avg of middle 3 of last 5 reads
    
    roomDelta =  roomNow - roomOld;
    stoveDelta = stoveNow - stoveOld;

    if (runState == ROOM_TEMP) { 
      Blynk.virtualWrite(V3,roomTgt-roomNow);
    } else if (runState == STOVE_TEMP) {
      Blynk.virtualWrite(V3,stoveTgt-stoveNow);
    } else {
      Blynk.virtualWrite(V3,0.0);
    }
    
    adjustAir();
    
    roomOld = roomNow;
    stoveOld = stoveNow;
    x=0;
  }
}

float roomTemp() {
  int aReadVal = 0;
  int highVal = 0;
  int highestVal = 0;
  
  for (int i = 0; i < 5; i++) {
    aReadVal = analogRead(A0);
    if (aReadVal > highestVal) {  // we through away the highest value
      highVal = highestVal;
      highestVal = aReadVal;
    } else if (aReadVal > highVal) {
      highVal = aReadVal;
    }
    delay(5);
  }

  unsigned int Rs = 300000;
  double Vcc = 3.3;

  double V_NTC = (double)highVal / 1024;
  double R_NTC = (Rs * V_NTC) / (Vcc - V_NTC);
  
  R_NTC = log(R_NTC);
  double roomTemp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * R_NTC * R_NTC )) * R_NTC );
  return((float)((roomTemp - 273.15) * 1.8) + 36 );
}

void showStoveMilestones() { 
  if ((stoveNow >= MIN_TEMP) && (!brokeMin)) {
    brokeMin = 1;
    myTermMsg = "Efficient Temp Reached.";
    outOfWood = 0;
    showTerm();
  }
  
  if ((stoveNow >= HIGH_TEMP) && (!brokeMed)) {
    brokeMed = 1;
    myTermMsg = "High Temp Reached.";
    showTerm();
  }
  
  if ((stoveNow >= MAX_TEMP) && (!brokeMax)) {
    brokeMax = 1;
    myTermMsg = "Max Temp Reached.";
    showTerm();
  }

  if ((stoveNow < MIN_TEMP - 5) && (brokeMin)) {
    brokeMin = 0;
    myTermMsg = "Below Efficient Temp.";
    showTerm();
  }
  
  if ((stoveNow < HIGH_TEMP - 5) && (brokeMed)) {
    brokeMed = 0;
    myTermMsg = "Below High Temp.";
    showTerm();
  }
  
  if ((stoveNow < MAX_TEMP - 5) && (brokeMax)) {
    brokeMax = 0;
    myTermMsg = "Below Max Temp.";
    showTerm();
  }
}

void raceCheck () {
  static float raceCheck = stoveNow;
  static int myCounter = 0;
  
  if (outOfWood) return;
  
  if (overFire) {  // wait 6 iterations (3 min) with low air before releasing
    if (myCounter++ > 4) {
      overFire = 0;
      myCounter = 0;
    }
    return;
  } else {
    myCounter = 0;
  }
  
  if ( ((stoveNow - raceCheck) > 15) && (posNow > 40) && (stoveNow > HIGH_TEMP) && (runState != MANUAL)) {  //temp rising too fast
    myTermMsg = "Up ";
    myTermMsg += stoveNow - raceCheck;
    myTermMsg += "℉ in 30s! Slowing.";
    showTerm();
    
    setPos(LOCK_DOWN_POS);
    overFire = 1;
  } else {
    overFire = 0;
  }

  raceCheck = stoveNow;
}

void lowWoodCheck() {
  static float woodCheck = stoveNow;
  static int myCounter = 0;

  if (overFire) return;
  if (outOfWood) return;
  
  if ( ((stoveNow - woodCheck) <= 0) && (posNow > 50) && (runState != MANUAL) && (burnTime > 80) && (stoveNow < MIN_TEMP)) {
    myCounter++;

    if (myCounter > 2) {  // temp dropped 4 times in a row
      outOfWood = 1;
      myTermMsg = "Out of fuel. Reducing air.";
      showTerm();
      setPos(NO_WOOD_POS);
      myCounter = 0;
    }
  } else {
    myCounter = 0;
    outOfWood = 0;
  }
  
  woodCheck = stoveNow;
}


void combSort11(float *ar, int n)
{
  int i, j, gap, swapped = 1;
  float temp;

  gap = n;
  while (gap > 1 || swapped == 1)
  {
    gap = gap * 10 / 13;
    if (gap == 9 || gap == 10) gap = 11;
    if (gap < 1) gap = 1;
    swapped = 0;
    for (i = 0, j = gap; j < n; i++, j++)
    {
      if (ar[i] > ar[j])
      {
        temp = ar[i];
        ar[i] = ar[j];
        ar[j] = temp;
        swapped = 1;
      }
    }
  }
}

void setup() { 
    pinMode(stepPin,OUTPUT);
    pinMode(dirPin,OUTPUT);
    pinMode(sleepPin,OUTPUT);

    Blynk.begin(auth, ssid, pass);
    Blynk.config(auth);
    
    myTermMsg.reserve(30);

    timer.setInterval(4000, getTemps);
    timer.setInterval(30000, raceCheck);
    timer.setInterval(40000, lowWoodCheck);
}

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

[Uploading…]

As that would undermine the entire underlying communication infrastructure of Blynk and its most used ESP8266 based devices (which will also stop it’s integrated WiFi communication, if blocked for more than a second or two - and are outside of Blynk control)… please don’t hold your breath :wink:

FYI - delay() does not block ESP’s. And you can break delay up into little bitty pieces, for the example… i’m still not holding my breath, though!

I think I have learned that it is not possible. No problem with that. The only problem I see is when a customer suggests a feature and a single stakeholder says “you are a bit out of luck on that”.

There is a large market segment of micro-controller applications that will require 10 seconds of exclusive execution periodically (precise movements, precise timing, uninterrupted communication, etc). Why would you exclude those customers from your model if a solution is so simple? It would not alter the underlying mechanisms, in fact it would rely upon them and benefit from them. I expect, Blynk will have an “WAIT EVERYBODY!” function within a year, when you realize many “connectivity complaints” and “timeout issues” that people are asking about is because their device needs to do something exclusively for just a moment… Better yet, if your general service could have variable synchronicity, with options for extended buffering and server-side timeouts. If the answer is “do it yourself” then you’ve given up on owning a good part of this IoT Cloud!

That’s my $0.02…

1 Like

I am just a fellow Maker, Blynk User and Forum Volunteer, in no way invested, employed or reimbursed for or by Blynk :wink: I am truly sorry if you somehow feel slighted, it was certainly not ment in any way as derogatory, it was just a simple comment.

True… but not nearly as common in the general IoT venue that Blynk primarily supports, wherein communication between many simple devices controlling simple things is generally the focus. However, with clever coding and proper libraries, everything is possible.

Well, this is the free do-it-yourself forum for the free do-it-yourself usage of Blynk ;P… That said, I did take my time to do something for you… I gave you a simple explanation for why you need to stay non-blocking and even took the time to search for and find you a suggestion for a solution… I can’t do anything more than that… where you go from here is entirely up to you.

@vshymanskyy please advise.

This is an advanced topic, that requires proper understanding and usage of interrupts, finite state machines and advanced coding techniques… sorry guys :wink:
BTW, it is not strictly related to Blynk - if you just use Arduino Client and make your own communication you would face the same situation.

The title may be misleading. I’m not asking about non-blocking code and don’t see Blynk using interrupts or other kernel hooks.

I see that Blynk already has a solution when the hardware “goes away” for 5 seconds; it makes an attempt to inform the app-client when the hardware is down: “XXX is Offline”. However, it still allows the client to mess with widgets and put them out of sync with the hardware. The current solution requires awkward work-arounds or the abandonment of Blynk for apps that go on an occasional walkabout.

What if, instead, you gave the hardware client a bit of code, to send a FREEZE_APP status through the server to all app clients (and some custom message instead of the “Offline” one), and prevented the change of widgets until there was a FREEZE_RELEASE flag?

This doesn’t require any low-level coding on hardware-side, but would require messaging via cloud-server to app, and (probably the hardest part), your app widgets would need to become “frozen”.

Or, since the communication stream is already asynchronous, another option (probably better for most cases) could be to allow for extended buffering and timeouts, so if someone did manipulate a widget, the server would just queue it until the next Blink.run() (as long as it is within the adjusted timeout window).

This would require no extra coding (ok, maybe you need to now slurp-up a config variable from the client), right?

You would only need more server-RAM per app node to increase the buffering that you already are doing, (and, this extra RAM is only required for those systems utilizing this extended buffering config).

Why is that useful? It let’s people go off on a non-Blynk action for a short while and then resume without a corrupted system state.

Maybe there are other ways or even existing ways for me increase buffering or initiate a “Blynk Hold”. I am just communicating a pain-point, seeking options and brain-storming solutions…

Thanks for your attention.

My Blynk screen never uploaded from the earlier post with code…

1 Like