user18522: I'm building a control system for three sump pumps using a single Arduino and a set of relays.  I have float sensors for each one, and need the Arduino to trigger the relays when the float sensors are triggered.  Sensor 1 triggers pump 1, sensor 2 triggers pump 2, etc.
Simple enough, but I also need each pump to remain on for 30 seconds after its float sensor returns to normal.  This throws me for a loop since I need the other pumps and sensors to function during this delay time.  I quickly found that the blocking delay() function is no longer feasible.
How can I trigger the relays, keep them on for 30 seconds after the sensors return to normal, but all the while still monitor and control the other sensors/relays?  Would a non-blocking timer solution work?  Would I have to delve into interrupts?

user14737: 
Would a non-blocking timer solution work?

Yes.  Your "timer" doesn't have to be anything more than a start time from millis() and a flag.  Then your loop just compares the start time to the current millis() value, and if it's more than the interval you wanted, do the "thing".  In code:
uint32_t startTime;
bool waiting = false;
const uint32_t PUMP_DURATION = 30000UL;

void setup()
{
  // start the "timer"
  startTime = millis();
  waiting = true;
}

void loop()
{
  if (waiting && (millis() - startTime >= PUMP_DURATION)) {
    waiting = false;
    // do something now!
    foo();
  }

The flag keeps it from checking when the pump isn't running.
If you have multiple timed things, just make another set of variables (i.e., startTime2 and waiting2).  Give 'em better names, eh?
There are also Arduino libraries that encapsulate this common design pattern.  TimeMark is one.
Here's some sample code on the Arduino forum.

user13432: The "blink-without-delay"-pattern shows how this can be solved but with several time periods and logic this becomes complex. Some abstraction is needed. The Timemark library provides a solution. Belows is the logic for a single sensor-relay pair with a state trace to serial:
#include <Timemark.h>

const uint32_t TURN_OFF_TIMEOUT = 30000L;
Timemark turnOff(TURN_OFF_TIMEOUT);
const int sensorPin = 2;
const int relayPin = 3;

void setup()
{
  Serial.begin(9600);
  while (!Serial);

  pinMode(sensorPin, INPUT_PULLUP);
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);

  trace.start();
}

void loop()
{
  if (trace.expired()) {
    Serial.print(millis());
    Serial.print(F(":sensor="));
    Serial.print(digitalRead(sensorPin));
    Serial.print(F(", relay="));
    Serial.println(digitalRead(relayPin));
  }

  if (digitalRead(sensorPin) == LOW) {
    digitalWrite(relayPin, HIGH);
    turnOff.start();
  }
  else if (turnOff.expired()) {
    digitalWrite(relayPin, LOW);
  }
}

Expanding to three sensor-relays becomes: 
#include <Timemark.h>

const uint32_t TURN_OFF_TIMEOUT = 30000L;
const int CONTROL_MAX = 3;
Timemark turnOff[CONTROL_MAX];
const int sensorPin[CONTROL_MAX] = { 2, 4, 6 };
const int relayPin[CONTROL_MAX] = { 3, 5, 7 };

void setup()
{
  for (int i = 0; i < CONTROL_MAX; i++) {
    turnOff[i].limitMillis(TURN_OFF_TIMEOUT);
    pinMode(sensorPin[i], INPUT_PULLUP);
    pinMode(relayPin[i], OUTPUT);
    digitalWrite(relayPin[i], LOW);
  }
}

void loop()
{   
  for (int i = 0; i < CONTROL_MAX; i++) {
    if (digitalRead(sensorPin[i]) == LOW) {
      digitalWrite(relayPin[i], HIGH);
      turnOff[i].start();
    }
    else if (turnOff[i].expired()) {
      digitalWrite(relayPin[i], LOW);
    }
  }
}

An alternative solution is to use the Scheduler library. Below is a rewrite using template functions and a controller task per sensor-relay pair. 
#include <Scheduler.h>

const uint32_t TURN_OFF_TIMEOUT = 30000L;
const uint32_t DEBOUNCE_TIMEOUT = 40L;

template<const int sensorPin, const int relayPin> void setupController()
{
  pinMode(sensorPin, INPUT_PULLUP);
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);
}

template<const int sensorPin, const int relayPin> void loopController()
{
  if (digitalRead(sensorPin) == LOW) {
    digitalWrite(relayPin, HIGH);
    while (digitalRead(sensorPin) == LOW) delay(DEBOUNCE_TIMEOUT);
    delay(TURN_OFF_TIMEOUT);
    digitalWrite(relayPin, LOW);
  }
  yield();
}

void setup()
{
  Scheduler.start(setupController<2,3>, loopController<2,3>);
  Scheduler.start(setupController<4,5>, loopController<4,5>);
  Scheduler.start(setupController<6,7>, loopController<6,7>);
}

void loop()
{
  yield();
}

Please note that the Scheduler requires yield() or delay() to switch between the tasks. 
Cheers!

18522: <p>I'm building a control system for three sump pumps using a single Arduino and a set of relays.  I have float sensors for each one, and need the Arduino to trigger the relays when the float sensors are triggered.  Sensor 1 triggers pump 1, sensor 2 triggers pump 2, etc.</p>

<p>Simple enough, but I also need each pump to remain on for 30 seconds after its float sensor returns to normal.  This throws me for a loop since I need the other pumps and sensors to function during this delay time.  I quickly found that the blocking <code>delay()</code> function is no longer feasible.</p>

<p>How can I trigger the relays, keep them on for 30 seconds after the sensors return to normal, but all the while still monitor and control the other sensors/relays?  Would a non-blocking timer solution work?  Would I have to delve into interrupts?</p>
 14737: <blockquote>
  <p>Would a non-blocking timer solution work?</p>
</blockquote>

<p>Yes.  Your "timer" doesn't have to be anything more than a start time from <code>millis()</code> and a flag.  Then your loop just compares the start time to the current <code>millis()</code> value, and if it's more than the interval you wanted, do the "thing".  In code:</p>

<pre><code>uint32_t startTime;
bool waiting = false;
const uint32_t PUMP_DURATION = 30000UL;

void setup()
{
  // start the "timer"
  startTime = millis();
  waiting = true;
}

void loop()
{
  if (waiting &amp;&amp; (millis() - startTime &gt;= PUMP_DURATION)) {
    waiting = false;
    // do something now!
    foo();
  }
</code></pre>

<p>The flag keeps it from checking when the pump <em>isn't</em> running.</p>

<p>If you have multiple timed things, just make another set of variables (i.e., <code>startTime2</code> and <code>waiting2</code>).  Give 'em better names, eh?</p>

<p>There are also Arduino libraries that encapsulate this common design pattern.  <a href="https://github.com/mikaelpatel/Arduino-Timemark" rel="nofollow">TimeMark</a> is one.</p>

<p>Here's some <a href="http://forum.arduino.cc/index.php?topic=223286.0" rel="nofollow">sample code</a> on the Arduino forum.</p>
 13432: <p>The <a href="https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay" rel="nofollow noreferrer">"blink-without-delay"-pattern</a> shows how this can be solved but with several time periods and logic this becomes complex. Some abstraction is needed. The <a href="https://github.com/mikaelpatel/Arduino-Timemark" rel="nofollow noreferrer">Timemark</a> library provides a solution. Belows is the logic for a single sensor-relay pair with a state trace to serial:</p>

<pre><code>#include &lt;Timemark.h&gt;

const uint32_t TURN_OFF_TIMEOUT = 30000L;
Timemark turnOff(TURN_OFF_TIMEOUT);
const int sensorPin = 2;
const int relayPin = 3;

void setup()
{
  Serial.begin(9600);
  while (!Serial);

  pinMode(sensorPin, INPUT_PULLUP);
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);

  trace.start();
}

void loop()
{
  if (trace.expired()) {
    Serial.print(millis());
    Serial.print(F(":sensor="));
    Serial.print(digitalRead(sensorPin));
    Serial.print(F(", relay="));
    Serial.println(digitalRead(relayPin));
  }

  if (digitalRead(sensorPin) == LOW) {
    digitalWrite(relayPin, HIGH);
    turnOff.start();
  }
  else if (turnOff.expired()) {
    digitalWrite(relayPin, LOW);
  }
}
</code></pre>

<p>Expanding to three sensor-relays becomes: </p>

<pre><code>#include &lt;Timemark.h&gt;

const uint32_t TURN_OFF_TIMEOUT = 30000L;
const int CONTROL_MAX = 3;
Timemark turnOff[CONTROL_MAX];
const int sensorPin[CONTROL_MAX] = { 2, 4, 6 };
const int relayPin[CONTROL_MAX] = { 3, 5, 7 };

void setup()
{
  for (int i = 0; i &lt; CONTROL_MAX; i++) {
    turnOff[i].limitMillis(TURN_OFF_TIMEOUT);
    pinMode(sensorPin[i], INPUT_PULLUP);
    pinMode(relayPin[i], OUTPUT);
    digitalWrite(relayPin[i], LOW);
  }
}

void loop()
{   
  for (int i = 0; i &lt; CONTROL_MAX; i++) {
    if (digitalRead(sensorPin[i]) == LOW) {
      digitalWrite(relayPin[i], HIGH);
      turnOff[i].start();
    }
    else if (turnOff[i].expired()) {
      digitalWrite(relayPin[i], LOW);
    }
  }
}
</code></pre>

<p>An alternative solution is to use the <a href="https://github.com/mikaelpatel/Arduino-Scheduler" rel="nofollow noreferrer">Scheduler</a> library. Below is a rewrite using template functions and a controller task per sensor-relay pair. </p>

<pre><code>#include &lt;Scheduler.h&gt;

const uint32_t TURN_OFF_TIMEOUT = 30000L;
const uint32_t DEBOUNCE_TIMEOUT = 40L;

template&lt;const int sensorPin, const int relayPin&gt; void setupController()
{
  pinMode(sensorPin, INPUT_PULLUP);
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);
}

template&lt;const int sensorPin, const int relayPin&gt; void loopController()
{
  if (digitalRead(sensorPin) == LOW) {
    digitalWrite(relayPin, HIGH);
    while (digitalRead(sensorPin) == LOW) delay(DEBOUNCE_TIMEOUT);
    delay(TURN_OFF_TIMEOUT);
    digitalWrite(relayPin, LOW);
  }
  yield();
}

void setup()
{
  Scheduler.start(setupController&lt;2,3&gt;, loopController&lt;2,3&gt;);
  Scheduler.start(setupController&lt;4,5&gt;, loopController&lt;4,5&gt;);
  Scheduler.start(setupController&lt;6,7&gt;, loopController&lt;6,7&gt;);
}

void loop()
{
  yield();
}
</code></pre>

<p>Please note that the Scheduler requires yield() or delay() to switch between the tasks. </p>

<p>Cheers!</p>
