Delay in reconnect() blocks execution of other code

Hi there.
I have switches controlling relays (onebutton library OneButton - Arduino Reference ) and when client is connected I can control relays both with MQTT (PubSubClient - Arduino Reference) and switches.
When there is no connection I cannot do anything as delay in reconnect() blocks execution of other code.

void loop() {
  // keep watching the push buttons:
  button1.tick();
  button2.tick();
  button3.tick();
  button4.tick();
  button5.tick();
  button6.tick();
  button7.tick();
  button8.tick();
  button9.tick();
  button10.tick();
  button11.tick();
  button12.tick();
  button13.tick();
  button14.tick();
  button15.tick();
  button16.tick();
  button17.tick();
  button18.tick();
  button19.tick();
  button20.tick();
  button21.tick();
  button22.tick();
  button23.tick();
  button24.tick();
  button25.tick();
  button26.tick();
  button27.tick();
  button28.tick();
  button29.tick();
  button30.tick();
  button31.tick();
  button32.tick();
  button33.tick();
  button34.tick();
  button35.tick();
  button36.tick();


  // You can implement other code in here or just wait a while 
  delay(10);
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
} // end of loop

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Subscribe
      client.subscribe(sub1);
      client.subscribe(sub2);
      client.subscribe(sub3);
      client.subscribe(sub4);
      client.subscribe(sub5);
      client.subscribe(sub6);
      client.subscribe(sub7);
      client.subscribe(sub8);
      client.subscribe(sub9);
      client.subscribe(sub10);
      client.subscribe(sub11);
      client.subscribe(sub12);
      client.subscribe(sub13);
      client.subscribe(sub14);
      client.subscribe(sub15);
      client.subscribe(sub16);
      client.subscribe(refresh);
      // Publish states
      publishAll();
    } 
    else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

I tried to use millis() but could not make it to work.
How to allow other code to be executed when trying to reconnect?

It would certainly be possible. What would that code do?

At the moment when there is no ethernet connection the loop is stuck in void reconnect() because it tries to reconnect.
If it fails to reconnect the delay(5000) is executed and I cannot read button presses.

else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);

Show us ?

This is creating the issue.

Use flowcharts and draw how the execution should work.
Check the example/topic Do several things at the same time, Blink without delay.

You will have to partly rewrite parts of the code.

unsigned long myTime = 0;
unsigned long rememberedTime = 0;
unsigned long difference = 0;

else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      myTime = millis();

    difference = myTime - rememberedTime;
    if (difference >= 5000UL) {
    rememberedTime = myTime;
    Serial.println(myTime);
    // Should I put reconnect() here???
    // I don't know what to do next
    }
  }

I took it from one of the examples and as long as it worked I did't think much of it :wink:

while (!client.connected())

What would suggest to use instead of while?

Check the example/topic Do several things at the same time, Blink without delay.

I will have a look

You have made reconnect() not wait until a connection is successful

void loop() {
  // keep watching the push buttons:
  button1.tick();
  button2.tick();
  button3.tick();
  button4.tick();
  button5.tick();
  button6.tick();
  button7.tick();
  button8.tick();
  button9.tick();
  button10.tick();
  button11.tick();
  button12.tick();
  button13.tick();
  button14.tick();
  button15.tick();
  button16.tick();
  button17.tick();
  button18.tick();
  button19.tick();
  button20.tick();
  button21.tick();
  button22.tick();
  button23.tick();
  button24.tick();
  button25.tick();
  button26.tick();
  button27.tick();
  button28.tick();
  button29.tick();
  button30.tick();
  button31.tick();
  button32.tick();
  button33.tick();
  button34.tick();
  button35.tick();
  button36.tick();


  // You can implement other code in here or just wait a while
  delay(10);
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
} // end of loop

void reconnect() {
  static unsigned long lastTime;
  const unsigned long reconnectInterval = 5000UL;
  
  if (!client.connected() && millis() - lastTime >= reconnectInterval) {
    lastTime = millis();
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Subscribe
      client.subscribe(sub1);
      client.subscribe(sub2);
      client.subscribe(sub3);
      client.subscribe(sub4);
      client.subscribe(sub5);
      client.subscribe(sub6);
      client.subscribe(sub7);
      client.subscribe(sub8);
      client.subscribe(sub9);
      client.subscribe(sub10);
      client.subscribe(sub11);
      client.subscribe(sub12);
      client.subscribe(sub13);
      client.subscribe(sub14);
      client.subscribe(sub15);
      client.subscribe(sub16);
      client.subscribe(refresh);
      // Publish states
      publishAll();
    }
    else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
    }
  }
}

Your loop() code may need to adjust as well since you may not be connected after the call the reconnect(). It may be a if/else clause. This allows loop() to cycle quickly and react to your buttons.

FYI... You should use arrays for those buttons....

1 Like

Exactly! I have the same thought.
Just check the rest of the code to manage during the time reconnection is in progress.

1 Like

When you use delay() and process every button every time that void loop() runs, you don't learn how fast non-blocked AVR code runs... many 10's of times per millisecond.

Make the buttons an array ( button[ 36 ] ) and make ( byte buttonIndex; ) to process only 1 button per pass through void loop(). They will all be done is less than 1ms. This will shorten the code and make void loop() run more often, becoming smoother and more responsive.

I would suggest making reconnect into a task with its own timer.
First it checks if the connection is down, if not then return.
Next it checks millis() - lastTry >= 5000L, if not then return.
lastTry = millis()
Then the connect code you have minus the delay now as a millis time check in step 2.

Everything done through the connection should check the connection and be able to execute later if not connected "now".

And do not use either "while()" or "delay()". Both are toxic. :face_with_raised_eyebrow:

Clearly not the way to go. :grin:

Do some practice projects with millis() before getting back to yours.

Thanks you for rewriting the code.
I am trying trying simple for loop

for (int i = 1; i <= 4; i++) {
    button[i].tick();
  }

to replace

  button1.tick();
  button2.tick();
  button3.tick();
  button4.tick();

but I get

exit status 1
button' was not declared in this scope

All of the buttons are set up before void setup () so should be set to global.

OneButton button1(37,true);  
OneButton button2(36,true); 
OneButton button3(35, true); 
OneButton button4(34, true); 

What am I missing?
Do I need to define them and then put them into an array?

int myButtons[] = {37, 36, 35, 34};
int myButtons2[] = {button1, button2, button3, button4};

Will both of the arrays work?

Hello gggg1981gggg
Either run a button tutorial to gain the knowledge how the libary works or brew a button manager by yourself.
Have a nice day and enjoy coding in C++.


I have read the library's documentation and I set up all of the buttons according to GitHub - mathertel/OneButton: An Arduino library for using a single button for multiple purpose input.
It works when:

  button1.tick();
  button2.tick();
  button3.tick();
  button4.tick();
  button5.tick();
  button6.tick();
  button7.tick();
  button8.tick();
  button9.tick();
  button10.tick();
  button11.tick();
  button12.tick();
  button13.tick();
  button14.tick();
  button15.tick();
  button16.tick();
  button17.tick();
  button18.tick();
  button19.tick();
  button20.tick();
  button21.tick();
  button22.tick();
  button23.tick();
  button24.tick();
  button25.tick();
  button26.tick();
  button27.tick();
  button28.tick();
  button29.tick();
  button30.tick();
  button31.tick();
  button32.tick();
  button33.tick();
  button34.tick();
  button35.tick();
  button36.tick();

It stopped working when I tried to put that in for loop.
I am still learning and trying to put all of the pieces together :wink:

I don't know your button library, I have button, multi-button and matrix-button code of my own.

// array of pointers to OneButton objects.
OneButton *myButton[ 4 ] = ( &button1, &button2, &button3, &button4 );

Either of these methods will work for declaring/using your buttons [pick ONE]

#include "OneButton.h"

// method 1
const int buttonPins[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
const int nButtons1 = sizeof(buttonPins) / sizeof(buttonPins[0]);

OneButton **myButton1;

// method 2
OneButton b1(2);
OneButton b2(3);
OneButton b3(4);
OneButton b4(5);

OneButton myButton2[] = { b1, b2, b3, b4 };
const int nButtons2 = sizeof(myButton2) / sizeof(myButton2[0]);


void setup() {
// method 1
  myButton1 = new OneButton *[nButtons1];
  for (int i = 0; i < nButtons1; i++) {
    myButton1[i] = new OneButton(buttonPins[i]);
    // do anditional function attach, etc
  }

// method 2
// nothing more to do in setup unless you want to attach functions, etc.

}

void loop() {
  // keep watching the push buttons:
  // method 1
  for (int i = 0; i < nButtons1; i++) {
    myButton1[i]->tick();
  }

  // method 2
  for (int i = 0; i < nButtons2; i++) {
    myButton2[i].tick();
  }

}
1 Like

I looked into oneButton.h to see if it has a constructor that can make arrays.. it doesn't but could be added since there is an init function.

I have a new-to-me way to debounce a button. I keep a history byte of the last 8 pin reads done every 500 micros. Every new read is added after the old bits are left shifted once. --- and that's all the handler does is know the pin number and update history and trigger on timer.

Functions/tasks in loop() can all see that INPUT_PULLUP pin history.

01111111 == 127 is pin down and 7 reads (stability) of pin up, call it Release.
10000000 == 128 is pin up and 7 pin down (stability), call it Press.

0 is Down
255 is Up

Everything else is Bounce. That simple. Most sketches only need to look for 128 (Press).

But this made me think how to tighten bounce detection so ver. 2 is OTW.
It will cost some cycles but tighten bounce detection.

Some things you should know when using multiple digital inputs:

The pins are arranged as 8 per port.
AVR ports have 3 registers; the PIN register, the DDR-egister and the PORT register.
The DDR = Data Direction Register sets whether each pin is Input or Output.
The PORT register sets whether each pin is Low or High.
The PIN register is to read all the pin states at once has a superpower. If you write a byte with any HIGH bits, the corresponding pins will be toggled in the PORT register.

On an Uno, some pins already have a purpose but there are 3 Ports with 6 consecutive pins open meaning that up to 6 buttons can be read in 1 cycle and the used pins bit-masked off in the next cycle. You do this with Arduino search on direct port manipulation.

It is difficult to say which is more efficient code in terms of how many cycles it takes to poll and debounce the pin readings, but my preferred debounce code uses a millis() "start"variable and a "state" flag.

This code is for multiple buttons - as many as you might reasonably need. :grin: However, a variant could debounce 8 (or 16 ...) at a time such as for a keyboard.

char bstate1 = 0;
char bstate2 = 0;
char bstate3 = 0;
char bstate4 = 0;
unsigned long bcount1 = 0; // button debounce timer.  Replicate as necessary.
unsigned long bcount2 = 0;
unsigned long bcount3 = 0;
unsigned long bcount4 = 0;

// Have we completed the specified interval since last confirmed event?
// "marker" chooses which counter to check
// Routines by Paul__B of Arduino Forum

// General timeout routine as used in butndown()
boolean timeout(unsigned long *marker, unsigned long interval) {
  if (millis() - *marker >= interval) { 
    *marker += interval;    // move on ready for next interval
    return true;       
  } 
  else return false;
}

// Deal with a button read; true if button pressed and debounced is a new event
// Uses reading of button input, debounce store, state store and debounce interval.
// Routines by Paul__B of Arduino Forum
// This routine is passed the button read value and pointers (&bstate) to the timer and state variables for the particular button, returns a debounced Boolean for success.

boolean butndown(char button, unsigned long *marker, char *butnstate, unsigned long interval) {
  switch (*butnstate) {               // Odd states if was pressed, >= 2 if debounce in progress
  case 0: // Button up so far, 
    if (button == HIGH) return false; // Nothing happening!
    else { 
      *butnstate = 2;                 // record that is now pressed
      *marker = millis();             // note when was pressed
      return false;                   // and move on
    }

  case 1: // Button down so far, 
    if (button == LOW) return false; // Nothing happening!
    else { 
      *butnstate = 3;                 // record that is now released
      *marker = millis();             // note when was released
      return false;                   // and move on
    }

  case 2: // Button was up, now down.
    if (button == HIGH) {
      *butnstate = 0;                 // no, not debounced; revert the state
      return false;                   // False alarm!
    }
    else { 
      if (millis() - *marker >= interval) {
        *butnstate = 1;               // jackpot!  update the state
        return true;                  // because we have the desired event!
      }
      else 
        return false;                 // not done yet; just move on
    }

  case 3: // Button was down, now up.
    if (button == LOW) {
      *butnstate = 1;                 // no, not debounced; revert the state
      return false;                   // False alarm!
    }
    else { 
      if (millis() - *marker >= interval) {
        *butnstate = 0;               // Debounced; update the state
        return false;                 // but it is not the event we want
      }
      else 
        return false;                 // not done yet; just move on
    }
  default:                            // Error; recover anyway
    {  
      *butnstate = 0;
      return false;                   // Definitely false!
    }
  }
}

The point is that this code is not called at or only at certain times, but every time you pass through the loop, allowing that other things may delay the loop to widely varying degrees. So it does not police any particular number of polls of the button, but however many polls happen to occur within the specified interval of milliseconds (generally about 5 to 10). :sunglasses:

I have a debouncer that checks state every time and looks for a given stable period to flag a stable transition.

And then I worked out that I didn't need all of that. Bounce happens over about 2ms but longer with dirty buttons, it always ends with a final transition to a new stable state. When history is 0, 127, 128 or 255, there's a stable state.

What I do times the reads far enough apart to use 1 byte history to measure a stable state longer than a bounce. Reading every 1 or 2 ms will also work but will take longer and still be "instant" in human terms. Also I time with 16 bit values instead of 32.

If you do use the millis clock, the accuracy is +/- 1ms. That's not good for short intervals. When it matters, use micros() -- as Nick Gammon posted 10 years ago.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.