Give the user 10 seconds to make a selection in the menu

Hi Guys, I hope everyone is having a nice summer. It is almost over up here in the North. Can someone can help with my question? I want to give the user 10 seconds to decide in the menu. If they don't hit 1, 2, or 3 within ten seconds, the program should run option 1 for the user. Here's my sketch:

char myArray[10];
void setup() {
  Serial.begin(9600); 
  Serial.println(F(""));
  Serial.println(F("Select from the following:"));
  Serial.println(F("  1 - Configure this device"));
  Serial.println(F("  2 - Reset to factory defaults"));
  Serial.println(F("  3 - Refresh Page"));
  while (1) {
    while (Serial.available() == 0) {}
    byte n = Serial.available(); //there is at least one char
    if ( n != 0)
    {
      byte m = Serial.readBytesUntil('\n', myArray, 10);
      myArray[m] = '\0'; //insert null-character
    }
    if (myArray[0] == '1') {
      Serial.println("You chose option 1");
    }
    else if (myArray[0] == '2') {
      Serial.println("You chose option 2");
    }
    else if (myArray[0] == '3') {
      Serial.println("You chose option 3");
    }
    break;
  }
}
void loop(){}

I tried using attachInterrupt() which I thought was going to work awesome with a cool count down but, then I couldn't terminate it and it kept looping. detachInterrupt() didn't seem to be a solution for that. Any suggestions would be greatly appreciated.

“It is almost over up here in the North“
Where :thinking:


Write your sketch using State Machine techniques.

When the selections are first offered, start a 10 second TIMER.

When the TIMER expires, run option 1 (one).

Turn your thinking around…
Consider the interval as a timeout, not a delay or interval.

when the menu is displayed, start a millis() timer, then if that ‘expires’, simply exit the menu.

LarryD I'm in Maine. State Machines look advanced for a novice. I'll spend some time on them. Thank you for the suggestion. lastchancename I'll noodle over your suggestion to "start a millis() timer". I found this post on millis() for timing which also talks about State Machine. I'll see if I can apply that. Thank you both!

Wow ( LarryD & lastchancename) you guys nailed it. It works beautifully:

char myArray[10];
long interval = 5000;           //time to display menu before running option 1

void setup() {
  Serial.begin(9600); 
  Serial.println(F(""));
  Serial.println(F("Select from the following:"));
  Serial.println(F("  1 - Configure this device"));
  Serial.println(F("  2 - Reset to factory defaults"));
  Serial.println(F("  3 - Refresh Page"));
  while(1){
    unsigned long currentMillis = millis(); // grab the current time.
    if(currentMillis > interval) {
      Serial.println("Time out! Going with option 1");
      break;
    }
    byte n = Serial.available(); //there is at least one char
    if ( n != 0)
    {
      byte m = Serial.readBytesUntil('\n', myArray, 10);
      myArray[m] = '\0'; //insert null-character
    }
    if (myArray[0] == '1') {
      Serial.println("You chose option 1");
      break;
    }
    else if (myArray[0] == '2') {
      Serial.println("You chose option 2");
      break;
    }
    else if (myArray[0] == '3') {
      Serial.println("You chose option 3");
      break;
    }
  }
}
void loop(){   
}

Thank you so much!

Here is a State Machine you can build on.


const byte heartbeatLED        = 13;

char myArray[10];


//timing stuff
unsigned long heartbeatMillis;
unsigned long commonMillis;
unsigned long delayInterval;


//State Machine stuff
//use names that mean something to you
enum Machine {StartState, StateOne, StateTwo, Option1, Option2, Option3, EndState};
Machine mState = StartState;


//********************************************^************************************************
void setup()
{
  Serial.begin(9600);

  pinMode(heartbeatLED, OUTPUT);


} //END of   setup()


//********************************************^************************************************
void loop()
{
  //*********************************                          heartbeat TIMER
  //is it time to toggle the heartbeatLED (every 500ms)?
  if (millis() - heartbeatMillis >= 500ul)
  {
    //restart this TIMER
    heartbeatMillis = millis();

    //toggle the heartbeatLED
    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }

  //*********************************
  //service our state Machine
  checkStateMachine();


  //*********************************
  //are we are waiting for input from the user
  if (mState == StateTwo)
  {
    //********************
    //are there receive characters ?
    if (Serial.available() > 0)
    {
      byte n = Serial.available(); //there is at least one char

      //*********
      if ( n != 0)
      {
        byte m = Serial.readBytesUntil('\n', myArray, 10);
        myArray[m] = '\0'; //insert null-character
      }

      //*********
      if (myArray[0] == '1')
      {
        //next state in our machine
        mState = Option1;
      }

      //*********
      else if (myArray[0] == '2')
      {
        //next state in our machine
        mState = Option2;
      }

      //*********
      else if (myArray[0] == '3')
      {
        //next state in our machine
        mState = Option3;
      }
    }
  }

  //*********************************
  //other non blocking code goes here
  //*********************************


} //END of   loop()


//                            c h e c k S t a t e M a c h i n e ( )
//********************************************^************************************************
//servicing the "current state" in our State Machine and doing what is required.
void checkStateMachine()
{
  switch (mState)
  {
    //*****************
    case StartState:
      {
        //next state in our machine
        mState = StateOne;
      }
      break;

    //*****************
    case StateOne:
      {
        Serial.println(F(""));
        Serial.println(F("Select from the following:"));
        Serial.println(F("  1 - Configure this device"));
        Serial.println(F("  2 - Reset to factory defaults"));
        Serial.println(F("  3 - Refresh Page"));

        //new interval for the common TIMER (10seconds)
        delayInterval = 10000ul;

        //restart the common TIMER
        commonMillis = millis();

        //next state in our machine
        mState = StateTwo;
      }
      break;

    //*****************
    case StateTwo:
      {
        //has the common TIMER expired ?
        if (millis() - commonMillis >= delayInterval)
        {
          //10 seconds has gone by
          //next state in our machine
          mState = Option1;
        }
      }
      break;

    //*****************
    case Option1:
      {
        Serial.println("\n You chose option 1");

        //new interval for the common TIMER (3 seconds)
        delayInterval = 3000ul;

        //restart the common TIMER
        commonMillis = millis();

        //next state in our machine
        mState = EndState;
      }
      break;

    //*****************
    case Option2:
      {
        Serial.println("\n You chose option 2");

        //new interval for the common TIMER (3 seconds)
        delayInterval = 3000ul;

        //restart the common TIMER
        commonMillis = millis();

        //next state in our machine
        mState = EndState;
      }
      break;

    //*****************
    case Option3:
      {
        Serial.println("\n You chose option 3");

        //new interval for the common TIMER (3 seconds)
        delayInterval = 3000ul;

        //restart the common TIMER
        commonMillis = millis();

        //next state in our machine
        mState = EndState;
      }
      break;

    //*****************
    case EndState:
      {
        //has the common TIMER expired ?
        if (millis() - commonMillis >= delayInterval)
        {
          //3 seconds has gone by
          //next state in our machine
          mState = StartState;
        }
      }
      break;

  } //END of   switch/state

} //END of   checkStateMachine()

nope

This code will only run 1 time right after the controller reset. For it to work stably, you need to store the current millis when entering the menu cycle and start your timeout from this moment.

Hello b707:
Regarding your comment "This code will only run 1 time right after the controller reset.". This is a multi file sketch and this code is of course in a function by itself. I plan on just calling the function again if necessary.

Regarding "For it to work stably"
unsigned long currentMillis = millis();
I thought the above stored the current millis. Every time the function is run, it grabs a new millis. Do I still have potential stability issues?

In your sketch, you have:

unsigned long currentMillis = millis(); // grab the current time.
if(currentMillis > interval) {

In the State Machine example offered you in post # 6 we have something similar:

//has the common TIMER expired ?
if (millis() - commonMillis >= delayInterval)

Can you see the problem :thinking: ?

yes

Millis is a counter that increments continuously after the controller starts up. Immediately after the reset, it is equal to zero, and after a minute it is already 60 thousands.

In this piece of code, you are comparing the current value of millis with the variable interval=5000. If you do this immediately after starting, you have a millis value less than 5000 and therefore the loop will wait for some time, although less than the expected 5 seconds.
If you enter this loop after a minute, then you have a value of 60k in millis and the condition

if(currentMillis > interval) {

is triggered immediately, there will be no timeout at all

My C skills aren't allowing me to see the problem but, I'll take your and b707's word and for it. I am seeing a problem in the serial monitor where it keeps displaying my count down even after I've left the function and entered another one. Something is wrong. b707's comment "there will be no timeout at all" is probably the issue.
I'm working with that code you gave me in #6 right now. Thank you for posting that. I pasted it into a sketch and tinkering and trying to understand. Need time....

The issue can be solved very simply.
You need to compare the duration of the interval not with the current value of millis, but with the difference between the current value and the value at the beginning of the timeout

Read the examples in the post #9 again

If you have questions, ask . . .

The point behind that sample sketch is to enable different pieces of code at an appropriate time and when certain events happen, example: when a switch closes, when an analog reaches a value, when a TIMER expires, or in you case, when a serial receive character comes in.

Hey LarryD, The sketch provided in post #6 looks to be very useful. Thank you. I understand it although some code I had to tinker with to figure out. !digitalRead(heartbeatLED)) was new to me as was enum and 10000ul (unsigned long). Thanks for the introduction. I'm learning and hope to implement this code into my core this week. You and the other guys on this forum are awesome.

Yup, you're right. Making changes.