Serial.println(x);
while it seems to work fine before I notice a small bug.
is some of the times I run the code
When the sequence of each solenoid is finished it stay at ON/HIGH position. how can I be sure
it will be LOW when finished?
edit: works fine. Not sure what was that bug
// Is this solenoid active
if (mySolenoids[x].startTime < currentMillis && mySolenoids[x].stopTime > currentMillis)
{
...
}
else
// Turn the solenoid off
I try to print when a solenoid is finished (become inactive)
if(mySolenoids[x].stopTime > currentMillis)
{
Serial.print("Solenoid: ");
Serial.print(x);
Serial.println("Is finished");
}
I expect it to print only once when stopTime is bigger then currentMillis but it is printing all the time
because it is in loop()
/ Is this solenoid active
if (mySolenoids[x].startTime < currentMillis && mySolenoids[x].stopTime > currentMillis)
{
mySolenoids[x].nextPulseStart = ?
}
else
// Turn the solenoid off
the much easier way to make sure that all solenoids are switched off is to do an extra
function that switches off all solenoids by direct executing
digitalWrite()
to execute this there must be found a condition that becomes only true if the whole sequence has finished.
Looks like you missed the actual requirements here @StefanL38. Anyway... good luck. I'm out.
struct:
you can imagine a struct similar to a box where you can put in smaller boxes

If you close the bigger box you can carry it around with you and all smaller boxes are included.
Each small yellow box of this pure physical analogon in a c++-struct is a "simple" variable-type as
int
byte
unsigned long
float
etc.
in a struct a lot of different variable-types can be combined
an array is another kind of "bigger" box that has a more special "structure"
accessing one of the "smaller"-boxes inside a struct is done by
structname.variable-name
In an array all "smaller boxes are from the same type

byte myByteArray[9] would be an array of 9 elements
array-element 1 is accessed by myByteArray[0]
array-element 2 is accessed by myByteArray[1]
array-element 3 is accessed by myByteArray[2]
...
array-element 9 is accessed by myByteArray[8]
the index inside the edgy brackets starts at zero
In an array all elements are f the same variabletype
This is correct there are different conditions for switching off always a part of the solenoids
@hk_jh
please post your actual code-version
There is an automatic function for doing this in the Arduino-IDE
just three steps
- press Ctrl-T for autoformatting your code
- do a rightclick with the mouse and choose "copy for forum"
- paste clipboard into write-window of a posting
best regards Stefan
I don't know what should be in this if statement:
// Is this solenoid active
if (mySolenoids[x].startTime < currentMillis && mySolenoids[x].stopTime > currentMillis)
{
// Do we need to calculate a next pulse time?
Here is the full code:
#define NUM_SOLENOIDS 4
#define BTN_PIN 10
uint8_t const PULSE_LENGTH = 50;
struct Solenoid
{
unsigned long startTime; // The time this solenoid is first active
unsigned long stopTime; // The time this solenoid becomes inactive
unsigned long minPulse; // The minimum time the next pulse is generated
unsigned long maxPulse; // The maximum time the next pulse is generated
unsigned long nextPulseStart; // The time of the next pulse starts
unsigned long nextPulseStop; // The time of the next pulse stops
uint8_t pin;
};
Solenoid mySolenoids[NUM_SOLENOIDS] = { 0, 10000, 100, 1000, 0, 0, 2,
5000, 20000, 1000, 1500, 0, 0, 3,
6000, 20000, 1500, 3500, 0, 0, 4,
7000, 20000, 1000, 2500, 0, 0, 5
};
void setup()
{
Serial.begin(115200);
pinMode( BTN_PIN, INPUT_PULLUP );
for (uint8_t i = 0; i < NUM_SOLENOIDS; i++)
{
pinMode(mySolenoids[i].pin, OUTPUT);
}
}
void loop()
{
startSequence();
}
void startSequence()
{
unsigned long currentMillis = millis();
for (uint8_t x = 0; x < NUM_SOLENOIDS; x++)
{
// Is this solenoid active
if (mySolenoids[x].startTime < currentMillis && mySolenoids[x].stopTime > currentMillis)
{
// Do we need to calculate a next pulse time?
// mySolenoids[x].nextPulseStart = currentMillis;
if (mySolenoids[x].nextPulseStop < currentMillis)
{
// Turn off solenoid
digitalWrite(mySolenoids[x].pin, LOW);
// Calculate nextPulseStart & nextPulseStop
long startOffset = random(mySolenoids[x].minPulse, mySolenoids[x].maxPulse);
Serial.print("solenoid: ");
Serial.println(x);
Serial.println(startOffset);
mySolenoids[x].nextPulseStart = startOffset + currentMillis;
mySolenoids[x].nextPulseStop = mySolenoids[x].nextPulseStart + PULSE_LENGTH;
}
// Are we currently in the middle of a pulse?
if (mySolenoids[x].nextPulseStart < currentMillis &&
mySolenoids[x].nextPulseStop > currentMillis)
{
//turn on solenoid
digitalWrite(mySolenoids[x].pin, HIGH);
}
}
}
}
not yet presenting a solution.
I started to analyse the actual code and post my thoughts about it
non-blockingtiming as far as I know it and as far as I use it does always caluclate timestamp-differencies
This if-condition does not calculate a difference
if (mySolenoids[x].startTime < currentMillis && mySolenoids[x].stopTime > currentMillis) {
This means it can't work in a repeated manner of multiple cycles where cylce means repeating 20 minutes cycles
This is a point that must be changed.
I want to ask a question about the basic concept you want to follow.
Me personal I prefere using step-chains which basically work this way:
execute code for "operation-mode 1"
if some condition becomes true
execute code for "operation-mode 2"
if some condition becomes true
execute code for "operation-mode 3"
where each operation-mode is one of these
operation-mode 1
![]()
operation-mode 2
![]()
operation-mode 3

operation-mode 4

etc. etc.
changing to this kind of coding means to re-write 90% of the code
So @hk_jh what do you think
would you like to keep the existing code modifying
or re-write the code with my support?
best regards Stefan
So I would like to change this.
I though about it, what if I want to add a pause option for each solenoid in addition for the stop ?
so the struct might be like this?
struct Solenoid
{
unsigned long startTime; // The time this solenoid is first active
unsigned long pauseTime; //The time this solenoid will pause
unsigned long resumeTime; //The time this solenoid will resume
unsigned long stopTime; // The time this solenoid becomes inactive
unsigned long minPulse; // The minimum time the next pulse is generated
unsigned long maxPulse; // The maximum time the next pulse is generated
unsigned long nextPulseStart; // The time of the next pulse starts
unsigned long nextPulseStop; // The time of the next pulse stops
uint8_t pin;
};
then this function:
mySolenoids[NUM_SOLENOIDS] = { 0, 10000, 100, 1000, 0, 0, 2}
might look like this?
mySolenoids[NUM_SOLENOIDS] = { 0, 24000, 48000, 600000, 100, 1000, 0, 0, 2}
//start at time 0, pause after 4 minutes, resume after another 4 minute, stop solenoid after 10 minutes
I do understand I would like the code to be more flexible as the previous answer I write just above.
do you think we-written the code will make the idea above optional? if so and If you don't mind of course (putting your time into it) I would like to try your idea as well
pausing and resuming adds some more complexity to the code but is doable.
example
solenoid 2
start at pulsing at 1:30
start pausing at 1:45
resume pulsing at 2:00
stop pulsing at 20:00
yes, this what I mean. to have the option to pause, but not necessarily to use it
all in all you are asking for a pretty complex functionality.
The code to do all this requires:
-
a lot of programming-knowledge and a lot of programming-experience
if you want to have the code in the most compact and most elegant way -
a lot of lines of code
if you want to have the code written in a way that is easy to understand
I have thrown out the struct because I'm using long but self-explaining names
With the long variable-names adding
mySolenoids.
in front of each variable makes things much less readable = much less understandable
It would only become "readable" again with shorter names.
But as long as you only have a little programming-experience the sjort names make it hard to understand the code
conclusion: => not using a struct
For easier understanding I still added a lot of comments
So here is a first rough sketch of how it can be coded with this approach
I have to do other work now
If you want to learn more about non-blocking timing, whic is used here a lot
read this tutorial
and to learn about step-chains ( state-machines) you can read here
#define NUM_SOLENOIDS 10 // using one more array-element to start indexes with 1 instead of zero
const byte BTN_PIN = 10;
const unsigned long PULSE_LENGTH = 50;
// declaring the timing-variables
// assigning values is MISSING and must be added
unsigned long PulsingStarts[NUM_SOLENOIDS]; // The time this solenoid is first active
unsigned long PulsingStops[NUM_SOLENOIDS]; // The time this solenoid becomes inactive
unsigned long PulsingStartsPausing[NUM_SOLENOIDS]; // The time this solenoid shall START the pausing
unsigned long PulsingResumes[NUM_SOLENOIDS]; // The time this solenoid shall resume pulsing again
unsigned long minWaitTimeBeforeNextPulse[NUM_SOLENOIDS]; // The minimum time the next pulse is generated
unsigned long maxWaitTimeBeforeNextPulse[NUM_SOLENOIDS]; // The maximum time the next pulse is generated
unsigned long WaitingIntervalUntilNextPulse[NUM_SOLENOIDS]; // The time that has to pass by until next pulse is created
unsigned long ShortPulseTimer[NUM_SOLENOIDS]; // variable used for creating the short pulses with non-blocking timing
unsigned long NextPulseTimer[NUM_SOLENOIDS]; // variable used for non-blocking timing of when the next short-pulse shall be created
uint8_t pin[NUM_SOLENOIDS];
const byte sc_idling = 0;
const byte sc_Sol1_pulsing = 1;
const byte sc_Sol12_pulsing = 2;
const byte sc_Sol1To3_pulsing = 3;
const byte sc_Sol1To4_pulsing = 4;
const byte sc_Sol1To5_pulsing = 5;
const byte sc_Sol1To6_pulsing = 6;
const byte sc_Sol1To7_pulsing = 7;
const byte sc_Sol1To8_pulsing = 8;
const byte sc_Sol1To9_pulsing = 9;
byte stepNo = sc_idling;
unsigned long SequenceStarted;
unsigned long PulsingStarted[NUM_SOLENOIDS];
unsigned long PausingStarted[NUM_SOLENOIDS];
unsigned long PulsingResumes[NUM_SOLENOIDS];
unsigned long PulsingStops[NUM_SOLENOIDS];
unsigned long TimeToStartOperationMode[NUM_SOLENOIDS];
// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if ( currentMillis - startOfPeriod >= TimePeriod ) {
// more time than TimePeriod has elapsed since last time if-condition was true
startOfPeriod = currentMillis; // a new period starts right here so set new starttime
return true;
}
else return false; // actual TimePeriod is NOT yet over
}
void mySequenceStepChain() {
static unsigned long CurrentMillis = millis();
switch (StepNo) {
case sc_idling:
if (digitalRead(BTN_PIN) == pressed) {
SequenceStarted = CurrentMillis; // store snapshot of time when sequence BEGINS
PulsingStarts[1] = CurrentMillis; // store snapshot of time when pulsing of solenoid 1 starts
NextPulseTimer[1] = CurrentMillis; // initialise Timer-variable that is used for non-blocking timing to create the short pulses with actual time
digitalWrite(pin[1], HIGH); //
ShortPulseTimer[1] = CurrentMillis; // store snapshot of time when IO-pin is switched to HIGH
// create randomised waiting time
WaitingIntervalUntilNextPulse[1] = random(minWaitTimeBeforeNextPulse[1], maxWaitTimeBeforeNextPulse[1]);
StepNo = sc_Sol1_pulsing;
}
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol1_pulsing:
if ( TimePeriodIsOver(NextPulseTimer[1], WaitingIntervalUntilNextPulse[1]) ) {
// if that amount of milliseconds HAVE passed by that shall be waited until
// the next short-pulse shall be created
digitalWrite(pin[1], HIGH); // create next short-pulse
ShortPulseTimer[1] = CurrentMillis; // store snapshot of time when IO-pin is switched to HIGH
WaitingIntervalUntilNextPulse[1] = random(minWaitTimeBeforeNextPulse[1], maxWaitTimeBeforeNextPulse[1]);
}
if ( TimePeriodIsOver(ShortPulseTimer[1], PULSE_LENGTH) ) { // check if it is time to finish the short pulse
// if it is time to finish short-pulse
digitalWrite(mySolenoids[1].pin, LOW); // switch IO-pin to LOW
// create next WaitingInterval that shall be waited until the next short pulse is created
WaitingIntervalUntilNextPulse[1] = random(minWaitTimeBeforeNextPulse[1], maxWaitTimeBeforeNextPulse[1]);
}
// check if it is time to switch to the next mode of operation
if ( CurrentMillis - SequenceStarted >= TimeToStartOperationMode[2] ) {
StepNo = sc_Sol12_pulsing;
}
break; // immidiately jump down to END-OF-SWITCH
case :
break; // immidiately jump down to END-OF-SWITCH
case :
break; // immidiately jump down to END-OF-SWITCH
case :
break; // immidiately jump down to END-OF-SWITCH
case :
break; // immidiately jump down to END-OF-SWITCH
case :
break; // immidiately jump down to END-OF-SWITCH
case :
break; // immidiately jump down to END-OF-SWITCH
case :
break; // immidiately jump down to END-OF-SWITCH
case :
break; // immidiately jump down to END-OF-SWITCH
}
}
void setup() {
Serial.begin(115200);
pinMode( BTN_PIN, INPUT_PULLUP );
for (uint8_t i = 0; i < NUM_SOLENOIDS; i++) {
pinMode(mySolenoids[i].pin, OUTPUT);
}
}
void loop() {
mySequenceStepChain();
}
best regards Stefan
This version has more code and gives you a sight to the end of the tunnel how it can be coded
Your code is a very good example to show how dividing a complex functionality into smaller parts
makes it easier to code and easier to understand
I have written some more functions with self-explaining names.
#define NUM_SOLENOIDS 10 // using one more array-element to start indexes with 1 instead of zero
const byte BTN_PIN = 10;
const unsigned long PULSE_LENGTH = 50;
// assigning values is MISSING and must be added
// this is just declaring the timing-variables
unsigned long PulsingStarts[NUM_SOLENOIDS]; // The time this solenoid is first active
unsigned long PulsingStartsOffset[NUM_SOLENOIDS];
unsigned long PulsingStops[NUM_SOLENOIDS]; // The time this solenoid becomes inactive
unsigned long PulsingStopsOffset[NUM_SOLENOIDS];
unsigned long TimeOffsetToStartOperationMode[16] = { // from 0 to 15 these are 16 elements
/* 0 */ 0, // dummy-element
/* 1 */ 0,
/* 2 */ ( 1 * 60 + 30) * 1000,
/* 3 */ 3 * 60 * 1000,
/* 4 */ 6 * 60 * 1000,
/* 5 */ ( 7 * 60 + 30) * 1000,
/* 6 */ 9 * 60 * 1000,
/* 7 */ 11 * 60 * 1000,
/* 8 */ 11 * 60 * 1000,
/* 9 */ 11 * 60 * 1000,
/* 10 */ (12 * 60 + 30) * 1000,
/* 11 */ 14 * 60 * 1000,
/* 12 */ (15 * 60 + 30) * 1000,
/* 13 */ 17 * 60 * 1000,
/* 14 */ (18 * 60 + 30) * 1000,
/* 15 */ 20 * 60 * 1000
};
unsigned long PausingStarts[NUM_SOLENOIDS]; // The time this solenoid shall START the pausing
unsigned long PausingStartsOffset[NUM_SOLENOIDS]; // The time this solenoid shall START the pausing
unsigned long PulsingResumes[NUM_SOLENOIDS]; // The time this solenoid shall resume pulsing again
unsigned long PulsingResumeOffset[NUM_SOLENOIDS]; // The time this solenoid shall resume pulsing again
// The minimum time the next pulse is generated
unsigned long minWaitTimeBeforeNextPulse[NUM_SOLENOIDS] = {
/* 0 */ 0, // dummy-element
/* 1 */ 7,
/* 2 */ 9,
/* 3 */ 9,
/* 4 */ 7,
/* 5 */ 7,
/* 6 */ 7,
/* 7 */ 5,
/* 8 */ 5,
/* 9 */ 5
};
// The maximum time the next pulse is generated
unsigned long maxWaitTimeBeforeNextPulse[NUM_SOLENOIDS] = {
/* 0 */ 0, // dummy-element
/* 1 */ 9,
/* 2 */ 25,
/* 3 */ 25,
/* 4 */ 19,
/* 5 */ 19,
/* 6 */ 19,
/* 7 */ 12,
/* 8 */ 12,
/* 9 */ 12
};
unsigned long WaitingIntervalUntilNextPulse[NUM_SOLENOIDS]; // The time that has to pass by until next pulse is created
unsigned long ShortPulseTimer[NUM_SOLENOIDS]; // variable used for creating the short pulses with non-blocking timing
unsigned long NextPulseTimer[NUM_SOLENOIDS];
unsigned long CurrentMillis;
boolean PulsingIsActivated[NUM_SOLENOIDS];
uint8_t pin[NUM_SOLENOIDS];
const byte sc_idling = 0;
const byte sc_Sol1_pulsing = 1;
const byte sc_Sol12_pulsing = 2;
const byte sc_Sol1To3_pulsing = 3;
const byte sc_Sol1To4_pulsing = 4;
const byte sc_Sol1To5_pulsing = 5;
const byte sc_Sol1To6_pulsing = 6;
const byte sc_Sol1To7_pulsing = 7;
const byte sc_Sol1To8_pulsing = 8;
const byte sc_Sol1To9_pulsing = 9;
const byte sc_Sol789_off = 10;
const byte sc_Sol6_off = 11;
const byte sc_Sol5_off = 12;
const byte sc_Sol4_off = 13;
const byte sc_Sol3_off = 14;
const byte sc_Sol2_off = 15;
byte StepNo;
unsigned long SequenceStarted;
// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if ( currentMillis - startOfPeriod >= TimePeriod ) {
// more time than TimePeriod has elapsed since last time if-condition was true
startOfPeriod = currentMillis; // a new period starts right here so set new starttime
return true;
}
else return false; // actual TimePeriod is NOT yet over
}
void mySequenceStepChain() {
static unsigned long CurrentMillis = millis();
switch (StepNo) {
case sc_idling:
if (digitalRead(BTN_PIN) == HIGH) {
// initialise timer-variables with the correct value for the next 20-minute cycle
for (int i = 1; i < NUM_SOLENOIDS; i++) {
PausingStarts[i] = CurrentMillis + PausingStartsOffset[i];
PulsingResumes[i] = CurrentMillis + PulsingResumeOffset[i];
}
SequenceStarted = CurrentMillis; // store snapshot of time when sequence BEGINS
PulsingStarts[1] = SequenceStarted + PausingStartsOffset[1]; // store snapshot of time when pulsing of solenoid 1 starts
NextPulseTimer[1] = CurrentMillis; // initialise Timer-variable that is used for non-blocking timing to create the short pulses with actual time
digitalWrite(pin[1], HIGH); //
ShortPulseTimer[1] = CurrentMillis; // store snapshot of time when IO-pin is switched to HIGH
// create randomised waiting time
WaitingIntervalUntilNextPulse[1] = random(minWaitTimeBeforeNextPulse[1], maxWaitTimeBeforeNextPulse[1]);
StepNo = sc_Sol1_pulsing;
}
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol1_pulsing:
ManageShortPulse(1);
CheckPausingResuming(1);
// check if it is time to switch to the next mode of operation
if ( CurrentMillis - SequenceStarted >= TimeOffsetToStartOperationMode[2] ) {
StepNo = sc_Sol12_pulsing;
}
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol12_pulsing:
for (int i = 1; i <= 2;) {
ManageShortPulse(i);
CheckPausingResuming(i);
}
// check if it is time to switch to the next mode of operation
if ( CurrentMillis - SequenceStarted >= TimeOffsetToStartOperationMode[3] ) {
StepNo = sc_Sol1To3_pulsing;
}
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol1To3_pulsing:
for (int i = 1; i <= 3;) {
ManageShortPulse(i);
CheckPausingResuming(i);
}
// check if it is time to switch to the next mode of operation
if ( CurrentMillis - SequenceStarted >= TimeOffsetToStartOperationMode[4] ) {
StepNo = sc_Sol1To4_pulsing;
}
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol1To5_pulsing:
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol1To6_pulsing:
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol1To7_pulsing:
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol1To8_pulsing:
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol1To9_pulsing:
break; // immidiately jump down to END-OF-SWITCH
case sc_Sol1To4_pulsing:
break; // immidiately jump down to END-OF-SWITCH
} // END-OF-SWITCH
}
void ManageShortPulse(byte SolNr) {
if ( TimePeriodIsOver(NextPulseTimer[SolNr], WaitingIntervalUntilNextPulse[SolNr]) ) {
// if that amount of milliseconds HAVE passed by that shall be waited until
// the next short-pulse shall be created
if (PulsingIsActivated[SolNr]) { // only in case pulsing is activated
digitalWrite(pin[SolNr], HIGH); // switch IO-pin HIGH
}
ShortPulseTimer[SolNr] = CurrentMillis; // store snapshot of time when IO-pin is switched to HIGH
}
if ( TimePeriodIsOver(ShortPulseTimer[SolNr], PULSE_LENGTH) ) { // check if it is time to finish the short pulse
// if it IS time to finish short-pulse
digitalWrite(pin[SolNr], LOW); // switch IO-pin to LOW
// create next WaitingInterval that shall be waited until the next short pulse is created
WaitingIntervalUntilNextPulse[SolNr] = random(minWaitTimeBeforeNextPulse[SolNr], maxWaitTimeBeforeNextPulse[SolNr]);
}
}
void CheckPausingResuming(byte SolNr) {
if ( CurrentMillis > PausingStarts[SolNr] && CurrentMillis < PulsingResumes[SolNr]) {
// if time is in the pausing-interval
PulsingIsActivated[SolNr] = false;
}
else {
PulsingIsActivated[SolNr] = true;
}
}
void setup() {
Serial.begin(115200);
pinMode( BTN_PIN, INPUT_PULLUP );
for (uint8_t i = 0; i < NUM_SOLENOIDS; i++) {
pinMode(pin[i], OUTPUT);
}
StepNo = sc_idling;
}
void loop() {
mySequenceStepChain();
}
best regards Stefan
There's a lot of good help going on in this topic but some of the posts showed some degree frustration between helpers and helped, and between each other; I've deleted those posts. I know helping can get frustrating sometimes and I know different helpers have different approaches that sometime clash, but please remember why we are here. My approach is that if I cannot reply in a polite way I don't reply, I ask you all please to consider doing the same.
I thank you.
