Cancel button code

I have a button hooked to change a global variable to detect user interrupt of the system.

There must be a better way to do this than constantly checking for the cancel flag?

Can I thread it out, check the flag in a loop, and kill the thread on cancel? This is what I'd do in Python.

if (interruptCycle) {return;}
  status("MIN WATER OK");
  if (interruptCycle) {return;}
  delay(5000);
  
  if (interruptCycle) {return;}
  status("DOSING SILICA"); relay("NUT1", true); delay(5000); relay("NUT1", false); delay(1000);
  if (interruptCycle) {return;}
  status("DOSING CALMAG"); relay("NUT2", true); delay(5000); relay("NUT2", false); delay(1000);
  if (interruptCycle) {return;}
  status("DOSING MICRO" ); relay("NUT3", true); delay(5000); relay("NUT3", false); delay(1000);
  if (interruptCycle) {return;}
  status("DOSING BLOOM" ); relay("NUT4", true); delay(5000); relay("NUT4", false); delay(1000);
  if (interruptCycle) {return;}
  status("DOSING GROW"  ); relay("NUT5", true); delay(5000); relay("NUT5", false); delay(1000);
  if (interruptCycle) {return;}
  status("DOSING NECTAR"); relay("NUT6", true); delay(5000); relay("NUT6", false); delay(1000);
  if (interruptCycle) {return;}
  status("DOSING THRIVE"); relay("NUT7", true); delay(5000); relay("NUT7", false); delay(1000);

A first approximation.

enum DosingState {
   MIN_WATER_OK,
   DOSING_SILICA,
   DOSING_CALMAG,
   DOSING_MICRO,
   DOSING_BLOOM,
   DOSING_GROW,
   DOSING_NECTAR,
   DOSING_THRIVE,
   DONE
} state;
char relayName[5];
   .
   .
   .
   state = MIN_WATER_OK;
   .
   .
   .
   while( !interruptCycle && state != DONE ) {
      switch( state ) {
         
         case MIN_WATER_OK:
            status("MIN WATER OK");
            relayName[0] = 0;
            state = DOSING_SILICA;
            break;
            
         case DOSING_SILICA:
            status("DOSING SILICA");
            strcpy(relayName, "NUT1");
            state = DOSING_CALMAG;
            break;
            
         case DOSING_CALMAG:
            status("DOSING CALMAG");
            strcpy(relayName, "NUT2");
            state = DOSING_MICRO;
            break;
            
         case DOSING_MICRO:
            status("DOSING MICRO" ); 
            strcpy(relayName, "NUT3");
            state = DOSING_BLOOM;
            break;
            
         case DOSING_BLOOM:
            status("DOSING BLOOM" ); 
            strcpy(relayName, "NUT4");
            state = DOSING_GROW;
            break;
            
         case DOSING_GROW:
            status("DOSING GROW"  ); 
            strcpy(relayName, "NUT5");
            state = DOSING_NECTAR;
            break;
            
         case DOSING_GROW:
            status("DOSING NECTAR"); 
            strcpy(relayName, "NUT6");
            state = DOSING_THRIVE;
            break;
            
         case DOSING_THRIVE:
            status("DOSING THRIVE"); 
            strcpy(relayName, "NUT7");
            state = DONE;
            break;
      }
      
      if( relayName[0] ) {
         relay(relayName, true);
         delay(5000);
         relay(relayName, false);
         delay(1000);
      } else {
         delay(5000);
      }
   }
1 Like

This is really great and shows me how to do a ton of things in C++ that I didn't know best practices for. (also... finally a decent case/switch example!) Thanks!

1 Like

if we are setting the state on each case, what is the purpose of enumerating states?
Is there a way to say "go to the next enumerated case" (switch to the next in order) instead of setting it on each case?

edit:

state = ++state;

? or why not a "for state in statelist"?

(is this just AI? it is all mashed and out of order)

I've been called many things over the years, but AI? That's a new one.

"Mashed and out of order"? I literally have no idea what you're talking about.

guess I've grown sensitive too it from too much Bard lol. just a question, not an accusation.

'dosing grow' is twice listed as a case and some vars are wrong. like Bard does all the time or did. but humans also can do.

Yep, I made some mistakes in the few minutes I spent typing out the pseudo-code. Mea culpa.

Good luck with your project.

Here's what I ended up with. Still needs work to add the water sensors and I don't have half of the hardware, including better wires, but the prototype works.

I did learn a lot from your answer though I didn't implement it here... not trying to go to the moon, well, not literally anyway :wink: Thanks again!

// 11/19/23
#include <String.h>

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 20 chars and 4 line display

#include <RTClib.h> // REAL TIME CLOCK
RTC_DS3231 rtc;

// Ppump RELAYS
const int NUT_1_PIN = 53;
const int NUT_2_PIN = 51;
const int NUT_3_PIN = 49;
const int NUT_4_PIN = 47;
const int NUT_5_PIN = 45;
const int NUT_6_PIN = 43;
const int NUT_7_PIN = 41;
// VALVE RELAYS
const int FILL_PIN = 39;
const int MIX_PIN =  37;
const int RES1_PIN = 35;
const int RES2_PIN = 33;
// OUTPUT PUMP RELAY
const int PUMP_PIN = 31;

// GOING TO NEED PINS FOR 7 WATER LEVEL SENSORS
// 3 SENSORS FOR MIX BUCKET
// 2 FOR EACH RES
const int LEVEL_MIX_HIGH_PIN = 52;
const int LEVEL_MIX_LOW_PIN = 50;
const int LEVEL_RES1_HIGH_PIN = 48;
const int LEVEL_RES1_LOW_PIN = 46;
const int LEVEL_RES2_HIGH_PIN = 44;
const int LEVEL_RES2_LOW_PIN = 42;

bool ic = false; 
unsigned long lastStopPress = millis();
void stopButtonInterrupt() {
  if ( millis() - lastStopPress > 2000 ) {
    Serial.println("RED BUTTON");
    Serial.println(ic);
    lastStopPress = millis(); // ez debounce
    if (ic) { 
      ic = false;
    } else { 
      ic = true;
    }
  }
}

bool startCycle = false;
unsigned long lastStartPress = millis();
void startButtonInterrupt() {
  if ( millis() - lastStartPress > 2000 ) {
    lastStartPress = millis(); // ez debounce
    Serial.println("BLACK BUTTON");
    Serial.println(ic);
    startCycle = true;
  }
}

// bool startCycle = false;
// void startButtonInterrupt() {
//   ic = false;
// }

void setup() {
  delay(3000);
  Serial.begin(9600);

  // EMERGENCY STOP
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), stopButtonInterrupt, HIGH); // CHANGE

  // START
  pinMode(3, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(3), startButtonInterrupt, HIGH); // CHANGE

  // RTC
  rtc.begin();
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  // LCD
  lcd.init();
  lcd.clear();
  lcd.backlight();
  lcd.setCursor(0,0); lcd.print("A NAME HERE");
  lcd.setCursor(17, 0); lcd.print("NOR"); // LCD INTERRUPT CONDITION NORMAL INITIAL STATE
  
  // PUMP RELAY
  pinMode(PUMP_PIN, OUTPUT); // OUTPUT PUMP
  
  // VALVE RELAYS
  pinMode(FILL_PIN, OUTPUT); // FILL RELAY
  pinMode(MIX_PIN, OUTPUT);  // BUCKET MIXER RELAY
  pinMode(RES1_PIN, OUTPUT); // RES 1 OUTPUT RELAY
  pinMode(RES2_PIN, OUTPUT); // RES 2 OUTPUT RELAY

  // Ppump RELAYS
  pinMode(NUT_1_PIN, OUTPUT); 
  pinMode(NUT_2_PIN, OUTPUT);
  pinMode(NUT_3_PIN, OUTPUT);
  pinMode(NUT_4_PIN, OUTPUT);
  pinMode(NUT_5_PIN, OUTPUT);
  pinMode(NUT_6_PIN, OUTPUT);
  pinMode(NUT_7_PIN, OUTPUT);
} 

void relay(String name, bool state) {
  if      (name == "PUMP") { if (state) { digitalWrite(PUMP_PIN,  HIGH); } else { digitalWrite(PUMP_PIN,  LOW); }} 
  else if (name == "FILL") { if (state) { digitalWrite(FILL_PIN,   LOW); } else { digitalWrite(FILL_PIN, HIGH); }}
  else if (name == "MIX" ) { if (state) { digitalWrite(MIX_PIN,    LOW); } else { digitalWrite(MIX_PIN,  HIGH); }}
  else if (name == "RES1") { if (state) { digitalWrite(RES1_PIN,   LOW); } else { digitalWrite(RES1_PIN, HIGH); }}
  else if (name == "RES2") { if (state) { digitalWrite(RES2_PIN,   LOW); } else { digitalWrite(RES1_PIN, HIGH); }}
  else if (name == "NUT1") { if (state) { digitalWrite(NUT_1_PIN, HIGH); } else { digitalWrite(NUT_1_PIN, LOW); }} 
  else if (name == "NUT2") { if (state) { digitalWrite(NUT_2_PIN, HIGH); } else { digitalWrite(NUT_2_PIN, LOW); }}
  else if (name == "NUT3") { if (state) { digitalWrite(NUT_3_PIN, HIGH); } else { digitalWrite(NUT_3_PIN, LOW); }}
  else if (name == "NUT4") { if (state) { digitalWrite(NUT_4_PIN, HIGH); } else { digitalWrite(NUT_4_PIN, LOW); }}
  else if (name == "NUT5") { if (state) { digitalWrite(NUT_5_PIN, HIGH); } else { digitalWrite(NUT_5_PIN, LOW); }}
  else if (name == "NUT6") { if (state) { digitalWrite(NUT_6_PIN, HIGH); } else { digitalWrite(NUT_6_PIN, LOW); }}
  else if (name == "NUT7") { if (state) { digitalWrite(NUT_7_PIN, HIGH); } else { digitalWrite(NUT_7_PIN, LOW); }}
}

void updateTime(int col, int row) {
  DateTime now = rtc.now();
  String year = String(now.year()).substring(2,4);
  String month = String(now.month());
  String day = String(now.day());
  String hours = String(now.hour());
  if (now.hour() < 10) {hours = "0" + hours;}
  String minutes = String(now.minute());
  if (now.minute() < 10) {minutes = "0" + minutes;}
  String dt = month + "/" + day + "|" + hours + ":" + minutes;
  // Serial.print("CLOCK: "); Serial.println(dt);
  lcd.setCursor(col, row);
  lcd.print(dt);
}

void status(String message) {
  lcd.setCursor(0,0); lcd.print("                ");
  lcd.setCursor(0,0); lcd.print(message);
}

void mixingCycle() {
  Serial.println(ic);
  // Serial.println("MIXING CYCLE START");
  if(ic){return;} status("CYCLE START");
  if(ic){return;} delay(1000);
  // verify bucket empty or try to empty

  // fill with some water
  if(ic){return;} status("INITIAL FILL");
  if(ic){return;} relay("FILL", true);
  if(ic){return;} delay(5000);
  
  if(ic){return;} status("PUMP RELAY ON");
  if(ic){return;} relay("PUMP", true);
  if(ic){return;} delay(5000);

  if(ic){return;} status("MIX RELAY ON");
  // start the circulation pump
  if(ic){return;} relay("MIX", true);
  if(ic){return;} delay(5000);

  // wait for minimum water sensor
  // while (not sensorFunction(resLow)) { delay(1000) }
  if(ic){return;} status("MIN WATER OK"); // assuming it is
  if(ic){return;} delay(5000);

  if(ic){return;} status("DOSING SILICA"); relay("NUT1", true); delay(5000); relay("NUT1", false); delay(1000);
  if(ic){return;} status("DOSING CALMAG"); relay("NUT2", true); delay(5000); relay("NUT2", false); delay(1000);
  if(ic){return;} status("DOSING MICRO" ); relay("NUT3", true); delay(5000); relay("NUT3", false); delay(1000);
  if(ic){return;} status("DOSING BLOOM" ); relay("NUT4", true); delay(5000); relay("NUT4", false); delay(1000);
  if(ic){return;} status("DOSING GROW"  ); relay("NUT5", true); delay(5000); relay("NUT5", false); delay(1000);
  if(ic){return;} status("DOSING NECTAR"); relay("NUT6", true); delay(5000); relay("NUT6", false); delay(1000);
  if(ic){return;} status("DOSING THRIVE"); relay("NUT7", true); delay(5000); relay("NUT7", false); delay(1000);

  if(ic){return;} status("MIX CYCLE DONE!");
  if(ic){return;} lcd.setCursor(0,1); lcd.print("LAST: "); updateTime(6,1);
  if(ic){return;} delay(1000);
  return;
}

void disableAllRelays() {
    digitalWrite(PUMP_PIN,  LOW);

    digitalWrite(FILL_PIN, HIGH);
    digitalWrite(MIX_PIN,  HIGH);
    digitalWrite(RES1_PIN, HIGH);
    digitalWrite(RES2_PIN, HIGH);

    digitalWrite(NUT_1_PIN, LOW);
    digitalWrite(NUT_2_PIN, LOW);
    digitalWrite(NUT_3_PIN, LOW);
    digitalWrite(NUT_4_PIN, LOW);
    digitalWrite(NUT_5_PIN, LOW);
    digitalWrite(NUT_6_PIN, LOW);
    digitalWrite(NUT_7_PIN, LOW);
}

bool loopStartup = false;
void loop() {
  if (!loopStartup) {
    loopStartup = true;
    disableAllRelays();
    delay(3000);
  }
  updateTime(9,3);
  if (!ic) {
    lcd.setCursor(17, 0); lcd.print("NOR");
    if (startCycle) {
      startCycle = false;
      mixingCycle();
      disableAllRelays();
    }
  } else {
    startCycle = false;
    lcd.setCursor(0, 0); lcd.print("CANCEL           INT");
  }
  delay(1000);
}

ok I was just guessing, I need to study up on them
I definitely did learn that certain things don't work in interrupt which is why I set up flags for them and detect them in loop.

here's the latest with a lot more logic.
What I don't know is why the serial print is necessary in delayInt() for it to work. I guess it forces the global update of the value somehow.

#include <String.h>

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 20 chars and 4 line display

#include <RTClib.h> // REAL TIME CLOCK
RTC_DS3231 rtc;

// Ppump RELAYS
const int NUT_1_PIN = 53;
const int NUT_2_PIN = 51;
const int NUT_3_PIN = 49;
const int NUT_4_PIN = 47;
const int NUT_5_PIN = 45;
const int NUT_6_PIN = 43;
const int NUT_7_PIN = 41;
// VALVE RELAYS
const int FILL_PIN = 39;
const int MIX_PIN =  37;
const int RES1_PIN = 35;
const int RES2_PIN = 33;
// OUTPUT PUMP RELAY
const int PUMP_PIN = 31;

// GOING TO NEED PINS FOR 7 WATER LEVEL SENSORS
// 3 SENSORS FOR MIX BUCKET
// 2 FOR EACH RES
const int LEVEL_MIX_HIGH_PIN = 52;
const int LEVEL_MIX_MED_PIN = 50;
const int LEVEL_MIX_LOW_PIN = 48;
const int LEVEL_RES1_HIGH_PIN = 46;
const int LEVEL_RES1_LOW_PIN = 44;
const int LEVEL_RES2_HIGH_PIN = 42;
const int LEVEL_RES2_LOW_PIN = 40;

// RED HALT BUTTON
bool ic = false; 
unsigned long lastStopPress = millis();
void stopButtonInterrupt() {
  if ( millis() - lastStopPress > 2000 ) {
    lastStopPress = millis(); // ez debounce
    if (ic) { 
      ic = false;
    } else { 
      ic = true;
    }
  }
}

// BLACK START BUTTON
bool startCycle = false;
bool mixRunning = false;
unsigned long lastStartPress = millis();
void startButtonInterrupt() {
  if (!mixRunning) {
    if ( millis() - lastStartPress > 2000 ) {
      lastStartPress = millis(); // ez debounce
      startCycle = true;
    }
  }
}

void setup() {
  delay(3000);
  Serial.begin(9600);

  // EMERGENCY STOP
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), stopButtonInterrupt, HIGH); // CHANGE

  // START
  pinMode(3, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(3), startButtonInterrupt, HIGH); // CHANGE

  // RTC
  rtc.begin();
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  // LCD
  lcd.init();
  lcd.clear();
  lcd.backlight();
  lcd.setCursor(0,0); lcd.print("A NAME HERE");
  lcd.setCursor(17, 0); lcd.print("NOR"); // LCD INTERRUPT CONDITION NORMAL INITIAL STATE

  // PUMP RELAY
  pinMode(PUMP_PIN, OUTPUT); // OUTPUT PUMP

  // VALVE RELAYS
  pinMode(FILL_PIN, OUTPUT); // FILL RELAY
  pinMode(MIX_PIN, OUTPUT);  // BUCKET MIXER RELAY
  pinMode(RES1_PIN, OUTPUT); // RES 1 OUTPUT RELAY
  pinMode(RES2_PIN, OUTPUT); // RES 2 OUTPUT RELAY

  // Ppump RELAYS
  pinMode(NUT_1_PIN, OUTPUT); 
  pinMode(NUT_2_PIN, OUTPUT);
  pinMode(NUT_3_PIN, OUTPUT);
  pinMode(NUT_4_PIN, OUTPUT);
  pinMode(NUT_5_PIN, OUTPUT);
  pinMode(NUT_6_PIN, OUTPUT);
  pinMode(NUT_7_PIN, OUTPUT);
} 

void relay(String name, bool state) {
  if      (name == "PUMP") { if (state) { digitalWrite(PUMP_PIN,  HIGH); } else { digitalWrite(PUMP_PIN,  LOW); }} 
  else if (name == "FILL") { if (state) { digitalWrite(FILL_PIN,   LOW); } else { digitalWrite(FILL_PIN, HIGH); }}
  else if (name == "MIX" ) { if (state) { digitalWrite(MIX_PIN,    LOW); } else { digitalWrite(MIX_PIN,  HIGH); }}
  else if (name == "RES1") { if (state) { digitalWrite(RES1_PIN,   LOW); } else { digitalWrite(RES1_PIN, HIGH); }}
  else if (name == "RES2") { if (state) { digitalWrite(RES2_PIN,   LOW); } else { digitalWrite(RES1_PIN, HIGH); }}
  else if (name == "NUT1") { if (state) { digitalWrite(NUT_1_PIN, HIGH); } else { digitalWrite(NUT_1_PIN, LOW); }} 
  else if (name == "NUT2") { if (state) { digitalWrite(NUT_2_PIN, HIGH); } else { digitalWrite(NUT_2_PIN, LOW); }}
  else if (name == "NUT3") { if (state) { digitalWrite(NUT_3_PIN, HIGH); } else { digitalWrite(NUT_3_PIN, LOW); }}
  else if (name == "NUT4") { if (state) { digitalWrite(NUT_4_PIN, HIGH); } else { digitalWrite(NUT_4_PIN, LOW); }}
  else if (name == "NUT5") { if (state) { digitalWrite(NUT_5_PIN, HIGH); } else { digitalWrite(NUT_5_PIN, LOW); }}
  else if (name == "NUT6") { if (state) { digitalWrite(NUT_6_PIN, HIGH); } else { digitalWrite(NUT_6_PIN, LOW); }}
  else if (name == "NUT7") { if (state) { digitalWrite(NUT_7_PIN, HIGH); } else { digitalWrite(NUT_7_PIN, LOW); }}
}

void updateTime(int col, int row) {
  DateTime now = rtc.now();
  String year = String(now.year()).substring(2,4);
  String month = String(now.month()); String day = String(now.day());
  String hours = String(now.hour()); if (now.hour() < 10) {hours = "0" + hours;}
  String minutes = String(now.minute()); if (now.minute() < 10) {minutes = "0" + minutes;}
  String dt = month + "/" + day + "|" + hours + ":" + minutes;
  // Serial.print("CLOCK: "); Serial.println(dt);
  lcd.setCursor(col, row); lcd.print(dt);
}

void status(String message) {
  lcd.setCursor(0,0); lcd.print("                ");
  lcd.setCursor(0,0); lcd.print(message);
}

void delayInt(int secs) { // INTERRUPT SENSITIVE DELAY
  int x = 0; 
  while (x < secs and !ic) { 
    x++; delay(1000); 
    Serial.println(ic); // required somehow
  }
}

int x = 0;
void mixingCycle() {
  Serial.println(ic);
  if(ic){return;} status("CYCLE START"); delay(1000);
  status("INITIAL FILL"); relay("FILL", true);
  
  delayInt(10); if(ic){return;} // WOULD WAIT FOR MIN WATER LEVEL

  status("MIN WATER OK"); delayInt(10); if(ic){return;}
  status("PUMP/MIX ON");  relay("MIX", true); delayInt(3); if(ic){return;} relay("PUMP", true); delayInt(10); if(ic){return;}
  status("DOSING SILICA"); relay("NUT1", true); delayInt(10); if(ic){return;} relay("NUT1", false); delay(1000);
  status("MAX WATER FILL"); 
  
  delayInt(10); if(ic){return;} // WOULD WAIT FOR FULL WATER LEVEL

  status("MAX WATER OK" ); delayInt(10); if(ic){return;}
  status("FILL OFF"); relay("FILL", false); delayInt(3); if(ic){return;}
  status("MIXING SILICA"); delayInt(20); if(ic){return;}

  status("DOSING CALMAG"); relay("NUT2", true); delayInt(10); if(ic){return;} relay("NUT2", false); delay(1000);
  status("MIXING CALMAG"); delayInt(10); if(ic){return;}
  status("DOSING MICRO" ); relay("NUT3", true); delayInt(10); if(ic){return;} relay("NUT3", false); delay(1000);
  status("MIXING MICRO"); delayInt(10); if(ic){return;}
  status("DOSING BLOOM" ); relay("NUT4", true); delayInt(10); if(ic){return;} relay("NUT4", false); delay(1000);
  status("MIXING BLOOM"); delayInt(10); if(ic){return;}
  status("DOSING GROW"  ); relay("NUT5", true); delayInt(10); if(ic){return;} relay("NUT5", false); delay(1000);
  status("MIXING GROW"); delayInt(10); if(ic){return;}
  status("DOSING NECTAR"); relay("NUT6", true); delayInt(10); if(ic){return;} relay("NUT6", false); delay(1000);
  status("MIXING NECTAR"); delayInt(10); if(ic){return;}
  status("DOSING THRIVE"); relay("NUT7", true); delayInt(10); if(ic){return;} relay("NUT7", false); delay(1000);
  status("MIXING THRIVE"); delayInt(10); if(ic){return;}

  status("FINAL MIXING"); delayInt(10); if(ic){return;} // CONTINUE MIXING EVERYTHING FOR A WHILE
  status("PUMP/MIX OFF"); relay("PUMP", false); delayInt(10); if(ic){return;} relay("MIX", false); delay(1000);
  status("MIX CYCLE DONE!");
  lcd.setCursor(0,1); lcd.print("LAST: "); updateTime(6,1);
  delay(1000);
}

void disableAllRelays() {
    digitalWrite(PUMP_PIN,  LOW);

    digitalWrite(FILL_PIN, HIGH);
    digitalWrite(MIX_PIN,  HIGH);
    digitalWrite(RES1_PIN, HIGH);
    digitalWrite(RES2_PIN, HIGH);

    digitalWrite(NUT_1_PIN, LOW);
    digitalWrite(NUT_2_PIN, LOW);
    digitalWrite(NUT_3_PIN, LOW);
    digitalWrite(NUT_4_PIN, LOW);
    digitalWrite(NUT_5_PIN, LOW);
    digitalWrite(NUT_6_PIN, LOW);
    digitalWrite(NUT_7_PIN, LOW);
}

bool loopStartup = false;
void loop() {
  if (!loopStartup) {
    loopStartup = true;
    disableAllRelays();
    delay(3000);
  }
  updateTime(9,3);

  // CONTROL FOR SYSTEM
  // DETECT WATER LEVELS AND SET startCyle
  // OR PUMP OUT EXISTING NUTRIENTS (CODE DOESN'T EXIST YET, NEED SENSORS)
  // OR DECIDE IT HASN'T BEEN LONG ENOUGH AND DON'T CYCLE AT ALL
  // LOGIC ETC

  if (!ic) {
    lcd.setCursor(17, 0); lcd.print("NOR");
    if (startCycle) {
      startCycle = false;
      mixRunning = true;
      mixingCycle(); delay(1000);
      disableAllRelays();
      mixRunning = false;
    }
  } else { // WHEN STOPPED MANUALLY
    startCycle = false;
    mixRunning = false;
    lcd.setCursor(0, 0); lcd.print("CANCEL           INT");
  }
  delay(1000);
}

It is well taken always.

The thing I experienced about doing it in loop is that you have to be holding the button at the point that the code reads the state. If I use the interrupts to set values then the button only has to be pressed once at any time. Or am I wrong and there is an easier way to do that?

This prototype, which is just proving out concepts, is 90% code complete. The stop/cancel and start buttons work really well. I just need a few water level sensors in loop. This could be a final but I constantly revise things as I gain understanding.

Probably once I get the p. pumps, sensors and pex hardware and start implementing that, debugging could lead to another rewrite.

Not that I don't want to learn to do better. But maybe better is the next project this time when I just want to get growing.

Generally speaking things that belong together are on a line together as events. To me it reads easier because each line is an event rather than hunting for instruction blocks and tons of scrolling... but I hear you.

Your code runs a loop tens of thousands of times per second. (Without any delays in your code) You wont miss a button press

You need to get rid of the delays in your loop. Delays should only be used if you need to test something quickly as they are blocking.

Not questioning you at all... but doesn't running loop at full speed use a lot more energy / create a lot more heat? On my LED controller Mega running it at full speed requires a heat sink. I'd rather not take half a watt off my little mofsets if I don't need to. Maybe that was just that project?

No thats not the case. Putting a delay in your code the cpu is still running at its current clock speed

The only way to reduce energy being used is to temporarily put it in low power mode.

I believe modern computer cpus will lower the cpu frequency when its not being used to reduce power consumption

I thought about it and did a bunch of stuff with the AI and reading docs, I am not getting how a state machine allows me to get around the problem of cancelling during delay. Do you think you could give a very basic example of that?

Thanks for the help. That was so great. Glad you took the time to type that.

Sure, don't put a break between the states and execution will continue until there is a break or the switch runs out of cases.

And that can lead to blocking and not sensing inputs.
Just like using delay().

You haven't seen the point of non-blocking code yet. When you get it down, you can write TASKS that run together.

I made an example for a member who wanted leds to twinkle like stars so 1st task in loop() blinked leds slow enough to see twinkle with some random to the twinkles.
But they had to twinkle-blink so 1st task got an ON/OFF and 2nd task got make to turn that ON/OFF. A 3rd task got added when each time one "star" blinked OFF, a different star blinked ON where only a few were twinkling at any time... power limit thing.
I didn't even try wedging or weaving it all in one big code knot. I got past that by 1987. I can write a task in test code and add it to any non-blocking sketch.. if I take proper care I get few if any bugs.

Writing tasks can reduce the number of indents to trace through dramatically. I did fixed-price jobs, I learned to reduce debug hours as a priority and still look to that.

With an Uno you can check inputs at 50KHz or faster. If you use direct port manipulation for digital reads and writes you can do those 5X faster than Arduino digitalRead() on single pins, when you read whole ports in a cycle the advantage gets bigger, same with writes.

Tasks should do one small thing and return. 20 ms later, next step and between those you other tasks run their one small step --- the result is smooth interleaving without the overhead of interrupts.

Fair enough! I looked at the material and I have several specific questions;

  1. How does this allow integration of a delay which can be stopped at any time? Even in the example near the top of this thread, the controlling loop still just uses explicit delay(x), which is not going to be interrupted by a change in a button press, nor is a command already running going to be interrupted if it were a loop of its own. am I missing something about the origin caller for this?

  2. Why bother creating an enum of states when the state is manually set at the end of each function? This is just a looping pattern setting its own state, it is topologically equivalent to calling the code directly, except that it is loop controlled. I see how that could be handy, but the enumeration of states does not ever seem to be used in the code?

    case countTo10:
.
.
.
        myStateVar = sayGoodbye;
  1. A project requirement is that the timing between commands must vary based on the command that was just executed. All of the examples I have seen of this do not integrate variable waiting times, and so it is harder for me to put this into the context of my project.
      if( relayName[0] ) {
         relay(relayName, true);
         delay(5000);
         relay(relayName, false);
         delay(1000);
      } else {
         delay(5000);
      }

I feel like if I were to make another attempt at this, I would import Dictionary and create a json like object containing values to be iterated and do it that way, with relay number, timing, etc, along with the command name and other values like LCD status etc, and use the same global variable interrupt checking in loop.

A Demo-Code explaining the switch-case state-machine and how to do things (almost) in parallel

Ok, I get it now. I see how the delay is controlled with a global.

All I can say is... syntactically speaking, I absolutely hate how this works with a real passion. What a mess! (not that what I came up with is any better in this regard)

I very much appreciate the help from everyone, thank you so much.

*also, duh, enumerating the states defines them. because of course it does.

We replace the delay with time checks (Arduino millis() and micros() 'timers') where until the wait is over, the function returns to loop() allowing the next function to run.

Non-blocking Tutorial with commonsense explanation and code.

Non-blocking serial reads including a state machine section. Much explains and code in this one, techniques!

1 Like