Timing dependent execution including a delay: how to approach?

A digital sensor (either high or low) is read at night time between 22pm and 7am, every 5 minutes. If the sensor reads "high" action is to be taken (closing a valve), but only with a 15 minute delay.
A DS3231 is used to keep track of actual time.

Suppose at 22.00 the sensor starts to be read every 5 minutes (reading low values), and at 22:10 the sensor value is read with a high value. Now a valve must receive a "close" command, but only with a 15 minute delay.

Can a timer be started within this if - function at this line comment: "state change detection to high level water" (not using delay, see https://www.arduino.cc/en/tutorial/BlinkWithoutDelay ), counting down these 15 minutes without being prevented when 5 minutes later the next check-up-time, of being executed?

Important: the state change detection prevents the sensor being read again the next time, and also prevents the routine where the close command is given from being executed again the next 5 minutes, the 5' after that etc.. until the sensor state has changed back to low again.

EDIT: should it be better to start a new function (including this delay and then the valve closure command) instead?

Here is the code to check for sensor state transition from low to high (and where I need to introduce the 15 minute delay before the "close" command is given to the valve, i.e. after the "state change detection to high level water):

  // B. valve is ACTIVE

  if (valveState == HIGH) {

    // wait between 30 and 60 minutes after valve is activated before starting level checks
    // use pool surface and fill flow rate to calculate minimum time-to-almost-full
    if ((currentTime - startTime) >= poolRefillTime) {
    // check sensor every 5 minutes
    if (minute == 00 || minute == 05 || minute == 10 || minute == 15 || minute == 20 || minute == 25 ||
          minute == 30 || minute == 35 || minute == 40 || minute == 45 || minute == 50 || minute == 55)
      {
        digitalWrite (powerPin, HIGH);  //activate sensor circuit
        // wait about 45 seconds for the sensing threshold level to pass
        // very important: ONLY READ SENSOR STATUS AFTER PULSE LENGTH DETECTION TIMER DELAY HAS PASSED (IC4 AND IC6)
        if (second > 45) {
          // check high level sensor
          hiLvlState = digitalRead(sensorPinH);
          if (hiLvlState != lastHiLvlState) {   // state change detection to high level water
            if (hiLvlState == HIGH) {
              digitalWrite(electroValve, LOW);
              digitalWrite(alarmPin, LOW);
            }
          }
          // check if filling takes too long, > 3h (10.000.000ms)
          valveState = digitalRead(electroValve);
          lastHiLvlState = hiLvlState;
          if ((currentTime - startTime) > 40000 && valveState == HIGH) {
            digitalWrite(alarmPin, HIGH);
          }
//          else {
//            digitalWrite(alarmPin, LOW); // alarm no longer valid
//          }
        }
      }
      // power down sensor
      else {
        digitalWrite(powerPin, LOW); // de-activate sensor
      }
    }
  }

Now a valve must receive a "close" command, but only with a 15 minute delay.

Can a timer be started within this if - function at this line comment: "state change detection to high level water" (not using delay, see https://www.arduino.cc/en/tutorial/BlinkWithoutDelay ), counting down these 15 minutes without being prevented when 5 minutes later the next check-up-time, of being executed?

Some of us are quite old and need things to be explicit.
Please elaborate.

BTW, this makes my eyes water :cry:

if (minute == 00 || minute == 05 || minute == 10 || minute == 15 || minute == 20 || minute == 25 ||
minute == 30 || minute == 35 || minute == 40 || minute == 45 || minute == 50 || minute == 55)

Try:

if(minute % 5 == 0)

Keep track of when your sensor changed state, and when 15 minutes passed activate the valve.

Do other actions (such as reading the sensor - or not) based on that same state.

“but only with a 15 minute delay.”

Not sure what the OP means by 'but only’.

Sleep time here.

wvmarle:
Keep track of when your sensor changed state, and when 15 minutes passed activate the valve.

Do other actions (such as reading the sensor - or not) based on that same state.

No other actions take place based on that state or state change.

This is the hardware setup:

  1. a pool is equiped with a low level sensor and a high level sensor.
  2. when the low level sensor sends a HIGH (the water level is below that sensor) then a valve is opened and filling starts (in the hardware there are lots of safeguards to detect waves etc)
  3. when the high level sensor detects water then a HIGH is issued by this sensor (again lots of hardware safeguards against waves etc)

So once a HIGH is sent by the high level sensor it does not go back to LOW level, and the valve must be closed.

But I want the closure to take place 15 minutes after the state change from LOW to HIGH for the high level sensor.

So would it be wise to use a function that executes both the timer and the (delayed) valve closure? Keeping track of the 15 minutes, can that be done within the if function that is allowed access only once, i.e. at state change?
I think not, therefor my proposition for a separate function?

Post-note: someone suggested I write a state diagram, which will be done.

larryd:
Some of us are quite old and need things to be explicit.
Please elaborate.

Hi larryd, I will try, thank you for your response.
In post #5 I described the hardware setup; in software I read the value of the high level sensor every 5 minutes as long as the valve is open (water being added to the pool).
As soon as the high level sensor issues a state change from LOW to HIGH (water level is detected at the high level sensor) then my current code immediately issues a command to the valve to close.

What I would like to introduce is a delay in this closure command.

But since the current command is nested in a if-function with a state change detection this command is currently issued only once.

My question: what would I best do to include this 15 minute delay?

Because of the state change detection I think that I cannot introduce this timing delay within this if-function with state change detection; but is that correct thinking?

larryd:
“but only with a 15 minute delay.”

Not sure what the OP means by 'but only’.

Sleep time here.

That is my bad english; it just means that as soon as a HIGH is detected then my code now commands a valve to close. However now a 15 minute delay before that valve closure needs to be introduced..

larryd:
BTW, this makes my eyes water :cry:

if (minute == 00 || minute == 05 || minute == 10 || minute == 15 || minute == 20 || minute == 25 ||
minute == 30 || minute == 35 || minute == 40 || minute == 45 || minute == 50 || minute == 55)

Try:

if(minute % 5 == 0)

Great, thank you!!

That's all easily done based on state changes. You loop() will look a bit like this:

void loop() {
highLevel = readHighLevelSensor();
lowLevel = readLowLevelSensor();
switch (state) {
  case FILLING:
    runPump();
    if (highLevel) {  // A high level detected, top up for another 15 minutes.
      startHighTime = millis();
      state = TOPPING_UP;
    }
    break;

  case TOPPING_UP:
    runPump();
    if (millis() - startHighTime > 15 * 60 * 1000) {  // 15 minutes passed: very full now.
      state = FULL;
    }

  case FULL:
    stopPump();
    if (lowLevel) {  // Low level detected: start filling the pool.
      state = FILLING;
    }
    break;
}
}

That seems to fulfil your requirements.

wvmarle:
That's all easily done based on state changes. You loop() will look a bit like this:

void loop() {

highLevel = readHighLevelSensor();
lowLevel = readLowLevelSensor();
switch (state) {
  case FILLING:
    runPump();
    if (highLevel) {  // A high level detected, top up for another 15 minutes.
      startHighTime = millis();
      state = TOPPING_UP;
    }
    break;

case TOPPING_UP:
    runPump();
    if (millis() - startHighTime > 15 * 60 * 1000) {  // 15 minutes passed: very full now.
      state = FULL;
    }

case FULL:
    stopPump();
    if (lowLevel) {  // Low level detected: start filling the pool.
      state = FILLING;
    }
    break;
}
}




That seems to fulfil your requirements.

Hi wvmarle, thanks!

Just to try and be more specific: when the pool is not filling (valve is closed) then only every half hour there is a check to see whether a low level is being detected.

When the low level sensor detects low level (status becomes HIGH) then the valve is opened BUT only several hours later (dependent on the time estimated for the water to reach the high level sensor) does the high level sensor start to be checked, and then every 5 minutes.

So my state diagram would look like this:

water level = high (high level sensor = HIGH):

  1. valve closed
  2. check low level sensor and high level sensor every half hour

water level is middle (high level sensor = LOW, low level sensor = HIGH):

  1. same as above

water level is low (low level sensor = LOW):

  1. open valve
  2. first x number of hours (estimate calculated based on pool size and fill flow): no action at all, no sensor checks
  3. after x hours: start to monitor high level sensor every 5 minutes for transition to HIGH
  4. when high level sensor = HIGH:
    4a. stop checking high level sensor
    4b. start 15 minute timer
    4c. after 15 minutes close valve
    4d. Back to "water level= high"

Time calculations are much easier if you treat all times as minutes (or seconds, if you need more precision) since some base time - for example since the midnight at the start of 1 Jan 2018.

If you do it that way you will have a number that can be used in the same way that millis() is used in the various demos such as Several Things at a Time and Using millis() for timing. A beginners guide

...R

brice3010:
water level = high (high level sensor = HIGH):

  1. valve closed
  2. check low level sensor and high level sensor every half hour

Why only every half hour? Any specific reason for that? I see no reason to stop reading the sensor (it makes your code more complex), the reading is simply not used. Level sensors are I assume simple switches so reading them doesn't cost you anything.

Otherwise, that's what the readLowLevelSensor() and readHighLevelSensor() functions can take care of. They just return the latest reading, and only now and then do the actual reading.

wvmarle:
Why only every half hour? Any specific reason for that? I see no reason to stop reading the sensor (it makes your code more complex), the reading is simply not used. Level sensors are I assume simple switches so reading them doesn't cost you anything.

Otherwise, that's what the readLowLevelSensor() and readHighLevelSensor() functions can take care of. They just return the latest reading, and only now and then do the actual reading.

This is to prevent oxidation. And a level read is needed at most every half hour, porbably even only every 2 hours.
But indeed, I can activate the actual sensor voltage supply only when needed, and use the latest reading from readLowLevelSensor and readHighLevelSensor.

So the code would have a part taking care of sensor activation and reading the actual value, but only every half hour when the valve is closed, and when the valve opens delay reading for x hours and then start reading every 5 minutes until high level is reached, and then wait another 15 minutes before closing the valve again.

And another part that uses the latest read values to determine the switch case setting.

Is this a good approach?

Or would it be better to include all these timed reads and valve actuations in one routine?

I think I get the idea that you want to "overfill" the pool by 15 minutes once the high sensor is triggered. Is this correct?

It would seem more logical to me to just move the high sensor up to the real level you want it to stop. If there are other uses for that sensor then add another at the right level.

MorganS:
I think I get the idea that you want to "overfill" the pool by 15 minutes once the high sensor is triggered. Is this correct?

Indeed

MorganS:
It would seem more logical to me to just move the high sensor up to the real level you want it to stop. If there are other uses for that sensor then add another at the right level.

The sensor I have consists of one module with three sensing probes: one high, one middle, one low.
A HIGH level means that the top and middle are submerged, a LOW level means that lower and middle probes are not submerged.
So, filling takes place until the top probe is submerged + additional time.
And filling only starts when the lower probe is no longer submerged.

brice3010:
This is to prevent oxidation.

I think you really should invest in a waterproof level switch. I know, they're kinda expensive, you have to shell out at least USD 1 a piece, but it'll make your project much more reliable. Get one with a magnet on the outside, reed switch on the inside. No oxidation. You just have to keep them clean.

Your sensor will get oxidation, current or no current, and then stop working. You can only hope it's the low level one that fails, not the high level one, or you have some serious mopping to do.

wvmarle:
I think you really should invest in a waterproof level switch. I know, they're kinda expensive, you have to shell out at least USD 1 a piece, but it'll make your project much more reliable. Get one with a magnet on the outside, reed switch on the inside. No oxidation. You just have to keep them clean.

Your sensor will get oxidation, current or no current, and then stop working. You can only hope it's the low level one that fails, not the high level one, or you have some serious mopping to do.

Hi wvmarle, thank you for your suggestion but this design stands after full testing and simulations.
I understand your suggestion, but basically my question remains:

How to include timing requirements as stated above, and include your suggested use of the switch case function?

Oh well, it's your pool. I really don't understand why you don't go for waterproof sensors to measure water levels, or why oxidising sensors would pass a test.

Just put that timing part in the functions where you read the sensor. Generally makes of the most maintainable code. My loop() functions are usually just a list of functions that are called, over and over again, with each function taking care of their own timing and so. It is of course important to make those functions return quickly, so no delay(), but then you can also count on the function being called frequently.

wvmarle:
(...)

Just put that timing part in the functions where you read the sensor. Generally makes of the most maintainable code. My loop() functions are usually just a list of functions that are called, over and over again, with each function taking care of their own timing and so. It is of course important to make those functions return quickly, so no delay(), but then you can also count on the function being called frequently.

If I understand correctly you make frequent use of functions; so that could mean that in my existing code I might introduce a function for valve closure after the previously stated 15 minute delay?