Use multiple millis

I want to use an ESP8266 board in my home automation system, the program is very simple: in the main loop I want too check the connection and send an update to the MQTT server every five minutes and the other thing it does is calling a function when it receives a MQTT message.

The part that I wrote in the main loop works fairly well:

void loop() {
  client.loop();

  currentMillisStatus = millis();  //get the current "time"
  if (currentMillisStatus - startMillisStatus >= fiveMinutes)  //test whether the period has elapsed
  {
    
    if ((WiFi.status() == WL_CONNECTION_LOST) || (WiFi.status() == WL_DISCONNECTED))
    {

      Serial.println("WiFi connection lost, connecting again");
      connectToWiFi();
      connectToMQTT();
      client.publish("status/AIRWICK", "WiFi connection lost, connected again");
    }
    Serial.println(client.state());
    if (client.connected() == false)
    {
      Serial.println("MQTT connection lost, connecting again");
      connectToMQTT();
      client.publish("status/AIRWICK", "MQTT connection lost, connected again");
    }

    startMillisStatus = currentMillisStatus;  //IMPORTANT 
  }
}

The problem is in the other function, spray(). It’s called correctly and it does what is supposed to do but it doesn’t wait 30 seconds. Sometimes the delay lasts 10 seconds, other times 20 and other times not even a second.

void wait (int waitTime)
{
  bool timeElapsed = false;
  while (timeElapsed == false)
  {
    client.loop();
    yield();
    currentMillisWait = millis();  //get the current "time" 
    if (currentMillisWait - startMillisWait >= waitTime)  //test whether the period has elapsed
    {
      timeElapsed = true;
      startMillisWait = currentMillisWait;  //IMPORTANT
    }
  }
}

void spray()
{
  Serial.println("");
  Serial.println("ON");
  client.publish("status/AIRWICK", "ON");
  digitalWrite(ledPin, LOW);
  digitalWrite(mist, HIGH);
  wait(30000);
  Serial.println("OFF");
  digitalWrite(ledPin, HIGH);
  digitalWrite(mist, LOW);
  client.publish("status/AIRWICK", "OFF");
}

If I understand correctly if I want to have two separate millis delays in the program I just use different variables to keep track of the time, if that’s the case I don’t really know where the problem is.

I hope someone can help me out, thanks.

You only set ‘startMillisWait’ at the end of the previous wait. That means the value you pass to ‘wait()’ is not how long to wait from NOW, but how long to wait from the last time you were done waiting.

You should put “startMillisWait = millis();” at the beginning of ‘wait()’.

Thanks a lot, it worked! I also noticed that I had done the same thing in the main loop, I didn’t put startMillisStatus = millis(); in the setup.

I don’t understand how it worked before, the first time the program run the main loop it compared a random number assigned to the variable startMillisStatus. Once the time has passed and the variable has the correct value assigned it should start counting the time correctly?

Global variables are initialized to zero and millis() starts at 0 so that is usually close enough.

your use of millis() is blocking in the same way as a simple delay() is blocking the program.

As long as it is OK that your program does “wait” and nothing else can be done by the program this works.
As soon as you want to do something in parallel you have to change to non-blocking timing.

The basic principle of doing things in parallel is like
reading the newspaper as you are waiting for the doctor or

watching TV as you are waiting for a pizza beeing long enough in the oven

as an allday example with easy to follow numbers
delay() is blocking. As long as the delay is “delaying” nothing else of the code can be executed.
Now there is a technique of non-blocking timing.

The basic principle of non-blocking timing is fundamental different from using delay()
You have to understand the difference first and then look into the code.
otherwise you might try to “see” a “delay-analog-thing” in the millis()-code which it really isn’t

Trying to see a “delay-analog-thing” in millis() makes it hard to understand millis()

Having understood the basic principle of non-blocking timing based on millis() makes it easy to understand.

imagine baking a frosted pizza
the cover says for preparation heat up oven to 200°C
then put pizza in.
Baking time 10 minutes

You are estimating heating up needs 3 minutes
You take a look onto your watch it is 13:02 (snapshot of time)
You start reading the newspaper and from time to time looking onto your watch
watch 13:02 not yet time
watch 13:03 not yet time
watch 13:04 not yet time 13:04 - 13:02 = 2 minutes is less than 3 minutes
watch 13:05 when did I start? 13:02? OK 13:05 - 13:02 = 3 minutes time to put pizza into the oven

New basetime 13:05 (the snapshot of time)
watch 13:06 not yet time
watch 13:07 not yet time
watch 13:08 not yet time (13:08 - 13:05 = 3 minutes is less than 10 minutes
watch 13:09 not yet time
watch 13:10 not yet time
watch 13:11 not yet time
watch 13:12 not yet time
watch 13:13 not yet time
watch 13:14 not yet time (13:14 - 13:05 = 9 minutes is less than 10 minutes
watch 13:15 when did I start 13:05 OK 13:15 - 13:05 = 10 minutes time to eat pizza (yum yum)

You did a repeated comparing how much time has passed by
This is what non-blocking timing does

In the code looking at “How much time has passed by” is done

currentTime - startTime >= bakingTime

bakingTime is 10 minutes

13:06 - 13:05 = 1 minute >= bakingTime is false
13:07 - 13:05 = 2 minutes >= bakingTime is false

13:14 - 13:05 = 9 minutes >= bakingTime is false
13:15 - 13:05 = 10 minutes >= bakingTime is TRUE time for timed action!!

if (currentTime - previousTime >= period) {

it has to be coded exactly this way because in this way it manages the rollover from Max back to zero
of the function millis() automatically

baldengineer.com has a very good tutorial about timing with function millis() too .

There is one paragraph that nails down the difference between function delay() and millis() down to the point:

The millis() function is one of the most powerful functions of the Arduino library. This function returns the number of milliseconds the current sketch has been running since the last reset. At first, you might be thinking, well that’s not every useful! But consider how you tell time during the day. Effectively, you look at how many minutes have elapsed since midnight. That’s the idea behind millis()!

Instead of “waiting a certain amount of time” like you do with delay(), you can use millis() to ask “how much time has passed”?

Me personal I use this version of non-blocking timing with a function.

it reduces assiging variables to a minimumand and IMHO shows the
basic principle “check if enough time has passed by for start the timed action” more clearly

here is a democode

void PrintFileNameDateTime() {
  Serial.println("Code running comes from file ");
  Serial.println(__FILE__);
  Serial.print("  compiled ");
  Serial.print(__DATE__);
  Serial.print(" ");
  Serial.println(__TIME__);  
}


boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - periodStartTime >= TimePeriod )
  {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}

unsigned long MyTestTimer = 0;  // variables MUST be of type unsigned long
const byte    OnBoard_LED = 2;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);
  
  if ( TimePeriodIsOver(MyBlinkTimer,BlinkPeriod) ) {
    digitalWrite(IO_Pin,!digitalRead(IO_Pin) ); 
  }
}



void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();

}


void loop() {
  BlinkHeartBeatLED(OnBoard_LED,100);

  if ( TimePeriodIsOver(MyTestTimer,1000) ) {

  }  

}

best regards Stefan

I remembered that the variables were initialized with a random number, apparently I was wrong.

I know I don’t use millis in the most optimal way, it only serves to call the client.loop () function constantly to prevent the connection from timing out.

In the various programs I wrote for my home automation system I’ve never had to do multiple things at the same time. Generally the program waits for an MQTT message and calls a function based on that message.
I still really appreciated your explanation, thanks a lot.

You are probably thinking of local variables. They get whatever value happened to be in the memory allocated for them.

If you are using multiple timers, then my millisDelay class simplifies things by keeping all the variables and running/stopped flags in the class.
see How to write Timers and Delays in Arduino
You should combine that with a task base approach to your sketch to avoid the ‘delay’ mentioned above.
see my tutorial Multi-tasking in Arduino
multitaskingDiagramSmall

There is a simple alternative to do non blocking operations: use a C++ timer object to encapsulate the millis() housekeeping.

#include "WawiTimer.h" 
// WawiTimer is part of WawiLib demos.

WawiTimer timer1, timer2; // add as many timer objects as you need

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

void loop() 
{
  if (timer1.isZero())
  {
    Serial.print("Timer1 1000 ms has expired; ");
    Serial.println("Restarting timer 1:");
    timer1.setMs(1000);
  }

  if (timer2.isZero())
  {
    Serial.print("Timer2 5000 ms has expired; ");
    Serial.println("Restarting timer 2:");
    timer2.setMs(5000);
  }

  timer1.loop();
  timer2.loop();
}

// examples where you can actually see and change the values of the timer count
// in a window you can put on the screen (variable table as in PLC or excel grid): 
// www.SylvesterSolutions.com

In order to make this work the only thing you have to do is to add WawiTimer.h and WawiTimer.cpp to your project directory. I added the zipped file with this example to this post.
WawiTimerDemo.zip (1.5 KB)

hi JOHI
Looking at the example .ino it seems that once the timer reaches zero it stays there and timer.isZero() will always return true (or am I missing something).

What is the suggested example code for a one shot timer that only executes the
if (timer1.isZero())
once.

In millisDelay the call to justFinished() only returns true once after the timer times out so a one shot timer looks like.

#include "millisDelay.h"
millisDelay timer;
void setup() {
  timer.start(5000); // 5sec
}
void loop() {
  if (timer.justFinished()) { // only returns true once for each start( )
      // timer timed out do something here just once
 }
}

@drmpf
The key is that in the code the timer is restarted by the line
timer1.setMs() to so it can restart its cycle, if you let it be, indeed it stays at 0.
So depending on what you like to do with it, you can restart it at the same value, at other values, whatever you like. It is similar to what PLC timers do.
The advantage of the separate timer.loop() is that if the isZero() or justFinished() is not called, the timing keeps going. This saves you time finding bugs.

My question was what happens if you remove the timer1.setMs()? How do you code a single shot timer.

If you want to do single shot: at first sight you need a helper variable. I could put something similar in the object, but it might reduce versatility: some hardware timers also stop at zero others act in a different manner…

#include "WawiTimer.h" 

WawiTimer timer1;

void setup() 
{
  Serial.begin(9600);
  pinMode(3,INPUT_PULLUP);
}

bool bTaskRunning=false;

void loop() 
{
  if (digitalRead(3)==0) // pin pulled to ground with switch:
  {
    timer1.setMs(1000);  
    bTaskRunning = true;
  }  
  if (bTaskRunning && timer1.isZero())
  {
    Serial.print("Timer1 1000 ms has expired; ");
    Serial.println("not Restarting timer 1:");
    bTaskRunning=false;
  }
  timer1.loop();
}

// examples where you can actually see and change the values of the timer count
// in a window you can put on the screen (variable table as in PLC or excel grid): 
// www.SylvesterSolutions.com

Thanks for the explanation. Yes I had to add a bool in the millisDelay class to handle this.