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(){
}