@gcjr: very compact code that does what was asked for. But very hard to understand for beginners.
You packed in everything that makes the code more compact:
arrays, enumeration, automatically calculating of max-index-numbers, leaving out comments.
to explain how it is done for two or more relays:
There has to be running on big loop very fast (at least hundreds of times per second often 10.000 times per second) which is really no problem because your arduino is running at 16 Million Herz = one command executed rough estimation once every 4 microseconds. 250.000 commands per second.
This "loop" is the function loop.
The loop is watching out for a triggerpulse and if it has detected one several things must be done
- making a snapshot of time.
- switching on the relay (of course)
- set a flagvariable true
further down in the loop there is an if-condition checking how much time has passed by since taking the snapshot of time
if the result of calculating how much time has passed by since snapshot is greater than a timeout-limit
then
- switch off relay
- set flagvariable false
as an allday example with easy to follow numbers
delay() is blocking. As long as the delay is "delaying" nothing else of the code can be executed.
Now there is a technique of non-blocking timing.
The basic principle of non-blocking timing is fundamental different from using delay()
You have to understand the difference first and then look into the code.
otherwise you might try to "see" a "delay-analog-thing" in the millis()-code which it really isn't
Trying to see a "delay-analog-thing" in millis() makes it hard to understand millis()
Having understood the basic principle of non-blocking timing based on millis() makes it easy to understand.
imagine baking a frosted pizza
the cover says for preparation heat up oven to 200°C
then put pizza in.
Baking time 10 minutes
You are estimating heating up needs 3 minutes
You take a look onto your watch it is 13:02 (snapshot of time)
You start reading the newspaper and from time to time looking onto your watch
watch 13:02 not yet time
watch 13:03 not yet time
watch 13:04 not yet time 13:04 - 13:02 = 2 minutes is less than 3 minutes
watch 13:05 when did I start 13:02? OK 13:05 - 13:02 = 3 minutes time to put pizza into the oven
New basetime 13:05 (the snapshot of time)
watch 13:06 not yet time
watch 13:07 not yet time
watch 13:08 not yet time (13:08 - 13:05 = 3 minutes is less than 10 minutes
watch 13:09 not yet time
watch 13:10 not yet time
watch 13:11 not yet time
watch 13:12 not yet time
watch 13:13 not yet time
watch 13:14 not yet time (13:14 - 13:05 = 9 minutes is less than 10 minutes
watch 13:15 when did I start 13:05 OK 13:15 - 13:05 = 10 minutes time to eat pizza (yum yum)
You did a repeated comparing how much time has passed by
This is what non-blocking timing does
In the code looking at "How much time has passed by" is done
currentTime - startTime >= bakingTime
bakingTime is 10 minutes
13:06 - 13:05 = 1 minute >= bakingTime is false
13:07 - 13:05 = 2 minutes >= bakingTime is false
...
13:14 - 13:05 = 9 minutes >= bakingTime is false
13:15 - 13:05 = 10 minutes >= bakingTime is TRUE time for timed action!!
if (currentTime - previousTime >= period) {
it has to be coded exactly this way because in this way it manages the rollover from Max back to zero
of the function mills() automatically
best regards Stefan