Sequence of timed functions

Hi,

I’m trying to understand how to do a sequence of timed events, however, I’m getting unexpected outputs. The following code was originally taken from the BWD example:

unsigned long startPre = 0;        
unsigned long startWait = 0;
unsigned long startShot = 0;

int preDone = 0;
int waitDone = 0;
int shotDone = 0;

const long preTime = 2000;
const long waitTime = 5000;
const long shotTime = 5000;

void setup() {
Serial.begin(115200);
  Serial.println("Start....");
}

void loop() {
  
 if (preDone < 1 ) {
    pre();
  } 
    
  if (preDone = 1) {
    wait();
  } 
  
  if (waitDone = 1) {
    shot();
  } 
  if (shotDone = 1) {
    Serial.println("Finished...");
  }
}


void pre(){           
unsigned long currentMillis = millis();
  if (currentMillis - startPre <= preTime) {
    Serial.println("Preinfusion");
    startPre = currentMillis;
  } else {
    preDone = 1;
  }
}

void wait(){
  unsigned long currentMillis = millis();
  if (currentMillis - startWait <= waitTime) {
      Serial.println("Wait");
    startWait = currentMillis;
  } else {
    waitDone = 1;
}
}

void shot(){
  unsigned long currentMillis = millis();
  if (currentMillis - startShot <= shotTime) {
    Serial.println("Extraction");
    startShot = currentMillis;
  } else{
   shotDone = 1;
  }
}

The results in Serial Monitor:

Start....
Preinfusion
Wait
Extraction
Finished...
Wait
Extraction
Finished... (etc..)

I’m trying to understand the basics before I delve into making a more complicated Finite State Machine.

Any help would be appreciated at this stage.

  if (preDone = 1) {Oops

The equality (comparison) operator is '=='. The single '=' is the assignment operator.

ahh! simple things, eh! I knew this is the back of my mind but failed to realise it!

Thanks for help and time on this!

You might want to consider a switch/case scenario instead of a bunch of if statements. You, should also move all those timing statemennts in each of your functions into a seperate timer function, or better yet a class.

A simple timer class you can use "off the shelf" is the elapsedMillis class. There's a plethora of task schedulers out there, but this is a good starter.

By the way, I hope you also roast your own coffee. It's the only way to get a really great espressso!

GM7DHA:
I knew this is the back of my mind but failed to realise it!

I miss it and I thought at one stage it had been promoted to the front of my mind. Spent an hour chasing a problem just the other day, and = vs == was the culprit.

Assuming your forum name is your ham call, I love the Jamesh Bond loop on the roof of your car on qrz!

elvon_blunden:
I miss it and I thought at one stage it had been promoted to the front of my mind. Spent an hour chasing a problem just the other day, and = vs == was the culprit.

Assuming your forum name is your ham call, I love the Jamesh Bond loop on the roof of your car on qrz!

Hah! yes it is. My magnetic loop for HF. Very rarely on air these days.

GM7DHA:
Very rarely on air these days.

Me neither. The nylon string holding the far end of my HF random wire in the tree snapped the other day, and I cba to climb up and sort that. Not that HF's been up to much lately anyway. So that leaves VHF repeater natter in the car, and I'm very bored with the same old same old every morning as folk complain about the weather and the traffic.

elvon_blunden:
Me neither. The nylon string holding the far end of my HF random wire in the tree snapped the other day, and I cba to climb up and sort that. Not that HF's been up to much lately anyway. So that leaves VHF repeater natter in the car, and I'm very bored with the same old same old every morning as folk complain about the weather and the traffic.

Yeah, I blame the Internet! I just tried out HF for a while as in the UK all Class B licenses are now able to use the HF bands. Was interesting for a while.

DBMcDonald:
You might want to consider a switch/case scenario instead of a bunch of if statements. You, should also move all those timing statemennts in each of your functions into a seperate timer function, or better yet a class.

A simple timer class you can use "off the shelf" is the elapsedMillis class. There's a plethora of task schedulers out there, but this is a good starter.

By the way, I hope you also roast your own coffee. It's the only way to get a really great espressso!

I shall look into the timers. Really need to keep things elegant and easy to follow. I'm just finding my feet with Arduino at the moment.

Yes, I use a Gene Cafe CBR101A.. however, I'm finding green beans are increasing in price rapidly!

Instead of a bunch of bool flags, a better way to do a sequence is with a single step number value that you increase each time you go to the next step.

If each of your steps is simple and doesn't require complex behavior, you can use an array to store information about the steps like what outputs are turned on and how long it lasts, and just use your step count as the index to that array.

This is going to be a problem since it re-starts the timer any time the timer has not expired:

void wait(){
  unsigned long currentMillis = millis();
  if (currentMillis - startWait <= waitTime) {
      Serial.println("Wait");
    startWait = currentMillis;   // ???????
  } else {
    waitDone = 1;
}
}

This might work:

void wait(){
  unsigned long currentMillis = millis();
  if (startWait == 0)
    startWait = currentMillis;
  if (currentMillis - startWait <= waitTime) {
      Serial.println("Wait");
  } else {
    waitDone = 1;
    startWait = 0;
}
}

Rather than having a flag for each state, a common practice is to have a ‘state variable’ that keeps track of which state your sketch is in:

enum States {Pre, Wait, Shot, Idle} State;

unsigned long StateStartTime = 0;

void SetState(States s, const char *name)
{
  State = s;
  StateStartTime = millis();
  Serial.print("Entering state \"");
  Serial.print(name);
  Serial.println("\"");
}

const unsigned long PreTime = 2000;
const unsigned long WaitTime = 5000;
const unsigned long ShotTime = 5000;

void setup()
{
  Serial.begin(115200);
  Serial.println("Start....");
  SetState(Pre, "Preinfusion");
}

void loop()
{
  switch (State)
  {
    case Pre: pre(); break;
    case Wait: wait(); break;
    case Shot: shot(); break;
    case Idle: break;
  }
}

void pre()
{
  unsigned long currentMillis = millis();
  if (currentMillis - StateStartTime >= PreTime)
  {
    Serial.println("Preinfusion Done");
    SetState(Wait, "Wait");
  }
}

void wait()
{
  unsigned long currentMillis = millis();
  if (currentMillis - StateStartTime >= WaitTime)
  {
    Serial.println("Wait Done");
    SetState(Shot, "Extraction");
  }
}

void shot()
{
  unsigned long currentMillis = millis();
  if (currentMillis - StateStartTime >= ShotTime)
  {
    Serial.println("Extraction Done");
    SetState(Idle, "Idle");
  }
}

Excellent information! Thanks very much. I've got it simulating as required now.

I'm going to try setting it up using the "state variable" as it looks much neater and easier to follow, which will be handy when I implement more code.

Next step initially is to activate the sequence of states on a button press:
One button for this automated sequence (Pre/Wait/Extraction)
One button for a manual ON and OFF for extraction only

This will have my machine running whilst I code my menu system (which could take some time)

Again, thanks for your time and help - I'm slowly starting to understand how things work. If I can only "see" my syntax errors things won't be so bad!

GM7DHA:
Next step initially is to activate the sequence of states on a button press:

With the state machine mode that is easy:

  • Add the new WaitingForButton state.
  • Make WaitingForButton the initial state (in setup()).
  • When in the WaitingForButton state, if the button is active, set the state to Pre.

If you want to be able to start again by pressing the button again, have the Idle state go to the WaitingForButton state.

Thanks John,

I was originally trying to run your code in TinkerCad but it was throwing errors, however, I tried it on a spare Nano and worked ok (I had no doubts your code was fine!). Thanks for showing me how simple it can be.

I’ve managed to get it working using a Buttons library (keeping code neat) for two buttons.

Here is my code:

#include <EasyButton.h>

enum States {Pre, Wait, Shot, Idle, WaitForButton} State;

unsigned long StateStartTime = 0;

const unsigned long PreTime = 2000;
const unsigned long WaitTime = 5000;
const unsigned long ShotTime = 5000;

EasyButton shotButton(4, 35, true); //D4 pin
EasyButton steamButton(3, 35, true); //D3 Pin

  void SetState(States s, const char *name)
{
  State = s;
  StateStartTime = millis();
  Serial.print("Entering state \"");
  Serial.print(name);
  Serial.println("\"");
}
  

void setup()
{
  Serial.begin(115200);
  Serial.println("Start....");
  SetState(WaitForButton, "Waiting for button");
  shotButton.begin();
  steamButton.begin();
}

void loop()
{
  switch (State)
  {
    case Pre: pre(); break;
    case Wait: wait(); break;
    case Shot: shot(); break;
    case Idle: break;
    case WaitForButton: waitForButton(); break;
  }
}

void pre()
{
  unsigned long currentMillis = millis();
  if (currentMillis - StateStartTime >= PreTime)
  {
    Serial.println("Preinfusion Done");
    SetState(Wait, "Wait");
  }
}

void wait()
{
  unsigned long currentMillis = millis();
  if (currentMillis - StateStartTime >= WaitTime)
  {
    Serial.println("Wait Done");
    SetState(Shot, "Extraction");
  }
}

void shot()
{
  unsigned long currentMillis = millis();
  if (currentMillis - StateStartTime >= ShotTime)
  {
    Serial.println("Extraction Done");
    SetState(WaitForButton, "Waiting for Button");
  }
}


void waitForButton()
{
  shotButton.read();
  steamButton.read();
  shotButton.onPressed(pre);
  steamButton.onPressed(shot);
}

[I’ve currently got steamButton going to shot(), this is just as a test to make sure it’s working]

As you see I’m always in the waitForButton() state, is this good practise If I’m going to be adding a menu system? I guess the menu system will have to be implemented in a ‘state’ of its own? (effectively doing away with waitForButton()).

I just released Fiber library in GitHub which simplifies this kind of code. Out of curiosity implemented your case with the library:

#include "EasyButton.h"
#include "ffunc.h"

EasyButton shotButton(4); //D4 pin
EasyButton steamButton(3); //D3 Pin
long cur_time, prev_time;
ffunc_callstack cs;

struct ffunc_check_button
{
  FFUNC()
  {
    FFUNC_BEGIN
    if(shotButton.IsPushed())
    {
      Serial.println("Preinfusion");
      FFUNC_CALL(ffunc_sleep, (2.0f));
      Serial.println("Preinfusion Done");
      Serial.println("Wait\r\n");
      FFUNC_CALL(ffunc_sleep, (5.0f));
      Serial.println("Wait Done");
      Serial.println("Extraction");
      FFUNC_CALL(ffunc_sleep, (5.0f));
      Serial.println("Extraction Done");
    }
    else if(steamButton.IsPushed())
    {
      Serial.println("Extraction\r\n");
      FFUNC_CALL(ffunc_sleep, (5.0f));
      Serial.println("Extraction Done");
    }
    FFUNC_END
  }
};

void setup()
{
  Serial.begin(115200);
  Serial.println("Start....");
  cur_time=millis();
  prev_time=cur_time;
}

void loop()
{
  if(!cs.tick((cur_time-prev_time)/1000.0f))
    FFUNC_START(cs, ffunc_check_button, ());
  prev_time=cur_time;
  cur_time=millis();
}