Perplexed around inconsistencies with non blocking intervals

Hi all after much research, reading, articles and attempts I am now looking to you the embedded God's to help fill in my evident conceptual gap. I have taken pieces of my code that works sort of but does not work consistently in this non-blocking messy state machine form, you can tell I am an amateur, for which I apologise.

Here is my big struggle:

// Non-Blocking Dosing system, button press calls a routine/function
//timing is proving a conceptual challenge

#include <Adafruit_MotorShield.h>
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *Dose1 = AFMS.getMotor(1);

/*ROUTINE STATES*/
//when one 1 stage 0f dosing is complete, the state is returns to false, we use this to progress the routine.

//ROUTINE 1: 1ST CHEM 1, 2ND CHEM 2, 3RD CHEM 3, 4TH CHEM 4//
boolean dose1aReady = false; //1st Chem
boolean dose1aState = false; // 2nd Chem


/*TIMING VARIABLES */

//Timing Variables for routine 1 D1, D2, D3, D4 eventually//
unsigned long mD1wait_interval = 500; //
unsigned long mD1run_interval = 15000; //
unsigned long mD1c_on; // variable for how long this state has run for
unsigned long ROUT1buttonPushedMillis; // how long since the button/low signal was received 

void setup() {
  Serial.begin(9600);           // Serial connection for Debug
  Serial.println("Starting Pumps");
  
  // put your setup code here, to run once:
if (!AFMS.begin()) {         // create with the default frequency 1.6KHz
  // if (!AFMS.begin(1000)) {  // OR with a different frequency, say 1KHz
    Serial.println("MDriver not working");
    while (1);
  }
  Serial.println("MDriver working.");

pinMode(Rout1, INPUT_PULLUP); // Routine 1 push button
  // Set the speed to start, from 0 (off) to 255 (max speed)
  Dose1->setSpeed(150);
  Dose1->run(FORWARD); //direction call
  Dose1->run(RELEASE); //command call (on)

void loop() {
  // put your main code here, to run repeatedly:
DoseR1();
}

/*FUNCTIONS */

//Dose Routine 1 : will be progressively called through state of change
void DoseR1(){
  unsigned long currentMillis = millis();
   if (digitalRead(Rout1) == LOW) {
    ROUT1buttonPushedMillis = currentMillis;
    dose1aReady = true;
}
if (dose1aReady){
  Serial.println("pump1 should have been on after .5 seconds");
  if (currentMillis - ROUT1buttonPushedMillis >= mD1wait_interval){
    ROUT1buttonPushedMillis += mD1wait_interval; //added from an article on here  about a reset time thingy everything else was how it was
    Dose1->setSpeed(150);
    Dose1->run(FORWARD);
    dose1aState = true;
    mD1c_on = currentMillis;
    dose1aReady = false;
  }
  }
  if (dose1aState) {
    if (currentMillis - mD1c_on >= mD1run_interval){
      mD1c_on += mD1run_interval; //added from an article on here around a reset time thingy
      Dose1->fullOff();
      dose1aState = false;
    }
  }
}

I have taken a small snippet of my code to simply run a routine as a called function. In it's complete form, it would be routines called deepening on a button pressed in a switch-case-esq way (if button1 pressed, pump this, if 2, pump this, if 3, do this, however don't block the code, break, default etc). But in order to test different solutions, I just wanted to run this function in isolation. I can not for the life of me logically reason out what a timing reset would look like.

I am trying to start the function time (millis), I pass that to the variable when my push button was pressed. I compare the two for an initial interval, so that a certain amount of time has passed since I pressed the button and then something is done.

When that thing is done, I have a state change which allows my next if statement to be true, therefore the routine proceeds. I pass the most recent time assuming its the on time, then I compare that to the current time vs my interval, if it has been on for interval length, do something. it works one loop round in this format.

Is my problem logical, or set up wise? How can I reset this to keep the timing consistent? is it because I am using this in a function and not bare in the loop? If mathematical, what am I missing, I feel like I get so close and then so far when I make a change.

Essentially, I want to run more intervals and do more things but here we are only using two states for the sake of my tiny brain. eventually, I would like several more states all sequentially relying on millis to perform their dedicated tasks at my intended intervals, I have been a long time lurker, please please help me out!

When it comes to switches, look at when they change state, not at the state they are in.

1 Like

I can't follow your explanation of what the program is supposed to do -- please try to write more clearly.

Looking at the code, what jumps out is that while the button is pressed, you reset the current time every pass through loop. You probably want to detect when the button becomes pressed instead. Look at the Arduino State Machine example.

Edit: LarryD beat me to it.

A state machine is easier to follow when there is only one state variable and not one flag per state. This allows a switch/case statement to do everything.

An 'enum' is an easy way to assign a set of unique integer constants to a collection of names. In the case of states, the actual numbers don't matter.

Your first 'state' was not a state so the button was active even while the other states were being done. Making the button processing a separate state keeps it from interfering with the other states.

void DoseR1()
{
  unsigned long currentMillis = millis();
  static enum {idle, ready, state} doseR1State = idle;

  switch (doseR1State)
  {
    case idle:
      if (digitalRead(Rout1) == LOW)
      {
        ROUT1buttonPushedMillis = currentMillis;
        doseR1State = ready;
      }
      break;

    case ready:
      Serial.println("pump1 should have been on after .5 seconds");
      if (currentMillis - ROUT1buttonPushedMillis >= mD1wait_interval)
      {
        Dose1->setSpeed(150);
        Dose1->run(FORWARD);
        mD1c_on = currentMillis;
        doseR1State = state;
      }
      break;

    case state:
      if (currentMillis - mD1c_on >= mD1run_interval)
      {
        Dose1->fullOff();
        doseR1State = idle;
      }
      break;
  }
}

Note: If you don't want the "pump1 should have been on after .5 seconds" repeating, you could make that a separate state between 'idle' and 'ready'.

Hi @johnwasser @LarryD and @jremington , I feel like I am having the unravelling moment reading your edits and the above comments. So thank you all, I will take the time to respond to all.

Life got in the way but I did as you all instructed and went on a deep dive through these forums and the web, around FSM. I came across many comments on issues regarding state machines from yourselves and I am so glad I have all of your expert attention to set me on the right path.

It led me to discovering this resource on state machines that was incredible thanks @LarryD !! github resource here

so lets get into my progress:

I will split into sections and dump the entire code at the bottom:

I've maintained a dummy time interval section but eventually I will enum this in a way where these are calculated as a function of speed and ml/min (dosing amount)

I pulled a genius state machine/flag hybrid for the buttons from the vending machine example from the github resource I mentioned above here.

/*MAIN STATE MACHINE STATES*/

enum DosingSystemStates{idle, DOSINGA, DOSINGB, DOSEFINISH}
DosingSystemStates dosingSystemState = idle;

// Switch states for push buttons 
enum SwitchStates {IS_OPEN, IS_RISING, IS_CLOSED, IS_FALLING};
SwitchStates switchState[2] = {IS_OPEN, IS_OPEN};

// 
enum switchModes {PULLUP, PULLDOWN};
SwitchModes switchMode[2] = {PULLUP, PULLUP};

//FLAGS
bool hasRout1_strtd() {                     //Routine 1 active?
  switchMachine(0);                           //Read switch 0
  if (switchState[0] == IS_FALLING)           //If it is in the state IS_FALLING
    return true;                                //R1 started, return true
  else                                        //If not
    return false;                               //return false
}

bool hasRout2_strtd() {                     //Routine 2 active?
  switchMachine(1);                           //Read switch 1
  if (switchState[1] == IS_FALLING)           //If it is in the state IS_FALLING
    return true;                                //R2 started, return true
  else                                        //If not
    return false;                               //return false
}

void switchMachine(byte i) {
  byte pinIs = debounce.pin(routinePin[i]);
  if (switchMode[i]) == PULLUP)
    switch (switchState[i]) {
      case IS_OPEN:    {                                //State is IS_OPEN
      if(pinIs == HIGH)                                 //If the pin is HIGH
        switchState[i] = IS_RISING;                       //We just changed form LOW to HIGH: State is now IS_RISING 
      break;                                            //Get out of switch
    }
    case IS_RISING:  {                                //State is IS_RISING
      switchState[i] = IS_CLOSED;                       //It is not rising anymore, State is now IS_CLOSED
      break;                                            //Get out of switch
    }
    case IS_CLOSED:  {                               //State is IS_CLOSED
      if(pinIs == LOW)                                 //If the pin is LOW
        switchState[i] = IS_FALLING;                     //We just changed from HIGH to LOW: State is now IS_FALLING 
      break;                                           //Get out of switch 
    }
    case IS_FALLING: {                               //State is IS_FALLING
      switchState[i] = IS_OPEN;                        //It is not falling anymore, State is now IS_OPEN    
      break;                     
    }
  }
}

That covers the main state machines, I have left function state machines within the function using static enum taking the correction from @johnwasser which really helped me conceptualise the button, timing and the flags requiring separation.

Maintained John's structure below, by starting the timer within the main IDLE state and passing that through to the dosing function depending on which button was pressed. I imagine there is a more elegant way I could/should do this as I am yet to thoroughly test this, my thoughts/questions in the comments. But mainly how does the main state machine know to progress to the next state when function has been executed?

/*MAIN STATE FUNCTION*/

void DosingSystem(){
switch(dosingSystemState) {
  case idle: 
    //check the time, I'm not sure yet whether this is logically illegal
    //but I think if it is global to the case/state then it applies to all
    //aspects of the if statement within it, please correct otherwise
    unsigned long currentMillis = millis(); //
    
    if (hasRout1_strtd())
    Routine1Millis = currentMillis; //if routine 1 button is pressed/TRUE start routine 1 timer/ get a snapshot of the time for void DoseR1()
    dosingSystemState = DOSINGA; // go to dosing state A
    
    if (hasRout2_strtd())          //if routine 2 button is pressed/TRUE same for the above
    Routine2Millis = currentMillis;
   
    dosingSystemState = DOSINGB;// go to dosing state B

    //might add connection to https server here, depends on my ESP32 abilities
    //might be my 2nd forum post when I break it :3
    
    break;
    
  case DOSINGA:
   DoseR1(); /// dose r2 is idential for now for simplicity, but essentially, r1 is the concept I am going for
    // How do we link/track the execution of this function
    // and the main state, so that the main state machine progresses?
    // when DoseR1(); is completed, how does the below line know to execute?
    dosingSystemState = DOSEFINISH;
    break;
    
  case DOSINGB:
  DoseR2();
  dosingSystemState = DOSEFINISH;
    break;
 
  case DOSEFINISH:
  //do somethinggggggg then return to idle I guess
  dosingSystemState = idle;
    break;
}
}

Finally, maintaining that structure I used the state design within the function itself:

void DoseR1()
{
static enum {DOSE1ra, DOSE1a, DOSE2ra, DOSE2a, DOSE3ra, 
DOSE3a, DOSE4ra, DOSE4ra, DOSEr1CMP} pumpR1State = DOSE1ra;

//1ra wait state
//1a dose state

  switch (pumpR1State)
  {
    case DOSE1ra:
      //Serial.println("pump1 should have been on after .5 seconds");
      if (currentMillis - Routine1Millis >= mD1wait_interval)
      {
        Dose1->setSpeed(150);
        Dose1->run(FORWARD);
        mDc_on = currentMillis;
        pumpR1State = DOSE1a;
      }
      break;

    case DOSE1a:
      if (currentMillis - mDc_on >= mD1run_interval) //will shut down after the run interval is met
      {
        Dose1->fullOff();
        mDc_on = currentMillis;//resetting the difference everytime 
        pumpR1State = DOSE2ra;
      }
      break;
      
    case DOSE2ra:
       if (currentMillis - mDc_on >= mD2wait_interval) //wait then run
      {
        Dose2->setSpeed(150);
        Dose2->run(FORWARD);
        mDc_on = currentMillis; //resetting the difference everytime 
        pumpR1State = DOSE2a;
      }
    break;
    
   case DOSE2a:
       if (currentMillis - mDc_on >= mD2run_interval) //will shut down after the run interval is met
      {
        Dose2->fullOff();
        mDc_on = currentMillis; //resetting the difference everytime 
        pumpR1State = DOSE3ra;
      }
    break;
    
   case DOSE3ra:
       if (currentMillis - mDc_on >= mD3wait_interval) //wait then run
      {
        Dose3->setSpeed(150);
        Dose3->run(FORWARD);
        mDc_on = currentMillis;
        pumpR1State = DOSE3a;
      }
    break;

    case DOSE3a:
       if (currentMillis - mDc_on >= mD3run_interval) //will shut down after the run interval is met 
      {
        Dose3->fullOff();
        mDc_on = currentMillis; //resetting the difference everytime
        pumpR1State = DOSE4ra;
      }
    break;
    
    case DOSE4ra:
       if (currentMillis - mDc_on >= mD4wait_interval) //wait then run 
      {
        Dose4->setSpeed(150);
        Dose4->run(FORWARD);
        mDc_on = currentMillis;
       pumpR1State = DOSE4a;
      }
    break;
    
    case DOSE4a:
       if (currentMillis - mDc_on >= mD4run_interval) //will shut down after the run interval is met 
      {
        Dose4->fullOff();
        mDc_on = currentMillis; //resetting the difference everytime
        pumpR1State = DOSEr1CMP;
      }
    case DOSEr1CMP:
// DO something with the web interface probably a data logging or function tracking statement, some led's, some buzzer, still thinking for now.
        mDc_on = currentMillis; //resetting the difference everytime
        pumpR1State = DOSE1ra;
  }
}      

Mind you I have many questions on a couple of things but for now lets start here. My main concern is my use of the variables MDc_on and Routine1Millis (or the structure, I pass the start time to Routine1Millis like this here to start tracking the time:

    if (hasRout1_strtd())
    Routine1Millis = currentMillis; //if routine 1 button is pressed/TRUE start routine 1 timer/ get a snapshot of the time for void DoseR1()
    dosingSystemState = DOSINGA; // go to dosing state A

within the function I track the time using mDc_on here:

    case DOSE1ra:
      //Serial.println("pump1 should have been on after .5 seconds");
      if (currentMillis - Routine1Millis >= mD1wait_interval)
      {
        Dose1->setSpeed(150);
        Dose1->run(FORWARD);
        mDc_on = currentMillis; //resetting the difference
        pumpR1State = DOSE1a;

I keep reseting mDc_on like this:

        Dose2->setSpeed(150);
        Dose2->run(FORWARD);
        mDc_on = currentMillis; //resetting the difference everytime 
        pumpR1State = DOSE2a;

is this safe? Is this logically correct? I know this isn't elegant, but I've written at my logic level. Guys thank you so much and looking forward to you tearing this apart.

Entire code for a system which doses 4 materials on a basic timing interval (for now) with 4 different pumps.

#include <Adafruit_MotorShield.h>
#include <EdgeDebounceLite.h>


//to do figure out timing intervals and whether or not you will remove the delay
EdgeDebounceLite debounce;


Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *Dose1 = AFMS.getMotor(1);   //pump 1
Adafruit_DCMotor *Dose2 = AFMS.getMotor(2);  //pump 2
Adafruit_DCMotor *Dose3 = AFMS.getMotor(3); //pump 3
Adafruit_DCMotor *Dose4 = AFMS.getMotor(4);//pump 4


/*GLOBAL VARIABLES */

//PINS & BUTTONS//
byte routinePin[2] = {3, 4}; //push buttons to be used with debounce

#define Gledstatus 6
#define Bledstatus 7
#define Rledstatus 8 

/*ROUTINE 1 TIMING VARIABLES*/
//Global to routine 1 and 2 maybe?? as time tracking variables//
unsigned long mDc_on; // similar to how previousMillis= currentmillis, a variable for how long a dose has run for
unsigned long Routine1Millis; //how long since routine 1 button was pressed

//Changing timwe intervals//
//I have left them all the same value for now for my brain

unsigned long mD1wait_interval = 2500; // Pump off time
unsigned long mD1run_interval = 5000; // Pump on time
//dose 2 with motor 2
unsigned long mD2wait_interval = 500; //
unsigned long mD2run_interval = 15000; //
// dose 3 with motor 3
unsigned long mD3wait_interval = 500; //
unsigned long mD3run_interval = 15000; //
//dose 4 with motor 4
unsigned long mD4wait_interval = 500; //
unsigned long mD4run_interval = 15000; //

/*ROUTINE 2 TIMING VARIABLES*/

//routine 2 as time tracking variables unsure if I need these//
unsigned long mDc2_on; // similar to how previousMillis= currentmillis, a variable for how long a dose has run for
unsigned long Routine2Millis; //how long since routine 2 button was pressed

//Changing time intervals//
//I have left them all the same value for now for my brain

unsigned long mD1bwait_interval = 2500; // Pump off time
unsigned long mD1brun_interval = 5000; // Pump on time
//dose 2 with motor 2
unsigned long mD2bwait_interval = 500; //
unsigned long mD2brun_interval = 15000; //
// dose 3 with motor 3
unsigned long mD3bwait_interval = 500; //
unsigned long mD3brun_interval = 15000; //
//dose 4 with motor 4
unsigned long mD4bwait_interval = 500; //
unsigned long mD4brun_interval = 15000; //

/*MAIN STATE MACHINE STATES*/

enum DosingSystemStates{idle, DOSINGA, DOSINGB, DOSEFINISH}
DosingSystemStates dosingSystemState = idle;

// Switch states
enum SwitchStates {IS_OPEN, IS_RISING, IS_CLOSED, IS_FALLING};
SwitchStates switchState[2] = {IS_OPEN, IS_OPEN};

// 
enum switchModes {PULLUP, PULLDOWN};
SwitchModes switchMode[2] = {PULLUP, PULLUP};


//FLAGS
bool hasRout1_strtd() {                     //Routine 1 active?
  switchMachine(0);                           //Read switch 0
  if (switchState[0] == IS_FALLING)           //If it is in the state IS_FALLING
    return true;                                //R1 started, return true
  else                                        //If not
    return false;                               //return false
}

bool hasRout2_strtd() {                     //Routine 2 active?
  switchMachine(1);                           //Read switch 1
  if (switchState[1] == IS_FALLING)           //If it is in the state IS_FALLING
    return true;                                //R2 started, return true
  else                                        //If not
    return false;                               //return false
}

void switchMachine(byte i) {
  byte pinIs = debounce.pin(routinePin[i]);
  if (switchMode[i]) == PULLUP)
    switch (switchState[i]) {
      case IS_OPEN:    {                                //State is IS_OPEN
      if(pinIs == HIGH)                                 //If the pin is HIGH
        switchState[i] = IS_RISING;                       //We just changed form LOW to HIGH: State is now IS_RISING 
      break;                                            //Get out of switch
    }
    case IS_RISING:  {                                //State is IS_RISING
      switchState[i] = IS_CLOSED;                       //It is not rising anymore, State is now IS_CLOSED
      break;                                            //Get out of switch
    }
    case IS_CLOSED:  {                               //State is IS_CLOSED
      if(pinIs == LOW)                                 //If the pin is LOW
        switchState[i] = IS_FALLING;                     //We just changed from HIGH to LOW: State is now IS_FALLING 
      break;                                           //Get out of switch 
    }
    case IS_FALLING: {                               //State is IS_FALLING
      switchState[i] = IS_OPEN;                        //It is not falling anymore, State is now IS_OPEN    
      break;                     
    }
  }
}

void DosingSystem(){
switch(dosingSystemState) {
  case idle: 
    //check the time, I'm not sure yet whether this is logically illegal
    //but I think if it is global to the case/state then it applies to all
    //aspects of the if statement within it, please correct otherwise
    unsigned long currentMillis = millis(); //
    
    if (hasRout1_strtd())
    Routine1Millis = currentMillis; //if routine 1 button is pressed/TRUE start routine 1 timer/ get a snapshot of the time for void DoseR1()
    dosingSystemState = DOSINGA; // go to dosing state A
    
    if (hasRout2_strtd())          //if routine 2 button is pressed/TRUE same for the above
    Routine2Millis = currentMillis;
   
    dosingSystemState = DOSINGB;// go to dosing state B

    //might add connection to https server here, depends on my ESP32 abilities
    //might be my 2nd forum post when I break it :3
    
    break;
    
  case DOSINGA:
   DoseR1(); /// dose r2 is idential for now for simplicity, but essentially, r1 is the concept I am going for
    // How do we link/track the execution of this function
    // and the main stateso that the main state machine progresses
    // when DoseR1(); is completed, how does the below line execute?
    dosingSystemState = DOSEFINISH;
    break;
    
  case DOSINGB:
  DoseR2();
  dosingSystemState = DOSEFINISH;
    break;
 
  case DOSEFINISH:
  //do somethinggggggg then return to idle I guess
  dosingSystemState = idle;
    break;
}
}


void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);           // Serial connection for Debug
  Serial.println("Starting Unity");
  
  // put your setup code here, to run once:
if (!AFMS.begin()) {         // create with the default frequency 1.6KHz
  // if (!AFMS.begin(1000)) {  // OR with a different frequency, say 1KHz
    Serial.println("MDriver not working");
    while (1);
  }
  Serial.println("MDriver working.");

//Setting buttons as pull ups
for (int i = 0 ; i < 2 ; i++)             //For each switch
    pinMode(RoutinePin[i], INPUT_PULLUP);      //Their modes are INPUT_PULLUP
}

void loop() {
  // put your main code here, to run repeatedly:
DosingSystem();
}

void DoseR1()
{
static enum {DOSE1ra, DOSE1a, DOSE2ra, DOSE2a, DOSE3ra, 
DOSE3a, DOSE4ra, DOSE4ra, DOSEr1CMP} pumpR1State = DOSE1ra;

//1ra wait state
//1a dose state

  switch (pumpR1State)
  {
    case DOSE1ra:
      //Serial.println("pump1 should have been on after .5 seconds");
      if (currentMillis - Routine1Millis >= mD1wait_interval)
      {
        Dose1->setSpeed(150);
        Dose1->run(FORWARD);
        mDc_on = currentMillis;
        pumpR1State = DOSE1a;
      }
      break;

    case DOSE1a:
      if (currentMillis - mDc_on >= mD1run_interval) //will shut down after the run interval is met
      {
        Dose1->fullOff();
        mDc_on = currentMillis;//resetting the difference everytime 
        pumpR1State = DOSE2ra;
      }
      break;
      
    case DOSE2ra:
       if (currentMillis - mDc_on >= mD2wait_interval) //wait then run
      {
        Dose2->setSpeed(150);
        Dose2->run(FORWARD);
        mDc_on = currentMillis; //resetting the difference everytime 
        pumpR1State = DOSE2a;
      }
    break;
    
   case DOSE2a:
       if (currentMillis - mDc_on >= mD2run_interval) //will shut down after the run interval is met
      {
        Dose2->fullOff();
        mDc_on = currentMillis; //resetting the difference everytime 
        pumpR1State = DOSE3ra;
      }
    break;
    
   case DOSE3ra:
       if (currentMillis - mDc_on >= mD3wait_interval) //wait then run
      {
        Dose3->setSpeed(150);
        Dose3->run(FORWARD);
        mDc_on = currentMillis;
        pumpR1State = DOSE3a;
      }
    break;

    case DOSE3a:
       if (currentMillis - mDc_on >= mD3run_interval) //will shut down after the run interval is met 
      {
        Dose3->fullOff();
        mDc_on = currentMillis; //resetting the difference everytime
        pumpR1State = DOSE4ra;
      }
    break;
    
    case DOSE4ra:
       if (currentMillis - mDc_on >= mD4wait_interval) //wait then run 
      {
        Dose4->setSpeed(150);
        Dose4->run(FORWARD);
        mDc_on = currentMillis;
       pumpR1State = DOSE4a;
      }
    break;
    
    case DOSE4a:
       if (currentMillis - mDc_on >= mD4run_interval) //will shut down after the run interval is met 
      {
        Dose4->fullOff();
        mDc_on = currentMillis; //resetting the difference everytime
        pumpR1State = DOSEr1CMP;
      }
    case DOSEr1CMP:
// DO something with the web interface probably a data logging or function tracking statement, some led's, some buzzer, still thinking for now.
        mDc_on = currentMillis; //resetting the difference everytime
        pumpR1State = DOSE1ra;
  }
}
       
void DoseR2(){
  }

Yes, but. I usually put one call to millis() as the very first action of a function. and use that variable (I call it the more pedestrian now) everywhere.

Very often now is a global variable that I set at the top of the loop() function, as I usually have a fast loop. now is the same, then for all functions be they FSMs or no.

You can use many tricks to communicate between state machines.

Global flags. Maxine is busy, not busy &c.

Sharing globally the state of machines.

Use of return values from machines for progress reporting.

And while we are at it, state machines can use an argument in the call, I use one sometimes to choose between restarting or initialising a machine or killing it or just stepping it along.

A state machine might have a calling code argument that was only meant to provoke it to return some thing you want to know about its state or progress within a state if there are timers or counters.

An argument to an FSM can also inform it of something that might change its behaviour.

a7

Hi All,

thanks alot. After separating the timing functions, calling the start time within the loop, keeping the instances of the intervals in separate states I have functional code. Here is the basic snippet of all your suggestions:

//---------------------------- LIBRARIES-----------------------------------------------//
#include <Adafruit_MotorShield.h>
#include <EdgeDebounceLite.h>
#include <ezBuzzer.h>

//to do figure out timing intervals and whether or not you will remove the delay
EdgeDebounceLite debounce;

//----------------------------------- OBJECTS------------------------------------//

Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *Dose1 = AFMS.getMotor(1);   //pump 1
Adafruit_DCMotor *Dose2 = AFMS.getMotor(2);  //pump 2
Adafruit_DCMotor *Dose3 = AFMS.getMotor(3); //pump 3
Adafruit_DCMotor *Dose4 = AFMS.getMotor(4);//pump 4

//---------------------------- LIBRARIES-----------------------------------------------//
#include <Adafruit_MotorShield.h>
#include <EdgeDebounceLite.h>
#include <ezBuzzer.h>

//to do figure out timing intervals and whether or not you will remove the delay
EdgeDebounceLite debounce;

//----------------------------------- OBJECTS-----------------------------------

Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *Dose1 = AFMS.getMotor(1);   //pump 1
Adafruit_DCMotor *Dose2 = AFMS.getMotor(2);  //pump 2
Adafruit_DCMotor *Dose3 = AFMS.getMotor(3); //pump 3
Adafruit_DCMotor *Dose4 = AFMS.getMotor(4);//pump 4

//----------------------------------- GLOBALS------------------------------------//

//PINS & BUTTONS//
byte RoutinePin[2] = {3, 4}; //push buttons to be used with debounce
byte StatusLEDS[3] = {6, 7, 8}; //G (6), B(7), R(8).

//GLOBAL FLAGS

bool DoneRoutine1= false; // these will become true when the last state of each routine is reached
bool DoneRoutine2= false;
bool doseAcmp = false;
bool doseBcmp = false;
//Buzzer states
bool bzer_on = false;
//Led states
bool ledR = false; //Red led 
bool ledG = false; //Green led 
bool ledB = false; // Blue led 
//GLOBAL TIMING VARIABLES
unsigned long currentMillis; 


//ROUTINE 1 TIMING VARIABLES //
//Global to routine 1 and 2 maybe?? as time tracking variables//
unsigned long mDc_on; // similar to how previousMillis= currentmillis, a variable for how long a dose has run for
unsigned long Routine1Millis; //how long since routine 1 button was pressed

//Changing timwe intervals//
//I have left them all the same value for now for my brain

unsigned long mD1wait_interval = 2500; // Pump off time
unsigned long mD1run_interval = 5000; // Pump on time
//dose 2 with motor 2
unsigned long mD2wait_interval = 500; //
unsigned long mD2run_interval = 5000; //
// dose 3 with motor 3
unsigned long mD3wait_interval = 500; //
unsigned long mD3run_interval = 5000; //
//dose 4 with motor 4
unsigned long mD4wait_interval = 500; //
unsigned long mD4run_interval = 5000; //

//----------------------------------- MAIN STATES------------------------------------//

// DOSING STATES
enum DosingSystemStates{IDLE, DOSINGA, DOSINGB, DOSEFINISH };
DosingSystemStates dosingSystemState = IDLE;

// SWITCH MODES
enum SwitchStates {IS_OPEN, IS_RISING, IS_CLOSED, IS_FALLING };
SwitchStates switchState[2] = {IS_OPEN, IS_OPEN }; // both switches are in is open

// SWITCH MODES
enum SwitchModes {MY1_PULLUP, MY1_PULLDOWN}; //the two possible modes the pins could be in
SwitchModes switchMode[2] = {MY1_PULLUP, MY1_PULLUP}; // I am focused on states where both switches are in pullup mode.

// BUTTON FLAGS
bool hasRout1_strtd() {                     //Routine 1 active?
  switchMachine(0);                           //Read switch 0
  if (switchState[0] == IS_FALLING)           //If it is in the state IS_FALLING
    return true;                                //R1 started, return true
  else                                        //If not
    return false;                               //return false
}

bool hasRout2_strtd() {                     //Routine 2 active?
  switchMachine(1);                           //Read switch 1
  if (switchState[1] == IS_FALLING)           //If it is in the state IS_FALLING
    return true;                                //R2 started, return true
  else                                        //If not
    return false;                               //return false
}

//SWITCH STATE MACHINE
void switchMachine(byte i) {
  byte pinIs = debounce.pin(RoutinePin[i]);
 if (switchMode[i] == MY1_PULLUP){pinIs = !pinIs;  //reverse the result for pullup 
    switch (switchState[i]) {
      case IS_OPEN:    {                                //State is IS_OPEN
      if(pinIs == HIGH)                                 //If the pin is HIGH
        switchState[i] = IS_RISING;                       //We just changed form LOW to HIGH: State is now IS_RISING 
      break;                                            //Get out of switch
    }
    case IS_RISING:  {                                //State is IS_RISING
      switchState[i] = IS_CLOSED;                       //It is not rising anymore, State is now IS_CLOSED
      break;                                            //Get out of switch
    }
    case IS_CLOSED:  {                               //State is IS_CLOSED
      if(pinIs == LOW)                                 //If the pin is LOW
        switchState[i] = IS_FALLING;                     //We just changed from HIGH to LOW: State is now IS_FALLING 
      break;                                           //Get out of switch 
    }
    case IS_FALLING: {                               //State is IS_FALLING
      switchState[i] = IS_OPEN;                        //It is not falling anymore, State is now IS_OPEN    
      break;
    }                     
    }
  }
}

//DOSING STATE MACHINE 
void DosingSystem(){
switch(dosingSystemState) {
  case IDLE: {
    Serial.println( "I am in idle");
    buzstrt(); //startup tone
    digitalWrite(StatusLEDS[2], HIGH);


    if (hasRout1_strtd()){
    digitalWrite(StatusLEDS[0], HIGH);
    digitalWrite(StatusLEDS[2], LOW);
    bbbuzz();
    Routine1Millis = currentMillis; //if routine 1 button is pressed/TRUE start routine 1 timer/ get a snapshot of the time for void DoseR1()
    dosingSystemState = DOSINGA; // go to dosing state A
    }
    if (hasRout2_strtd()){         //if routine 2 button is pressed/TRUE same for the above
    digitalWrite(StatusLEDS[1], HIGH);
    digitalWrite(StatusLEDS[2], LOW);
    bbbuzz();
    Routine2Millis = currentMillis;
    dosingSystemState = DOSINGB;// go to dosing state B
    }
    //might add connection to https server here, depends on my ESP32 abilities
    //might be my 2nd forum post when I break it :3
    break;
  }
  case DOSINGA: {
   Serial.println( "I am in DOSINGA");
   DoseR1();    
    if(DoseR1()) {
    digitalWrite(StatusLEDS[0], LOW);  //Green led goes off
    Serial.println( "doseR1 is cmp");
    doseAcmp = true;
    dosingSystemState = DOSEFINISH;
    }
    break;
  }
  case DOSINGB: {
  DoseR2();
  Serial.println( "I am in DOSINGB");
   if(DoseR2()) {
   Serial.println( "doseR2 is cmp");
   digitalWrite(StatusLEDS[1], LOW); //Blue led goes off
   doseBcmp = true;
   dosingSystemState = DOSEFINISH;
  }
    break;
  }
  case DOSEFINISH: {
    Serial.println( "I am in DOSEFINISH");
    digitalWrite(StatusLEDS[2], HIGH);
     if (doseAcmp || doseAcmp){
      StrtUpBuz = 0; //reset the counter for the buzz tone, whilst in IDLE itll go once
       dosingSystemState = IDLE;
    break;
  }
 }
}
}

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);           // Serial connection for Debug
  Serial.println("Starting Pumps");
  
  // put your setup code here, to run once:
if (!AFMS.begin()) {         // create with the default frequency 1.6KHz
  // if (!AFMS.begin(1000)) {  // OR with a different frequency, say 1KHz
    Serial.println("MDriver not working");
    while (1);
  }
  Serial.println("MDriver working.");

//Setting buttons as pull ups
for (int i = 0 ; i < 2 ; i++) {          //For each switch
    pinMode(RoutinePin[i], INPUT_PULLUP);
}
}

void loop() {
  // put your main code here, to run repeatedly:
currentMillis = millis(); //start the time globally
DosingSystem();

int DoseR1()
{
static enum {DOSE1ra, DOSE1a, DOSE2ra, DOSE2a, DOSE3ra,  // 1a is run. 1ra is wait to run
DOSE3a, DOSE4ra, DOSE4a, DOSEr1CMP} pumpR1State = DOSE1ra; 
  switch (pumpR1State)
  {
    case DOSE1ra: {
      //Serial.println("pump1 should have been on after .5 seconds");
      if (currentMillis - Routine1Millis >= mD1wait_interval)
      {
        Dose1->setSpeed(150);
        Dose1->run(FORWARD);
        mDc_on = currentMillis;
        pumpR1State = DOSE1a;
      }
      break;
    }
    case DOSE1a: {
      if (currentMillis - mDc_on >= mD1run_interval) //will shut down after the run interval is met
      {
        Dose1->fullOff();
        mDc_on = currentMillis;//resetting the difference everytime 
        pumpR1State = DOSE2ra;
        Serial.println("Dose1 has begun");
      }
      break;
    }
    case DOSE2ra: {
       if (currentMillis - mDc_on >= mD2wait_interval) //wait then run
      {
        Dose1->setSpeed(150);
        Dose1->run(FORWARD);
        mDc_on = currentMillis; //resetting the difference everytime 
        pumpR1State = DOSE2a;
      }
    break;
    }
   case DOSE2a: {
       if (currentMillis - mDc_on >= mD2run_interval) //will shut down after the run interval is met
      {
        Dose1->fullOff();
        mDc_on = currentMillis; //resetting the difference everytime 
        pumpR1State = DOSE3ra;
      }
    break;
   }
   case DOSE3ra: {
       if (currentMillis - mDc_on >= mD3wait_interval) //wait then run
      {
        Dose1->setSpeed(150);
        Dose1->run(FORWARD);
        mDc_on = currentMillis;
        pumpR1State = DOSE3a;
      }
    break;
   }
    case DOSE3a: {
       if (currentMillis - mDc_on >= mD3run_interval) //will shut down after the run interval is met 
      {
        Dose1->fullOff();
        mDc_on = currentMillis; //resetting the difference everytime
        pumpR1State = DOSE4ra;
      }
    break;
    }
    case DOSE4ra: {
       if (currentMillis - mDc_on >= mD4wait_interval) //wait then run 
      {
        Dose1->setSpeed(150);
        Dose1->run(FORWARD);
        mDc_on = currentMillis; //resetting the difference everytime
       pumpR1State = DOSE4a;
      }
    break;
    }
    case DOSE4a: {
       if (currentMillis - mDc_on >= mD4run_interval) //will shut down after the run interval is met 
      {
        Dose1->fullOff();
        mDc_on = currentMillis; //resetting the difference everytime
        pumpR1State = DOSEr1CMP;
      }
      break;
    }
    case DOSEr1CMP: {
        // DO something with the web interface probably a data logging or function tracking statement, some led's, some buzzer, still thinking for now.
        mDc_on = currentMillis; //resetting the difference everything
        pumpR1State = DOSE1ra; //opting to use named flags just to keep track of everything for now in this phase
        Serial.println("Dose A is complete");
        StrtUpBuz = 0;
        return 1; //dose is done
        break;
        //test return 
      
  }
 }
 return 0; //dose is not done
}

I have just kpet base functionality in, but this works cleanly and consistently. Thanks for the critique, suggestions and support!

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