Adding timer function to an IF condition with multiple conditions

Hello All,

Appreciate any help or links to articles that can educate me on how to set up a timer for multiple conditions within an IF condition.

Using an Arduino ESP32 Nano with 4 water flow switches that are switching GND to a digital input to confirm water is flowing. If all switches are closed I have the Nano output to a relay closing contacts for a separate latching circuit. I have an initial 5 second delay on startup to allow the pump to get things moving before checking the flow switches are closed. The separate latching circuit needs to be reset manually if any of the safeties are open, and in some cases a flow issue might resolve itself within a short period, of which I would like to give it that time to do so. In this 4 water flow safety circuit I would like to set a 1 or 2 second delay in that if any of the flow switches OPEN for less than the 1-2 sec period it won't de-energize the relay.

Here is what I have thus far:

int out1 = 10;
int in1 = 2;
int in2 = 3;
int in3 = 4;
int in4 = 5;


const int pauseTime = 5000; // 5 seconds pause

unsigned long startTime = 0; // Variable to store start time



void setup() {
  // put your setup code here, to run once:
pinMode(out1, OUTPUT);
digitalWrite(out1, HIGH);

pinMode(in1, INPUT_PULLUP);
pinMode(in2, INPUT_PULLUP);
pinMode(in3, INPUT_PULLUP);
pinMode(in4, INPUT_PULLUP);

startTime = millis(); // Set start time when program begins

}


void loop() {
 
 if (millis() > startTime + pauseTime) { 

  // put your main code here, to run repeatedly:
  if( (digitalRead(in1) == LOW) && (digitalRead(in2) == LOW) && (digitalRead(in3) == LOW) && (digitalRead(in4) == LOW) ) {
    digitalWrite(out1, HIGH);
}
  else {
    digitalWrite(out1,LOW);
}


  }
}

Thank you in advance!

Best,

Mark

First thing, review your use of millis().

Rather than run through a number of if() statements, my preferred way to handle these types of requirements, is to have an array[] of event times… then when a single event is completed, step forward to the next event time… that way you’re only looking / testing for a single time match l
Code will be shorter, and likely run faster, and easier to modify.

Even better, if you have an event structure, you can make an array of those structures, which might include different intervals, pins, actions etc,

1 Like

Thank you for the advice lastchancename. I will dig into using event structures.

Best regards,

Mark

look this over

  • separate timers for startup and disruption
struct {
    const byte     PinEn;
    const byte     PinRelay;
    const byte     PinSensor;
    const byte     PinLedNoFlow;
    const char    *label;

    bool           open;
    int            state;

    byte           enState;
    unsigned long  msec;
    unsigned long  msecPeriod;
}
valve [] = {
  //  En  Rly  Sen  Led  label
    { A1,  12,  A2,  11, "valve-1" },
};
const int Nvalve = sizeof(valve)/sizeof(valve [0]);

enum { S_Off, S_Start, S_Flow, S_Pending, S_NoFlow };

enum { RelayOff = HIGH, RelayOn  = LOW };
enum { LedOff   = HIGH, LedOn    = LOW };
enum { Flow     = LOW,  NoFlow   = HIGH };

const unsigned long MsecPeriodStartup  = 5000;
const unsigned long MsecPeriodNoFlow   = 2000;

char s [90];

// -----------------------------------------------------------------------------
void
loop (void)
{
    unsigned long msec = millis ();

    // monitor valves
    for (int n = 0; n < Nvalve; n++)  {
        switch (valve [n].state)  {
        case S_Off:                     // nothing to do
            break;

        case S_Start:
            if (Flow == digitalRead (valve [n].PinSensor))  {
                sprintf (s, "flow-detected - %s", valve [n].label);
                Serial.println (s);
                valve [n].state = S_Flow;
            }

            else if (msec - valve [n].msec >= valve [n].msecPeriod)  {
                sprintf (s, "no-flow after start - %s", valve [n].label);
                Serial.println (s);
                valve [n].state = S_NoFlow;
            }
            break;

        case S_NoFlow:
            digitalWrite (valve [n].PinRelay,     RelayOff);
            digitalWrite (valve [n].PinLedNoFlow, LedOn);
            break;

        case S_Flow:
            if (NoFlow == digitalRead (valve [n].PinSensor))  {
                sprintf (s, "no-flow pending - %s", valve [n].label);
                Serial.println (s);
                valve [n].state      = S_Pending;
                valve [n].msecPeriod = MsecPeriodNoFlow;
                valve [n].msec       = msec;
            }
            break;

        case S_Pending:
            if (Flow == digitalRead (valve [n].PinSensor))  {
                sprintf (s, "flow resumed - %s", valve [n].label);
                Serial.println (s);
                valve [n].state = S_Flow;
            }

            else if (msec - valve [n].msec >= valve [n].msecPeriod)  {
                sprintf (s, "no-flow - %s", valve [n].label);
                Serial.println (s);
                valve [n].state = S_NoFlow;
            }
            break;

        }
    }

    // monitor control to enable valve
    for (int n = 0; n < Nvalve; n++)  {
        byte en = digitalRead (valve [n].PinEn);

        if (valve [n].enState != en)  {
            valve [n].enState  = en;
            delay (20);                 // debounce

            if (HIGH == en)
                continue;

            if (S_Off == valve [n].state)  {
                sprintf (s, " valve on,  %d, %s", n, valve [n].label);
                Serial.println (s);

                valve [n].state = S_Start;
                valve [n].msecPeriod = MsecPeriodStartup;
                valve [n].msec       = msec;

                digitalWrite (valve [n].PinRelay,     RelayOn);
                digitalWrite (valve [n].PinLedNoFlow, LedOff);
            }
            else  {
                sprintf (s, " valve off,  %d, %s", n, valve [n].label);
                Serial.println (s);

                valve [n].state = S_Off;

                digitalWrite (valve [n].PinRelay,     RelayOff);
                digitalWrite (valve [n].PinLedNoFlow, LedOff);
            }
        }
    }
}

// -----------------------------------------------------------------------------
void
setup (void)
{
    Serial.begin (9600);
    Serial.println (Nvalve);

    for (int n = 0; n < Nvalve; n++)  {
        sprintf (s, " configure valve %d, %s", n, valve [n].label);
        Serial.println (s);

        pinMode (valve [n].PinEn,     INPUT_PULLUP);
        pinMode (valve [n].PinSensor, INPUT_PULLUP);

        pinMode (valve [n].PinLedNoFlow, OUTPUT);
        digitalWrite (valve [n].PinLedNoFlow, LedOff);

        pinMode (valve [n].PinRelay, OUTPUT);
        digitalWrite (valve [n].PinRelay, RelayOff);
    }
}
1 Like

Wow, thank you very much gcjr. This is a bit above my pay grade, I will need to play with this and see how this works. I am sure I will have some follow up questions. Thank you again!

i tested it with buttons and LEDs on a Multifunction shield. I chose on/off conditions based on that board which may not work with your hardware. change the enums.

suggest using The C Programming Language for questions about the language (i.e. struct, enum)

1 Like

Ok, much appreciated for the additional insight and programming language. Do you take on paid projects in case I get stuck? I am a novice with this and I can usually figure something out after enough time.

Thank you again!

i think you'll do fine with help from the forum

1 Like

Just one additional quick question: what is the pinEn ? Is that to enable the pin for the pulldown resistor? Should that be the same as the Sen pin?

valve [] = {
  //  En  Rly  Sen  Led  label
    { 2,  10,  2,  11, "valve-1" },

Thanks again!

Your problem is the same or exactly opposite to basic "keep alive" code, remaining in the state of doing one thing until some criterion is meant that means it's really time to start doing another.

I may get this backwards or otherwise wrong, and I am only talking about one pimp.

Every so often (dozens of times a second, or even more) check the pimp. If it is functioning, reset a timer variable to zero.

At the same time, and totally independent to that (just another line of code in your free running loop) see how long it has been since the pimp was working.

If the time since the pomp timer was reset to zero exceeds the window where you want to say, ok, that pimp is def failed, we call this the expiration of the timer, and you can mark the pimp as truly malfunctional.

I have a demo of keeping alive that is straightforward code using millis() in a common pattern that uses no language features beyond very basic beginning C.

You can tinker with it in the simulator and see for yourself:

Setting a timer is done by storing the current time in a timer. A timer is just a variable that can hold a time. Here the time is the number of milliseconds since the beginning of time, measured from when the program starts.

Checking the timer is just determining the elapsed time, which is a simple matter of calculating the difference between now and then, just like real life.

Expiration is simply when the elapsed time exceeds the duration you want.

The demo codes that literally. Set, check and action on expiry.

If you keep your finger on the button long enough, the LED will illuminate. Try it here:


The code

// https://forum.arduino.cc/t/how-to-delay-using-millis/1142938
// https://forum.arduino.cc/t/timer-on-pin-low-high-detection/1325679

// https://wokwi.com/projects/415656135572365313

// version 5. same same with variants

const byte buttonPin = 2;
const byte ledPin = 3;

const unsigned long actionDelay = 2500; // action 2.5 seconds for testing - life too short

void setup() {
  Serial.begin(115200);
  Serial.println("\nWake up!\n");

  pinMode (buttonPin, INPUT_PULLUP);
  pinMode (ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
}

void loop() {
  static unsigned long startTime; // time at action

  unsigned long now = millis();   // fancier 'currentMillis' ooh!
  bool buttonIsPressed = digitalRead(buttonPin) == LOW;  // button pulled up! true is button pressed

// when the button is _not_ pressed, reset the timer, turn OFF the LED
  if (!buttonIsPressed) {
    startTime = now;
    digitalWrite(ledPin, LOW);
  }

// when (if!) the timer expires, turn ON the LED
  if (now - startTime >= actionDelay) {
    digitalWrite(ledPin, HIGH);
  }
}

HTH

Don't worry about that, that code above many ppls' pay grade. :expressionless:

Too soon to worry about paying anyone anything. We have a real desire to help you get over this, it is a basic concept and you should struggle however you must to see it working and to see how it works and to learn how to exploit the pattern.

a7

1 Like

a pin for a momentary switch to toggle the valve on and off

it's never obvious to me what people find hard to understand.

th ecode implements a state machine. The behavior of the code depends on the value of the state variable. I suggest you try to follow the transitions the value of state changes and understand what can happen in each state

1 Like

In the demo linked in #10 above

think of the button not being pressed as the pemp running to plan, and when the button is held down the pump has stalled.

You can press the button all you want, the LED stays off, but if you stall the pimp (go,d your finger on the button for 2.5 seconds, a long fault) the LED lights up. That is logically equivalent to wanting to rally consider that the thing has failed hard.

Some inversion of the input (not pressing would be the problem) and/or the output (LED on would means eveything is OK) should be able to serve as the condition.

You'd need four timers. Coded naively that woukd be four separate variable.
s and mean basically writing by copy/paste/edit four identical sequences of code, but for the variables they are informed by and manipulate.

Which can and certainly is done… but when we see that, the recommendation is to back off a bit and add array variables and functions to your programming skills; in my opinion this is best done outside the context of the immediate goal.

But the first step would by to get the literal code working with just one pimp. Write for one the way in which you want to handle one.

Then put arrays and functions on the table:

https://docs.arduino.cc/built-in-examples/control-structures/Arrays/

and

https://docs.arduino.cc/learn/programming/functions/

and generalize the one working literal pattern so that an array of timer variables can be handled by one function that will handle any number of pumps, pumps distinguished as are so many things in code by having a number assigned to them.

Learning arrays and functions as such will be much more valuable than reverse engineering their use by analysing (or struggling to do) some use of them in some fairly obscure context, even if it is a working sketch that absolutely nails the requirements. Which @gcjr may very well do, TBH I looked at it and even knowing what I was looking for, namely setting timers, checking them and handling their expirations, all of which must be in there in some way, none of those things was even close to obvious.

BTW and FWIW: Now is the time to get into another C/C++ habit: numbers when number things start with zero, so your four would be numbers 0, 1, 2 and 3.

This is for many reasons, not the least of which is that arrays are handled by indexing, and array elements start with the one found at index 0.

If you want to get some glazed eyes, read here and just read like you already understand it:

https://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD831.html

but srsly, don't. Maybe one day. I only mention it because just now all I could find was Dijkstra's hand-written original. It's hard enpough to read in nice type.

a7

can you post an example?

i thought The Elements of Programming Style was one of the best books i read on how to program. It reviewed flawed programs, from college textbooks no less, exposed their flaws and showed how to correct them

starting on pg 116, i think The C Programming Language succintly discusses the use of a struct to describe an x,y coordinate and uses in in an array

1 Like

Thank you
From the originals many thanks

I am not sure an example of what it is you want me to post.

I have read both books you recommend and would second that recommendation for both. Both are well worth any time spent reading them, and would return many times over for the rest of the time one plays in this area.

My copy of The C Programming Language from the 20th century shows def signs of heavy use. My The Elements of Programming Style shows less use than my The Elements of Style that it riffs on, another book I would argue is equally important for ppl who pretend to communication.

A book I have somewhere, however, is more suited for noobs if my memory is accurate. The two you cite are great but are better for someone who knows quite a bit more than nothing. What's missing from both is any time spent on the matter of programming machines to our will to suit our purposes.

Niklaus Wirth's Algorithms + Data Structures = Programs might yet be stiff going for a beginner, but at least it tries to teach programming in a realistic manner.

Noobs are barely aware that programs run by sequences of lines of code that can form steps, repeat things and make decisions based on values held in variables.

Or that such code can, in fact, be read like anything written. With the possible exception of APL. :expressionless:

Add to sequence, choice and iteration the concept that an entire algorithm can be subordinated in a general manner for use wherever it is needed (functions, or these days objects) and you've built a solid basis for learning how a particular language's features are used at the pseky-syntax level to implement them.

Much like I said - reading the code you posted was massively informed by knowing what it must contain and an very good idea of how it was going to function. Without that prior experience, the code is not easy to see as anything other than impenetrable nonsense.

HTH

a7

reverse engineering is difficult. it's even more difficult if there's no idea what the thing does. fortunately we have the OP's "description" of the problem.

proper design procedure is to describe requirements in a form that the user can understand and verify, then code from the design diagrams. the forum often skips that step, replacing it with 10s of posts asking questions that often don't get answered. i try to skip those "steps"

Hello a7,

Thank you very much as well for your suggestion and insight. I took a look at your example and yes, basically what I need is an inverted version of that (switch opens for 2.5 seconds vs closed). I incorporated parts of your code into mine and came up with the following code. After some tweaking, it seems to work. Let me know if you see any glaring issues. I also link to my Wokwi demo below the code.

I am also planning on incorporating other safeties into the microcontroller (which are in a separate circuit now), so I will dive into event structures recommended by gcjr. I appreciate both of your help with this. At the moment I am behind the eight-ball and need to get something that works asap.

int out1 = 10;
int in1 = 2;
int in2 = 3;
int in3 = 4;
int in4 = 5;


const int pauseTime = 5000; // 5 seconds pause

unsigned long startTime = 0; // Variable to store start time

const unsigned long actionDelay = 2500; // action 2.5 seconds for testing

void setup() {
  // put your setup code here, to run once:
pinMode(out1, OUTPUT);
digitalWrite(out1, HIGH);

pinMode(in1, INPUT_PULLUP);
pinMode(in2, INPUT_PULLUP);
pinMode(in3, INPUT_PULLUP);
pinMode(in4, INPUT_PULLUP);

startTime = millis(); // Set start time when program begins

}


void loop() {
 
 if (millis() > startTime + pauseTime) { 

  static unsigned long readTime; // time at action

  unsigned long now = millis();   // fancier 'currentMillis' ooh!
  bool flowIsLow1= digitalRead(in1) == HIGH;  // button pulled up! true is button pressed
  bool flowIsLow2= digitalRead(in2) == HIGH;  // button pulled up! true is button pressed
  bool flowIsLow3= digitalRead(in3) == HIGH;  // button pulled up! true is button pressed
  bool flowIsLow4= digitalRead(in4) == HIGH;  // button pulled up! true is button pressed


  // put your main code here, to run repeatedly:
  if( (!flowIsLow1) && (!flowIsLow2) && (!flowIsLow3) && (!flowIsLow4) ) {
    readTime = now;
    digitalWrite(out1, HIGH);
  }


// when (if!) the timer expires, turn ON the LED
  if (now - readTime >= actionDelay) {
    digitalWrite(out1, LOW);
  }

  }
}

Here is the Wokwi demo of what I have, I am using slide switches to simulate the water flow switches.

https://wokwi.com/projects/420304243098346497

Thank you again!

Best regards,

Mark

Thank you gcjr for your help. I am diving into this now and appreciate your patience in answering my questions. I really am a novice and need to look up everything (like what is a "state variable"). I try to find the relationships in the code, and I do learn faster when trying to solve an actual problem I'm having. I do plan on incorporating the other safeties that are currently in another circuit using switches and relays (water temp, over temp, water level, etc) into a single microcontroller so I need to learn these best practices.

Thank you again fort your help!

Best regards,

Mark

Thanks for this, but shows you how little I know, I don't know what these numbers refer to, do you mean the digital input numbers? I had to spend some time understanding and correlating between Arduino PIN numbers, physical PIN numbers, printed PIN numbers etc. and how to specify in the Sketch.