Main loop - break, continue or return?


#1

Hi,

I have a tank with multiple float switch sensors that I want to process from the bottom up - i.e. if the tank is empty there’s no need to report the state of the sensor at the halfway point. To avoid a deeply nested if statement I thought I’d check the lowest switch first and if it shows the tank empty then process it and loop back around. If the lowest switch is shows above empty, then go to the next switch.

Thing is, I’m unsure how to exit the loop() properly. If it is called as a subroutine I should return and it’ll get called again immediately, right? Or if it uses loop semantics then is break or continue the appropriate call? Or, alternatively, is this approach an anti-pattern in the Particle world and I should go back to the giant if statement?

Any advice is eagerly welcomed. I tried searching but either loop() is a tough search term or my search-fu is a lot worse than I thought. I found surprisingly little on best practices for loop() and tons of content on for/while loops.

Thanks.

void loop() {
  // Check lowest float sensor
  if(switchStateL == LOW) {
    if (floatstateL == TRUE) { // Was it low last time?
      floatstateL = FALSE;     // Save state change
      // Do some stuff
    }
  // Continue?  Break? Return?
  }

  // Tank level above empty. Fall through.
  
  // Check for state change on low sensor if it previously showed empty
  if (floatstateL == FALSE) {
    floatstateL = TRUE;     // Save state change
    // Do some stuff
  }
 
  // Process middle & top sensors
  // Omitted for brevity
}

#2

loop() is continuously called by Device-OS but otherwise operates like any other function.

A break statement wouldn’t even compile unless it was in a for, while or switch statement, and only to break out of those constructs not the function itself.

You can explicitly return or just fall off end of the function.


#3

How about

#define levelSwitch0 A0
#define levelSwitch1 A1
#define levelSwitch2 A2
#define levelSwitch3 A3
#define levelSwitch4 A4


int detectLevel() {
    
    // you may need to put some debounce here to account for water sloshing around in the tank

    int tankLevel   = digitalRead(levelSwitch0) * 1 
                    + digitalRead(levelSwitch1) * 1 
                    + digitalRead(levelSwitch2) * 1 
                    + digitalRead(levelSwitch3) * 1 
                    + digitalRead(levelSwitch4) * 1;
    
    return tankLevel;  // returns 0 - 5
}


void setup() {
    Serial.begin();
    while(!Serial.isConnected()) // wait for Host to open serial port
        Particle.process();
    
    Serial.print("\nTank level system");
    
    pinMode(levelSwitch0, INPUT_PULLUP);
    pinMode(levelSwitch1, INPUT_PULLUP);
    pinMode(levelSwitch2, INPUT_PULLUP);
    pinMode(levelSwitch3, INPUT_PULLUP);
    pinMode(levelSwitch4, INPUT_PULLUP);


}

void loop() {
    int tankLevel;
    static int prevTankLevel;
    
    tankLevel =  detectLevel() * 20;
    if(prevTankLevel != tankLevel) {
        prevTankLevel = tankLevel;
        Serial.printf("\nTank level is %d%%", detectLevel() * 20);
    }
    
    // do other stuff

}

#4

If all the actions for each individual sensor are the same then I’d opt for an explicit for() loop which you control when to drop out of. If you have the control variable outside of your for() then you can even act upon the final index once dropped out of the loop.

@shanevanj, I’d rather give each switch its own place to be able to filter bad readings.
With the approach shown, you couldn’t tell apart 1 1 0 0 0 (=2) from a bad sensor producing the same with 1 0 0 0 1.
With this (which I’d consider doing in a loop again) you can filter out bad sensors

    int tankLevel   = digitalRead(levelSwitch0) *  1 
                    + digitalRead(levelSwitch1) *  2 
                    + digitalRead(levelSwitch2) *  4 
                    + digitalRead(levelSwitch3) *  8 
                    + digitalRead(levelSwitch4) * 16;

BTW, instead of levelSwitchX I’d suggest you use something like this

const int pinSW[] = { D0, D1, D2, D5, D6 };
const int numSW   = sizeof(pinSW) / sizeof(pinSW[0]);

void setup() {
  for (int sw = 0; sw < numSW; sw++) 
    pinMode(pinSW[sw], INPUT_PULLUP);
}

void loop() {
  int sw;
  for (sw = 0; sw < numSW; sw++) {
    // do stuff
    if (!digitalRead(pinSW[sw]) break;
  }
  switch(sw) {
    case 0: // special treatment for switch 0
      break;
    case 1: ...
      break:
    ...
    case numSW: // case that we just dropped out of for() without break
      // do stuff
      break;
    default:
      // treat the non-special cases
      break;
  }
} 

This way you can add/remove switches to the array without ever having to revisit the code (providing all are treated the same).


#5

@ScruffR, This isn’t my thread, but thanks for going to the trouble of posting that code and comments.

Just last night I was thinking about the CODE for a project exactly like this. And behold, this morning you have posted a very clean solution. I wish my brain worked like yours.

Thanks again !


#6

I’m torn as to which reply to mark as the solution here. On the one hand, @joel provided valuable info as to how loop() works which was the essence of my original question. On the other hand, @shanevanj and @ScruffR gave me better ways to approach my problem in code. With apologies to @joel, point goes to code in this case. Thanks go out to all who responded, though. I very much appreciate the help!

Of the two code examples, @ScruffR’s use of arrays and the powers-of-two differentiation on the switches is the one I’ll end up using, mainly because it gives me a way to detect if a switch fails.

And, yes, the switches do get special treatment. In particular, once the bottom switch trips there about 3 days worth of liquid left, after which I will stop the pump so it doesn’t run dry. During those three days text alerts are sent with increasing frequency. All the other switches send a single notification each. This code framework will support the differentiated responses.

Thanks again, everyone!


#7

There is an option that allows you to read all of the switches on one analog pin. It requires a few resistors, but only requires one pair of wires running between the MPU and the water barrel.

The following diagram shows the wiring concept.

image

Essentially, this circuit creates a voltage divider that will deliver a voltage to Pin A0. That voltage will vary between 0 and 3.3v depending on the value of the resistors that are used and the number of switches that are closed.

An analog read of pin A0 will return a number between 0 and 4095 based on the voltage, so you can simply set thresholds based on the number that is returned.


#8

That’s pretty cool! I’m not constrained on pins though, but if I ever am I’ll keep this in mind.

Thanks!