The first step is to move the current content of your loop to a function like below. The main reason is to keep your loop() clean so the program is easier to follow.
void runShow()
{
// loop through the patterns
for (uint16_t patternIndex = 0; patternIndex < NUMELEMENTS(sprinklerPattern); patternIndex++)
{
// if the pattern entry was not started
if (sprinklerPattern[patternIndex].started == false)
{
// if it is time
if (millis() >= sprinklerPattern[patternIndex].absStartTime)
{
// set the output
digitalWrite(sprinklerPins[sprinklerPattern[patternIndex].pinIndex], sprinklerPattern[patternIndex].pinStatus);
// update 'started' status
sprinklerPattern[patternIndex].started = true;
// debugging
Serial.print(F("Sprinkler pin "));
Serial.print(sprinklerPins[sprinklerPattern[patternIndex].pinIndex]);
Serial.print(" set to ");
Serial.print(sprinklerPattern[patternIndex].pinStatus == SPRINKLER_ON ? "ON" : "OFF");
Serial.print(F(" at "));
Serial.println(millis());
}
}
}
}
And call that function from loop().
void loop()
{
runShow();
}
This function needs a modification so it can tell loop() when the pattern is finished. If you don't do that, the show will go on forever.
/*
run the show
Returns:
true if the show is completed, else false
*/
bool runShow()
{
// loop through the patterns
for (uint16_t patternIndex = 0; patternIndex < NUMELEMENTS(sprinklerPattern); patternIndex++)
{
// if the pattern entry was not started
if (sprinklerPattern[patternIndex].started == false)
{
// if it is time
if (millis() >= sprinklerPattern[patternIndex].absStartTime)
{
// set the output
digitalWrite(sprinklerPins[sprinklerPattern[patternIndex].pinIndex], sprinklerPattern[patternIndex].pinStatus);
// update 'started' status
sprinklerPattern[patternIndex].started = true;
// debugging
Serial.print(F("[show]Sprinkler pin "));
Serial.print(sprinklerPins[sprinklerPattern[patternIndex].pinIndex]);
Serial.print(" set to ");
Serial.print(sprinklerPattern[patternIndex].pinStatus == SPRINKLER_ON ? "ON" : "OFF");
Serial.print(F(" at "));
Serial.println(millis());
}
}
}
// the show is completed if each step (element) in sprinklerPattern is 'started
// assume it's completed
bool isCompleted = true;
for (uint16_t patternIndex = 0; patternIndex < NUMELEMENTS(sprinklerPattern); patternIndex++)
{
// if step was not done yet
if (sprinklerPattern[patternIndex].started == false)
{
// clear the flag
isCompleted = false;
}
}
return isCompleted;
}
The function now returns a boolean variable which will be false if the show is not completed yet and true if the show is completed. Compared to the previous function, there is an additional for-loop at the end to check if all elements of the sprinklerPattern are processed (started).
For demonstration (it's not what you want but to demonstrate)
void loop()
{
static bool showCompleted = false;
if (showCompleted == false)
{
showCompleted = runShow();
}
}
The above will run the show only once.
So it's time to implement the switch. You will need to wire the switch between a pin and GND; the below code uses pin 2 and you need to change it to a pin that suites you and in setup() use INPUT_PULLUP. Note that I had to remove pin 2 from the sprinklerPins (and pin 1 was already removed). For that reason I also had to shorten the sprinklerPattern as well.
Below the updated version of the first part of the code (up to loop()).
// macro to calculate the number of elements in any array
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))
// sprinkler will be on if pin is set LOW
#define SPRINKLER_ON LOW
// button wired between pin and GND; use INPUT_PULLUP
#define SWITCH_ON LOW
// the sprinkler pins
//const uint8_t sprinklerPins[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
const uint8_t sprinklerPins[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
// a struct holding relevant information
struct SPRINKLER
{
const uint32_t relStartTime; // relative start time
const uint8_t pinIndex; // index in sprinklerPins array
uint8_t pinStatus; // on or off
bool started; // flag indicating that pattern entry is started
uint32_t absStartTime; // absolute start time
};
SPRINKLER sprinklerPattern[] = {
{ 5000UL, 0, SPRINKLER_ON, false, 0 },
{ 7000UL, 1, SPRINKLER_ON, false, 0 },
{ 10000UL, 0, !SPRINKLER_ON, false, 0 },
{ 15000UL, 1, !SPRINKLER_ON, false, 0 },
{ 15000UL, 2, SPRINKLER_ON, false, 0 },
{ 20000UL, 2, !SPRINKLER_ON, false, 0 },
{ 20000UL, 3, SPRINKLER_ON, false, 0 },
{ 25000UL, 3, !SPRINKLER_ON, false, 0 },
{ 25000UL, 4, SPRINKLER_ON, false, 0 },
{ 30000UL, 4, !SPRINKLER_ON, false, 0 },
{ 30000UL, 5, SPRINKLER_ON, false, 0 },
{ 35000UL, 5, !SPRINKLER_ON, false, 0 },
{ 35000UL, 6, SPRINKLER_ON, false, 0 },
{ 40000UL, 6, !SPRINKLER_ON, false, 0 },
{ 40000UL, 7, SPRINKLER_ON, false, 0 },
{ 45000UL, 7, !SPRINKLER_ON, false, 0 },
{ 45000UL, 8, SPRINKLER_ON, false, 0 },
{ 50000UL, 8, !SPRINKLER_ON, false, 0 },
{ 50000UL, 9, SPRINKLER_ON, false, 0 },
{ 55000UL, 9, !SPRINKLER_ON, false, 0 },
//{ 55000UL, 10, SPRINKLER_ON, false, 0 },
//{ 60000UL, 10, !SPRINKLER_ON, false, 0 },
//{ 60000UL, 11, SPRINKLER_ON, false, 0 },
//{ 65000UL, 11, !SPRINKLER_ON, false, 0 },
};
/*
for random sprinkler
*/
uint8_t pinSwitch = 2; // switch pin to switch between normal pattern and random pattern
const uint32_t interval = 180000UL; // every 3 minutes
const uint32_t duration = 3000; // on for 3 seconds
void setup()
{
Serial.begin(115200);
Serial.println(F("Sprinkler Demo"));
pinMode(pinSwitch, INPUT_PULLUP);
// validate configuration
if (validateSprinklerPattern() == false)
{
// hang forever
for (;;) {}
}
// for debugging
//printSprinklerPins();
//printSprinklerPattern();
// set sprinkler pins to output
for (uint8_t cnt = 0; cnt < NUMELEMENTS(sprinklerPins); cnt++)
{
//Serial.print(F("Setting sprinkler pin "));
//Serial.print(sprinklerPins[cnt]);
//Serial.println(F(" to output"));
digitalWrite(sprinklerPins[cnt], !SPRINKLER_ON);
pinMode(sprinklerPins[cnt], OUTPUT);
}
// reset the pattern
resetSprinklerPattern();
}
In loop() we first declare two static variables. Static variables are only known inside the function but will be remembered when the function (loop() in this case) finishes.
void loop()
{
// keep track of the last position of the switch
static uint8_t lastSwitchState = !SWITCH_ON;
// keep track if the show is completed
static bool showCompleted = false;
...
...
Next we read the switch
uint8_t switchState = digitalRead(pinSwitch);
Because switches and buttons might bounce a short delay is implemented when if the switch goes from on to off or vice versa
// if the switch state changed
if (lastSwitchState != switchState)
{
// a crude debounce timing
Serial.println(F("Debounce"));
delay(50);
}
If the switch is off, we run the random pattern. We also set showCompleted to false so if you flip the switch runShow() will be started (again).
// if the switch is off
if (switchState == !SWITCH_ON)
{
// indicate that a show is no longer completed
showCompleted = false;
// run a random pattern
runRandom();
}
If the switch is on, we check if the switch changed and reset the sprinkler pattern variables to initial states.
else
{
// if the switch changed (from off to on)
if (lastSwitchState != switchState)
{
// reset timing and output state of sprinkler pins
resetSprinklerPattern();
}
...
...
Next we check if the show is completed; if not, we run the show.
// if the show is not completed
if (showCompleted == false)
{
// run the show
showCompleted = runShow();
if (showCompleted == true)
{
Serial.println(F("Show completed"));
// reset the output state of the sprinkler pins
resetSprinklerPattern();
}
}
The last part here is the call to resetSprinklerPatten() to make sure that when you throw the switch to off all solenoids are deactivated for starters.
And lastly we remember the state of the switch
// remember the last state of the switch
lastSwitchState = switchState;
}
Full code below
// macro to calculate the number of elements in any array
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))
// sprinkler will be on if pin is set LOW
#define SPRINKLER_ON LOW
// button wired between pin and GND; use INPUT_PULLUP
#define SWITCH_ON LOW
// the sprinkler pins
//const uint8_t sprinklerPins[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
const uint8_t sprinklerPins[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
// a struct holding relevant information
struct SPRINKLER
{
const uint32_t relStartTime; // relative start time
const uint8_t pinIndex; // index in sprinklerPins array
uint8_t pinStatus; // on or off
bool started; // flag indicating that pattern entry is started
uint32_t absStartTime; // absolute start time
};
SPRINKLER sprinklerPattern[] = {
{ 5000UL, 0, SPRINKLER_ON, false, 0 },
{ 7000UL, 1, SPRINKLER_ON, false, 0 },
{ 10000UL, 0, !SPRINKLER_ON, false, 0 },
{ 15000UL, 1, !SPRINKLER_ON, false, 0 },
{ 15000UL, 2, SPRINKLER_ON, false, 0 },
{ 20000UL, 2, !SPRINKLER_ON, false, 0 },
{ 20000UL, 3, SPRINKLER_ON, false, 0 },
{ 25000UL, 3, !SPRINKLER_ON, false, 0 },
{ 25000UL, 4, SPRINKLER_ON, false, 0 },
{ 30000UL, 4, !SPRINKLER_ON, false, 0 },
{ 30000UL, 5, SPRINKLER_ON, false, 0 },
{ 35000UL, 5, !SPRINKLER_ON, false, 0 },
{ 35000UL, 6, SPRINKLER_ON, false, 0 },
{ 40000UL, 6, !SPRINKLER_ON, false, 0 },
{ 40000UL, 7, SPRINKLER_ON, false, 0 },
{ 45000UL, 7, !SPRINKLER_ON, false, 0 },
{ 45000UL, 8, SPRINKLER_ON, false, 0 },
{ 50000UL, 8, !SPRINKLER_ON, false, 0 },
{ 50000UL, 9, SPRINKLER_ON, false, 0 },
{ 55000UL, 9, !SPRINKLER_ON, false, 0 },
//{ 55000UL, 10, SPRINKLER_ON, false, 0 },
//{ 60000UL, 10, !SPRINKLER_ON, false, 0 },
//{ 60000UL, 11, SPRINKLER_ON, false, 0 },
//{ 65000UL, 11, !SPRINKLER_ON, false, 0 },
};
/*
for random sprinkler
*/
uint8_t pinSwitch = 2; // switch pin to switch between normal pattern and random pattern
const uint32_t interval = 3000UL; // every 3 minutes
const uint32_t duration = 3000; // on for 3 seconds
void setup()
{
Serial.begin(115200);
Serial.println(F("Sprinkler Demo"));
pinMode(pinSwitch, INPUT_PULLUP);
// validate configuration
if (validateSprinklerPattern() == false)
{
// hang forever
for (;;) {}
}
// for debugging
//printSprinklerPins();
//printSprinklerPattern();
// set sprinkler pins to output
for (uint8_t cnt = 0; cnt < NUMELEMENTS(sprinklerPins); cnt++)
{
//Serial.print(F("Setting sprinkler pin "));
//Serial.print(sprinklerPins[cnt]);
//Serial.println(F(" to output"));
digitalWrite(sprinklerPins[cnt], !SPRINKLER_ON);
pinMode(sprinklerPins[cnt], OUTPUT);
}
//delay(3000);
// reset the pattern
resetSprinklerPattern();
}
void loop()
{
// keep track of the last position of the switch
static uint8_t lastSwitchState = !SWITCH_ON;
// keep track if the show is completed
static bool showCompleted = false;
// check the switch
uint8_t switchState = digitalRead(pinSwitch);
// if the switch state changed
if (lastSwitchState != switchState)
{
// a crude debounce timing
Serial.println(F("Debounce"));
delay(50);
}
// if the switch is off
if (switchState == !SWITCH_ON)
{
// indicate that a show is no longer completed
showCompleted = false;
// run a random pattern
runRandom();
}
else
{
// if the switch changed (from off to on)
if (lastSwitchState != switchState)
{
// reset timing and output state of sprinkler pins
resetSprinklerPattern();
}
// if the show is not completed
if (showCompleted == false)
{
// run the show
showCompleted = runShow();
if (showCompleted == true)
{
Serial.println(F("Show completed"));
// reset the output state of the sprinkler pins
resetSprinklerPattern();
}
}
}
// remember the last state of the switch
lastSwitchState = switchState;
}
enum RANDOMSTATES
{
WAIT,
RUN,
};
void runRandom()
{
static uint32_t startTime;
static uint8_t pin = 255;
static RANDOMSTATES currentState = WAIT;
switch (currentState)
{
case WAIT:
if (millis() - startTime >= interval)
{
// pick a random sprinkler
uint8_t randomNumber = random(NUMELEMENTS(sprinklerPins));
pin = sprinklerPins[randomNumber];
// switch selected sprintkler on
digitalWrite(pin, SPRINKLER_ON);
// remember the start time
startTime = millis();
// go to next state
currentState = RUN;
// some debug
Serial.print(F("[random]Pin "));
Serial.print(pin);
Serial.println(F(" switched on"));
}
break;
case RUN:
if (millis() - startTime >= duration)
{
// switch selected sprintkler off
digitalWrite(pin, !SPRINKLER_ON);
// remember the start time
startTime = millis();
// go to next state
currentState = WAIT;
// some debug
Serial.print(F("Pin "));
Serial.print(pin);
Serial.println(F(" switched off"));
}
break;
}
}
/*
run the show
Returns:
true if the show is completed, else false
*/
bool runShow()
{
// loop through the patterns
for (uint16_t patternIndex = 0; patternIndex < NUMELEMENTS(sprinklerPattern); patternIndex++)
{
// if the pattern entry was not started
if (sprinklerPattern[patternIndex].started == false)
{
// if it is time
if (millis() >= sprinklerPattern[patternIndex].absStartTime)
{
// set the output
digitalWrite(sprinklerPins[sprinklerPattern[patternIndex].pinIndex], sprinklerPattern[patternIndex].pinStatus);
// update 'started' status
sprinklerPattern[patternIndex].started = true;
// debugging
Serial.print(F("[show]Sprinkler pin "));
Serial.print(sprinklerPins[sprinklerPattern[patternIndex].pinIndex]);
Serial.print(" set to ");
Serial.print(sprinklerPattern[patternIndex].pinStatus == SPRINKLER_ON ? "ON" : "OFF");
Serial.print(F(" at "));
Serial.println(millis());
}
}
}
// the show is completed if each step (element) in sprinklerPattern is 'started
// assume it's completed
for (uint16_t patternIndex = 0; patternIndex < NUMELEMENTS(sprinklerPattern); patternIndex++)
{
// if step was not started
if (sprinklerPattern[patternIndex].started == false)
{
isCompleted = false;
}
}
return isCompleted;
}
/*
display information about the sprinkler pins
*/
void printSprinklerPins()
{
Serial.print(F("There are "));
Serial.print(NUMELEMENTS(sprinklerPins));
Serial.println(F(" sprinklers"));
for (uint8_t cnt = 0; cnt < NUMELEMENTS(sprinklerPins); cnt++)
{
Serial.print(F("Sprinkler "));
Serial.print(cnt);
Serial.print(F(" on pin "));
Serial.println(sprinklerPins[cnt]);
}
}
/*
display information about the pattern entries
*/
void printSprinklerPattern()
{
Serial.print(F("The pattern contains "));
Serial.print(NUMELEMENTS(sprinklerPattern));
Serial.println(F(" entries"));
for (uint16_t cnt = 0; cnt < NUMELEMENTS(sprinklerPattern); cnt++)
{
Serial.print(F("Entry "));
Serial.print(cnt);
Serial.print(F(", pin "));
Serial.print(sprinklerPins[sprinklerPattern[cnt].pinIndex]);
Serial.print(F(" will change at "));
Serial.print(sprinklerPattern[cnt].relStartTime);
Serial.print(F(" to "));
Serial.println(sprinklerPattern[cnt].pinStatus == SPRINKLER_ON ? "ON" : "OFF");
}
}
/*
validate sprinkler pattern pin indices
Returs:
false on error, else true
*/
bool validateSprinklerPattern()
{
bool isValid = true;
for (uint16_t cnt = 0; cnt < NUMELEMENTS(sprinklerPattern); cnt++)
{
if (sprinklerPattern[cnt].pinIndex >= NUMELEMENTS(sprinklerPins))
{
Serial.print(F("Invalid pinIndex for pattern["));
Serial.print(cnt);
Serial.println(F("]"));
isValid = false;
}
}
return isValid;
}
/*
reset sprinkler pattern variables and deactivate solenoids
*/
void resetSprinklerPattern()
{
// hold the current time
uint32_t startTime = millis();
Serial.print(F("resetSprinklerPattern; time = "));
Serial.println(startTime);
// loop through all entries in sprinklerPattern
for (uint16_t cnt = 0; cnt < NUMELEMENTS(sprinklerPattern); cnt++)
{
// clear the flag
sprinklerPattern[cnt].started = false;
// set a new absolute start time
sprinklerPattern[cnt].absStartTime = startTime + sprinklerPattern[cnt].relStartTime;
// make sure the outputs are off
digitalWrite(sprinklerPins[sprinklerPattern[cnt].pinIndex], !SPRINKLER_ON);
}
}
Notes:
- I've modiefied the sprinklerPattern slightly in the beginning to demonstrate that you can have two sprinklers on at the same time; in this case they partially overlap.
- Once the show is over, it will not be repeated and the code will not automatically fall back to a random pattern; you have to put the switch in the off position to run the random functionality.
Request:
Please do not write long pieces of text; seperate into paragraphs where needed as it makes it a lot easier to read and to find specifics back.