Water Show With Timing

Ok so in my attempts to search for what i need its not working out, or to complicated for something simple. I want to create a water show for my kids birthday in june using 30 sprinklers and behind those sprinklers are water solenoids that turn on and off which i want to control using the arduino r3. I have found basic on and off solenoids tutorials but not what im looking for. I was trying to then find a tutorial using a midi file that has mapped out points where i can turn on and off the solenoids like a light show would do but it also got to complicated and the programs are janky and would require me to do other stuff i do not need to do and they missed lots of steps on how it works. It would be nice if i can use 30 midi files that are mapped out which will be silent, uploaded to a usb and while they are playing it will activate the solenoids at those points, but i have no idea how to do that, it would be the simplest way. So now im to the point where i originally planned for simplicity. I just want to start a timer when i push a button that will start the show and my music in a usb and make the sprinklers go off at certain times and length in that timer, the whole show is about 5 minutes 30 seconds. Remember i will have about 30 sprinklers so i will like them to be called sprinkler 1, 2 and so on so i can understand the code. Ive done coding with actionscript 2 back in the day with adobe flash and seems similar but still never got around to coding in actionscript 3 which this mostly looks like. Thanks if you can help.

Is that an Uno? In which case controlling 30 sprinklers will not be possible without additional hardware. A Mega will be suitable.

Wrong approach :wink: You should use arrays so you can keep your program tidy.

I will see if I can brew something for you to demonstrate the basics for the sprinklers

Playing music from a PC?

Yes its the uno. I also got TWO SainSmart 16-Channel 12V Relay Module for the 30 solenoids. The sprinklers will be activated and stopped multiple times. And no everything will be in a usb as i dont think the arduino has space for all the music files.

So i will need to input miliseconds for each activation point to be precise on the show i think i seen somewhere that you can do something like sprinkler1(1000, 2300, 2500) sprinkler2(1500, 4200, 6000) then delay it of course. I got a whole nother problem to connect big speakers to play the music, i know i will need to get a separate board for it but that will come later i just need to figure out the main problem first. I will mainly have one button to start a music library in my usb. And the other button is to stop that music and start the shows music and code. Then when i press the first button again it will go back to playing the music from that library/folder and reset the timer of the code just in case i want to run the show again. If you can help, that would be appreciated.

Do you have a suitable power supply? Solenoids will probably want a lot of power. Maybe for your project you will need an Arduino, buttons, DFplayermini or similar player, possibly with an additional amplifier if his is not powerful enough, speakers and maybe a micro SD card. So you won't need a computer and everything will work independently. You could also add some LEDs or a display to show you what mode is currently selected. If you are adding a powerful amplifier, look at the PAM and TPA series, as you should also plan for a suitable power supply for them as well as speakers. You can have a simple text file on the memory card that simply lists the times which sprinkler is on and off. DFplayermini also works with a micro sd card but i don't know if it is possible to use the same card for both the music and the control file for the sprinklers.

With an Uno you can add pins through chains of cheap shift registers.
It takes 3 IO pins to output data to a whole chain, 256 output 8x8x8 led cube projects did just that but be sure there was a lot of wiring needed as shown on pics and video (youtube). More pins can be added if speed is not critical.

Yes i will have an external battery of some sort that will power everything because i will also be using my water pump to push the water out of a container instead of using a water hose that will not supply enough water for my needs.. The speakers dont really need to be amplified as it will not be really loud and they will be powered by their own power cord of course, but the speakers will be powerful enough to be loud. And yeah i need my arduino and my usb to control everything as soon as i push the button to start it so that i wont use my pc. How would you code that simple text file in the arduino and what would you list/code in the text file for each sprinkler? Thats all i really need to list the times each sprinkler will go off and cutoff, nothing major.

yeah my project will consist of many wires and pluming tubes lol

Let's try to get you started with the software side of things.

Note in advance; I usually don't use byte, int etc. but uint8_t (unsigned 8-bit int) or uint32_t (unsigned 32-bit integer).

I already mentioned arrays so we create an array of for the pins that control the sprinklers.

// the sprinkler pins
const uint8_t sprinklerPins[] = {6, 7, 8};

Your array will obviously be bigger. By not specifying the size of the array, it will scale to your needs.

Instead of sprinklerPin1, sprinklerPin2 and sprinklerPin3, we now have sprinklerPins[0], sprinklerPins[1] and sprinklerPins[2]; note that the first element of an array has index zero and the index of last element is the number of elements minus one.

You can iterate through this array using e.g. a for-loop as demonstrated below

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Sprinkler Demo"));

  // 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);
  }
}

There are two things that will possibly not look familiar, NUMELEMENTS and SPRINKLER_ON. Place the following at the top of the sketch

// 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 HIGH
#define SPRINKLER_ON HIGH

NUMELEMENTS is a macro that tells you how many elements there are in an array; the type of the array does not matter.
SPRINKLER_ON is also a macro which gives a clear indication what will happen when using it in the code; HIGH and LOW do not mean much. If the sprinklers switch on when the output is LOW, you can change the line to #define SPRINKLER_ON LOW

Instead of having 30 pinMode() statements (one for each of your sprinklers), you only have one.

Before setting the pinMode, the sketch sets the output level. By default a pin that is switched to output will be LOW. If your sprinklers would switch on when the pin is LOW, all sprinklers would switch on when the pinMode is executed. By first setting the (not yet output) to the value that indicates off, you prevent this from happening. Note the exclamation mark in !SPRINKLER_ON, it means 'NOT SPRINKLER_ON'.

Next an introduction to 'structs'. In a struct, related information is grouped; think of a phonebook where you have a name and a phone number. I our case we have a pin, a time, and the what the status of the pin must be.

// 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
};

The relStartTime indicates the relative time in the pattern that something must happen. Instead of using the pin, we use the index of a pin in the sprinklerPins array; the advantage is that if you ever have the need to change a pin, you only have to change it in one place. pinStatus should indicates of a sprinkler must switched on or off.

There are two additional variables, a flag indicating if the specific pattern entry is started and an absolute start time that will be filled in when the pattern is reset.

We can now create our pattern as shown below

SPRINKLER sprinklerPattern[] = {
  {1000UL, 1, SPRINKLER_ON, false, 0},   // sprinklerPins[1], on at 1000 ms, not started
  {1000UL, 0, SPRINKLER_ON, false, 0},
  {2000UL, 0, !SPRINKLER_ON, false, 0},
  {3000UL, 1, !SPRINKLER_ON, false, 0},  // sprinklerPins[1], off at 3000 ms, not started
  {3000UL, 2, SPRINKLER_ON, false, 0},
  {5000UL, 0, SPRINKLER_ON, false, 0},
  {7000UL, 2, !SPRINKLER_ON, false, 0},
  {7000UL, 0, !SPRINKLER_ON, false, 0},
};

The first line states that 1000 milliseconds after the program starts looping through the pattern the sprinkler on sprinklerPins[1] will be switched on; similar for the second line.
The thirld line sdtates that 2000 milliseconds after the program starts looping through the pattern the sprinkler on sprinklerPins[0] will be switched off.

Except for the setup() and loop() functions there a few additional functions. printSprinklerPins(); prints the sprinkler pins and printSprinklerPattern() that you have created.

Further there is a function validateSprinklerPattern() which checks if the pinIndex that you specified in sprinklerPattern[] does indeed exists; this function returns true if there are no errors, else false. resetSprinklerPattern() resets the pattern when you want to run it again

We can now expand the setup() function.

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Sprinkler Demo"));

  // 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();
}

In loop() we can now loop through the pattern entries and take action when needed.

void loop()
{
  // 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)
      {
        ...
        ...
      }
    }
  }
}

The above shows the logic. I've used a for-loop as I suspect that it's easier to understand for you; it might be needed to change later. The ... indicates the actual action that needs to be performed which is

  1. Set the sprinkler pin to on or off.
  2. Indicate that this entry is started/done.
        // 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 that is basically it to get you started.

Full code

// 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 HIGH
#define SPRINKLER_ON HIGH
// button wired between pin and GND; use INPUT_PULLUP
#define ISPRESSED LOW

// the sprinkler pins
const uint8_t sprinklerPins[] = {6, 7, 8};

// 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[] = {
  {1000UL, 1, SPRINKLER_ON, false, 0},   // sprinklerPins[1], on at 1000 ms, not started
  {1000UL, 0, SPRINKLER_ON, false, 0},
  {2000UL, 0, !SPRINKLER_ON, false, 0},
  {3000UL, 1, !SPRINKLER_ON, false, 0},  // sprinklerPins[1], off at 3000 ms, not started
  {3000UL, 2, SPRINKLER_ON, false, 0},
  {5000UL, 0, SPRINKLER_ON, false, 0},
  {7000UL, 2, !SPRINKLER_ON, false, 0},
  {7000UL, 0, !SPRINKLER_ON, false, 0},
};

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Sprinkler Demo"));

  // 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()
{
  // 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());
      }
    }
  }
}

/*
  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
*/
void resetSprinklerPattern()
{
  // hold the current time
  uint32_t startTime = millis();
  // 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;
  }
}

Ask what you don't understand.

Notes:

  1. As this will more than likely not run for more than a day, no attention was paid to the millis() overflow after 49 days.
  2. Kept as simple as possible.

To do:

  1. Add button stuff to start the pattern.
  2. Indication that a pattern was completed.
  3. 30 inputs.

What about shift registers and relays?

Arduino type byte is uint8_t. Same thing only easier to type and read.
Signed 8 bit is Arduino type char, also easy to type and read.

I think that some people must like tight boots.

hi, welcome to the forum.

OK, wait. You are starting way too soon. Usually june projects are turned to in any serious way sometime late in May.

a7

Nice. I don't like the relays' indicators so I took the liberty of adding LEDs that follow them and can actually be seen so one can better appreciate the processes.

If it isn't obvious, in either just comment in or out the function you want to observe.

a7

Thats a whole lot of code for simple lol but thanks i wouldn't have figured all that out.

I started 3 months ago lol. This isnt a simple mans project. I have to learn electronics, plumbing, framing, sculpting, and plan the whole damn birthday while also setting it up myself. And you know i work and got other projects i will do all at the same time. You think you can do this all in one sitting. Nope you have to plan ahead of time because you get tired of doing research and i have done lots. But i do have the show file completed. It consists of music sound effects and character voices. That itself took lots of research. Its not a easy task, but if your motivation is there then you can do anything. I just need help with the coding because i understand some of it but not all the way.

You could store in flash for each servo a series of ON and OFF times and process one servo at a time in loop() to check time and either return (not up) or read the next wait into the timer for that valve and flip the valve. Code it right, all 30 process in less than 1 ms.

Flash has lots of space.

for my own project, i used a lead accu 12 vlot with 7Ah that will give power enougg for 3 solenoids duringe 2 days six hour each. So one show with 20 sprinklers and added solenoids can be used 36 hour. compare it to 1 hour and onder device, you will succeed!

"sprinklers" updated with @alto777 WS2812 idea. That was fun.

Water Show Test
Ok so this is what i did with the code so far, it was just me testing the water guns for the show. But i was wondering if anyone can help me with something. Ok so with this code i have to start my sprinkler count at 0 or else this code will not work. Can we get it to start at 1 so it wont be confusing for when i code all the timing? Because 0 is 1 and 1 is 2, it will get confusing mid coding through the show. I will have a total of 18 sprinklers. 12 on robotnic and 6 on the plane, currently working on the water guns for the plane. I have also switched to the arduino mega so is there a way to use the analog pins 1-18 instead of the digital ones? Again just to make it easier for me. When i start up my arduino all the sprinklers go off at once for 5 seconds for whatever reason, i do not want that to happen as itl drain my battery which i will use for this. I will also need another event to happen which is random activation. So before the show begins i want a random sprinkler from 1-12 to activate every 3 minutes for 3 seconds. I was also thinking of getting a digital timer to let everyone know when the blast will happen but lets focus on the code for now. I will have a on/off flip switch button to activate the show part. So when the switch is off the random sprinklers from 1-12 script runs, then when i turn it on the show begins. One more thing i purchased a mp3 player to play the track that will run in the background,
DY-HV20T 12V/24V Power Supply10W/20W Voice Playback Module Supporting Micro-SD Card MP3
i need that file which is a .WAV file to play when the switch is turned on for the show. This was supposed to be done in june but i didnt know it was going to take me this long. But im hoping to have my sons bday party in 3 weeks and need this script ready so i can code the show. It will be 5 kids and sonic on a plane fighting robotnic. THANKS FOR ALL THE HELP!

Im using a truck battery and at the same time i will be charging it. It will run for only about 4-5 hours really and the show is only 5 minutes. I think i will be ok maybe. One thing im worried about is that the solenoids say it only draws 1 amp to use but when i read it with a tester its pulling amps. So hopefully nothing catches fire lol. But na im only going to run about 4-5 sprinklers at once if my pump can handle it. Might have to buy another one. I will have to test all this later this week or next week.

1 Like