Delay vs Timer vs Millis - Solved

I been working a garage door alarm and I am at the point where I have everything working pretty good. I have loop at will send an IFTTT notification every 15 min. after 9 PM if my garage door is open. I am using a delay which I believe is not the preferred method. Is there a reason why it is not and does anyone have a better way to do this. I am also concerned my other loops sometimes get hung up while it is in this delay. Is that true? The part of the code is here. Thanks in advanced.


if ((doorvalue ==1 && hour >= 21  ) || (doorvalue ==1 && hour <=3 )) {
    Particle.publish ("AFTER9PM","After_9PM", 60, PRIVATE);
    delay (900000);
    }
    else {
    }
}

The whole code is below.


int doordetector = A2;
int button = D4;
int doorvalue;
int hour;
int out = A5;
bool dooropened = false;
bool after9 = false;
bool AFH = false;
int awayfromhome;




void setup() {
Time.zone(-4);
pinMode(out, OUTPUT);
pinMode (doordetector, INPUT);
pinMode (button, OUTPUT);
Particle.variable ("GDoor-1open", doorvalue);
Particle.variable ("away__",awayfromhome);
Particle.subscribe("away", myHandler1,MY_DEVICES);
Particle.subscribe("home", myHandler2,MY_DEVICES);
Particle.function ("CLOSEopener",CLOSEopener);
Particle.function ("OPENGarage", OPENGarage);

}
void loop() {
doorvalue=digitalRead(doordetector);
hour=Time.hour();

if (doorvalue==1 && AFH != true && awayfromhome==1 ) {
    AFH = true ;
    Particle.publish ("AWAYFROMHOME","GDOOR_OPEN" , 60 , PRIVATE);
    delay (1000);
    }
    else if (awayfromhome==0 ) {
        AFH = false;
    }


if (doorvalue==1 && dooropened != true) {
        dooropened = true ;
        Particle.publish("Garage_Door","Opened" , 60 , PRIVATE);
        delay (1000);
        }
    else if (doorvalue == 0) {
    dooropened = false;
    }


if ((doorvalue ==1 && hour >= 21  ) || (doorvalue ==1 && hour <=3 )) {
    Particle.publish ("AFTER9PM","After_9PM", 60, PRIVATE);
    delay (900000);
    }
    else {
    }
}
int CLOSEopener(String command){
    if (command == "close" && (digitalRead(doordetector)==HIGH) ){
        digitalWrite (button , HIGH);
        delay (500);
        digitalWrite (button, LOW);
        delay (16000);
        if (digitalRead(doordetector)==LOW) {
        return 1111;
        Particle.publish ("GdoorCommand","Close-Success", 60, PRIVATE);
        }
        else {
        return 999999;
        Particle.publish ("GdoorCommand","Close-Fail", 60, PRIVATE);
        }
    }
    else {
        return 1919191;
    }
}
int OPENGarage(String command){
    if (command == "open" && (digitalRead(doordetector)==LOW)){
        digitalWrite (button , HIGH);
        delay (500);
        digitalWrite (button, LOW);
        delay (16000);
        if (digitalRead(doordetector)==HIGH) {
        return 1111;
        Particle.publish ("GdoorCommand","Open-Success", 60, PRIVATE);
        }
        else {
        return 999999;
        Particle.publish ("GdoorCommand","Open-Fail", 60, PRIVATE);
        }
    }
    else {
        return 1919191;
    }
}

void myHandler1(const char *event, const char *data) {
    awayfromhome = 1;
    
}
void myHandler2(const char *event, const char *data) {
    awayfromhome = 0;
}

You clearly sort of know the answer already. delay() is blocking, for the time specified nothing else happens within the routine that calls it.

A timer is a good way to reliably call a function at set intervals, however it will interrupt whatever else the system is doing, which is why, like interrupts themselves, whatever you do in a timer should be short and sweet.

millis() based timers compare the current millis() against the last value every time you run through loop(), so all your code runs as intended, this makes it good for things that happen frequently that don’t need high precision.

1 Like

Definitely don’t use delay() in your context as @viscacha has noted!

You could use a timer which kicks off a timer callback on expiry. Within the callback just set a global flag that loop() checks, if the flag is up, clear it and then execute the desired function.

You could of course extend this paradigm so that you use a “tick counter” instead, which counts up or down depending on your logic.

For example a 1 sec timer could increment a variable called secs, which you could set up to wrap every 60 seconds.

In this way, from the one timer, you can kick off different functions at different rates, eg functions to occur every minute check to see when secs is 0.

You of course need to put logic in place to ensure that you only call the function once within the time secs is zero.

Conclusion, there are many ways to skin a cat, depends on preference, eg it is simple to call the elapsed millis() function within loop and apply similar logic.

PS don’t forget to mark this ticket as solved if so!

Agreed. Don’t use delay(). Consider using a static long variable to store the start time via the Unix time functions Time.now() or Time.local(). Then call these functions again check time elapsed. A timer can call a short service function to handle this and keep this non-blocking.

Ok. I think I got it working. Here is what I added.

Before Void Setup

int  publish_delay=900000;
unsigned int lastPublish = 0;

In the loop it looks like this.

if ((doorvalue ==1 && hour >= 21  ) || (doorvalue ==1 && hour <=3 )) {
    
    unsigned long now = millis();

    if ((now - lastPublish) < publish_delay) {
        // it hasn't been 15 min. yet...
        return;
    }

    Particle.publish ("AFTER9PM","After_9PM", 60, PRIVATE);

    lastPublish = now;
}
    else {
    }
}

The whole code is below in case anyone wants to do the same thing.


int doordetector = A2;
int button = D4;
int doorvalue;
int hour;
int out = A5;
bool dooropened = false;
bool after9 = false;
bool AFH = false;
int awayfromhome;
int  publish_delay=900000;
unsigned int lastPublish = 0;




void setup() {
Time.zone(-4);
pinMode(out, OUTPUT);
pinMode (doordetector, INPUT);
pinMode (button, OUTPUT);
Particle.variable ("GDoor-1open", doorvalue);
Particle.variable ("away__",awayfromhome);
Particle.subscribe("away", myHandler1,MY_DEVICES);
Particle.subscribe("home", myHandler2,MY_DEVICES);
Particle.function ("CLOSEopener",CLOSEopener);
Particle.function ("OPENGarage", OPENGarage);

}
void loop() {
doorvalue=digitalRead(doordetector);
hour=Time.hour();

if (doorvalue==1 && AFH != true && awayfromhome==1 ) {
    AFH = true ;
    Particle.publish ("AWAYFROMHOME","GDOOR_OPEN" , 60 , PRIVATE);
    delay (1000);
    }
    else if (awayfromhome==0 ) {
        AFH = false;
    }


if (doorvalue==1 && dooropened != true) {
        dooropened = true ;
        Particle.publish("Garage_Door","Opened" , 60 , PRIVATE);
        delay (1000);
        }
    else if (doorvalue == 0) {
    dooropened = false;
    }


if ((doorvalue ==1 && hour >= 21  ) || (doorvalue ==1 && hour <=3 )) {
    
    unsigned long now = millis();

    if ((now - lastPublish) < publish_delay) {
        // it hasn't been 15 min. yet...
        return;
    }

    Particle.publish ("AFTER9PM","After_9PM", 60, PRIVATE);

    lastPublish = now;
}
    else {
    }
}
int CLOSEopener(String command){
    if (command == "close" && (digitalRead(doordetector)==HIGH) ){
        digitalWrite (button , HIGH);
        delay (500);
        digitalWrite (button, LOW);
        delay (16000);
        if (digitalRead(doordetector)==LOW) {
        return 1111;
        Particle.publish ("GdoorCommand","Close-Success", 60, PRIVATE);
        }
        else {
        return 999999;
        Particle.publish ("GdoorCommand","Close-Fail", 60, PRIVATE);
        }
    }
    else {
        return 1919191;
    }
}
int OPENGarage(String command){
    if (command == "open" && (digitalRead(doordetector)==LOW)){
        digitalWrite (button , HIGH);
        delay (500);
        digitalWrite (button, LOW);
        delay (16000);
        if (digitalRead(doordetector)==HIGH) {
        return 1111;
        Particle.publish ("GdoorCommand","Open-Success", 60, PRIVATE);
        }
        else {
        return 999999;
        Particle.publish ("GdoorCommand","Open-Fail", 60, PRIVATE);
        }
    }
    else {
        return 1919191;
    }
}

void myHandler1(const char *event, const char *data) {
    awayfromhome = 1;
    
}
void myHandler2(const char *event, const char *data) {
    awayfromhome = 0;
}

If there is a different way to make it as solved, please let me know.

I would suggest something like this for a bit more readability. It uses a local static which saves chasing a global.

unsigned long now = millis();
static long lastPublish = 0;

if (doorvalue ==1) {
	if ( hour >= 21  &&  hour <=3 ) {
        if ((now - lastPublish) > publish_delay) {
           Particle.publish ("AFTER9PM","After_9PM", 60, PRIVATE);
           lastPublish = now;
        }
    }
}

I often use this:

inline void softDelay(uint32_t msDelay)
{
  for (uint32_t ms = millis(); millis() - ms < msDelay; Particle.process());
}

I can replace all delay() with softDelay() and maintain cloud connection etc.

1 Like

How is this any different than delay()? delay() also maintains the cloud connection, and softDelay is blocking just like delay().

It isn’t any different in regards to blocking the application thread, but delay() only calls Particle.process() once per second, while this kind of softDelay() calls it hundreds if not thousands of times per second - making the device much more responsive to cloud requests and less prone to fail OTA updates.

@jzalar, with unsigned maths you should have all variables as unsigned, don’t mix signed and unsigned as in here

int  publish_delay=900000;
unsigned int lastPublish = 0;
...
    unsigned long now = millis();
    if ((now - lastPublish) < publish_delay)

Although it doesn’t make any difference on these 32bit µC, where int and long are synonymous, you should still stick with one data type.

So the clean way of doing it would be

unsigned long publish_delay=900000;
unsigned long lastPublish = 0;
...
    unsigned long now = millis();
    if ((now - lastPublish) < publish_delay)

Due to the fact that this int vs. long might be confusing, I much prefer the more explicit datatype uint32_t which undoubtadly makes clear it’s an unsigned, integer type with 32bit depth - and it’s shorter to type :wink:

2 Likes

For anyone implementing the check if current time - lastPublish is greater than the delay, be very careful to reset the lastPublish when the mills() clock increments beyond 4,294,967,295 millis which is the max 32 bit unsigned number and resets back to 0, otherwise your code will only work for about 49.7 days and then never update after that.

One fix is to reset lastPublish back to now when the millis() goes back to zero at some point.

// Detect timer roll over
if (now < lastPublish){
Serial.println(" WARNING: millis timer has flipped past 32bits back to zero (about 49.7 days)");
lastPublish=now;
}

When done right that's no issue since unsigned calculations don't care for the roll-over.
The result of (smallUnsignedNumber - bigUnsignedNumber) will not give you a negative but the expected difference as if you had an extra bit allowing the "smallUnsignedNumber" count past the roll-over limit.

That's why you often see code like this

  if (millis() - msLastPublish < period) doNothing();

See here

4 Likes