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…]