Sprinkler Controller

OK. I have finally finished my sprinkler system controller box and want to post it here for the sake of sharing. It has a small problem that I’m sure I can fix but frankly right now my mind is mush. I have commented my code as much as I can so maybe that will help. I plan to add a parts list and find a place to post some pictures in the future too.

My problem is simple (famous last words). Some times when I reset the system it doesn’t cycle and all the relays engage. Somewhere I need to initialize all of my outputs to a known state upon startup. (Done such before but can’t pull it out of my… you get the point.) Also in the comments you will see a section called “Futurum Consilium” which is Latin for “Future Plan” (Sorry but I throw Latin in my descriptions of all my code as a sort of trademark/schema) where if you have any ideas please post them.

Ego sum ​​panis tosti,

Russ

/*
Aspersorio Moderatorem

Auctor: Russ Lewis

WARNING
Do not plug in the USB connector to the Arduino unless the system is receiving power from the
115vac/12vdc/5vdc power system. The Arduino cannot handle the current and will become a smoke
generator instead of a sprinkler controller.

Summarium:
 Ground moisture sensor turns on sprinkler for a specified time if the sensor
 indicates that the ground moisture drops below a specified level.
 
Descriptio:
 Controller is contained within a 6"x6"x4" watertight enclosure. One side has two 16 pin
 connectors (115vac in and I/O). Internally on the cover there is a small fan and an LCD display
 with 20 characters x 4 lines. Affixed to one side of the box is a 12vdc wall wart supply that
 is hard wired to the AC input connector. Mounted to the base of the box is a 4 relay board on
 one side and an Arduino UNO R3 micro-controller on the other side. Stacked on top of the
 Arduino is a custom made "shield" that has a LM7805 regulator (and infrared receiver for
 future expansion). The shield also has a switch to turn off the LCD display when not in use.
 
Singula Res:
 Initially I planned to use 12vdc throughout the system since the sprinkler solenoid valves are
 controlled by 12vdc however the relay board uses 5vdc for its control signals. While this
 particular relay board has the ability to use the 5vdc from the Arduino it incurred too much
 of a current draw and overheated the Arduino fairly quick. As a result I had to build a 5vdc
 supply using a LM7805 regulator with a high efficiency heatsink and add a cooling fan to stir
 the air around for cooling. This solved the power problems and provided 5vdc to the Arduino,
 the LCD, and the relay board and the 12vdc was used to run the small fan and outputs from the
 relays.
 
Ratio Operandi:
 When the system starts it begins to interrogate the soil moisture sensors PINmoisture[]. It
 does this through the iteration of an array so the primary section of the code is "reusable". 
 The moisture is stored in a temporary variable called VALUEmoisture which is compared to the 
 corresponding preset value of LEVELmoisture[]. If the moisture level is less than the preset 
 level then the system turns on the sprinkler for that system using the array PINsprinkle[]. 
 Then since the system is no longer idling the fan is turned on using PINfan. PINsprinkle[] 
 stays on for a preset time set by TIMEsprinkle[] and then it shuts off. It doesn't matter if 
 it delivered enough water to saturate the soil right now since based on the water delivery 
 system (sprinkler or subterainean weep hose) the moisture sensor just got wet so its readings 
 are no longer reliable until the soil percolates the newly delivered water. When the system 
 is done watering it shuts off the sprinlkler and runs the fan for an additional time set by 
 TIMEcooldown to allow the 5vdc regulator board to recover slightly before the system proceeds 
 to the next moisture sensor and repeating the above process. 
 Two functions are used; Checkdisplay() and MSGdisplay(). Checkdisplay() is used as a logic 
 check for the LCD switch. If it's off then it returns to whatever it was doing. If it's on 
 then it will pass on the three variables NUMmessage, NUMvalue, NUMbox to the MSGdisplay() 
 function. The MSGdisplay() formats and outputs these values to the LCD and then returns to
 whatever was going on beforehand.
 
Error Tenendo et Sustentacionem:
Unum) The system has a 20 character by 4 line LCD display that allows for monitoring of the 
 operation of the system. the display is controlled by the tiny switch on the shield board by 
 reading PINdisplaySwitch. The logic software uses a variable called SWdisplay to decide 
 wheather to turn on the LCD and its backlight based on the value of PINdisplaySwitch 
 (ergo, turn on the switch and the display comes on and vice versa).
Duobus) Each sensor is checked to be sure that its value falls within a certain range 
 (MINsensor - MAXsensor). This is done to check for shorts or opens in the moisture sensors. 
 Some open circuit conditions however can still cause the PINmoisture[] values to float and 
 may not always be 100% reliable.
Tribus) After so many cycles (NUMfancount) as specified by NUMfancycle the fan is turned on 
 for cooling whether it needs it or not. It will run for the time specified by TIMEcooldown 
 before continuing. In all cases of fan operation blnFan is used to indicate the status of 
 the fan so it can be displayed on the LCD display.
Quatuor) The value of TIMEconstant can be changed to alter the speed of the cycling of
 everything. This value is used as a multiplier to set the number of milliseconds that
 it takes for each operation. 60000 will set everything to happen in a matter of minutes and 
 1000 will set everything to happen in a matter of seconds. This is especially useful for 
 verifying operation and troubleshooting.
 
Futurum Consilium:
 Temperature sensor to control the fan operation rather than just time based operation.
 Implement the remote control capability so it doesn't have to be reprogrammed to change the
 moisture levels and watering times.
 Use the EEPROM capability of the Arduino to allow for permanent storage of values changed by 
 remote control so they aren't lost if the system loses power.
 Implement the "sleep" function of the Arduino to minimize heat inside the box and have it
 alive once in a while to check the soil. If it makes a few passes and the moisture values
 are good then go to sleep and check again in some specified time.
 
Nominando Variabiles:
 PIN... = Pin assignments
 LEVEL... = Pre-defined (or calibrated) level
 VALUE... = Value obtained from an input
 TIME... = A value corresponding to a time measurement
 NUM... = A generic number type but most commonly an integer
 */

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

// Variable definitions
int PINdisplaySwitch      = 11;              // controller status display
int SWdisplay             = 0;               // display on/off switch
int VALUEmoisture         = 0;               // initialize the soil moisture sensor
int NUMboxes              = 3;               // number of boxes (= array elements)
int TIMEconstant          = 60000;           // time multiplier constant in milliseconds
int PINfan                = 6;               // cooling fan pin
int TIMEcooldown          = 1;               // delay time to leave fan on after each cycle
int MAXsensor             = 1020;            // max sensor reading anything greater is an error
int MINsensor             = 20;              // min sensor reading anything less is an error
int PINsprinkle[]         = {3, 4, 5};       // sprinkler valve control pins (DOs)
int PINmoisture[]         = {0, 1 ,2};       // moisture sensors (AIs)
int TIMEsprinkle[]        = {9, 9, 9};       // time each sprinkler stays on
int LEVELmoisture[]       = {5, 5, 5};       // minimum soil moisture levels (value = 1-10)
int NUMfancycle           = 20;              // fan cycles on every 20 minutes at least
int NUMfancount           = 0;               // running count to cycle the fan
boolean blnFan            = false;           // fan status

LiquidCrystal_I2C lcd(0x3f,20,4);            // set the LCD address to 0x3f for a 20 chars and 4 line I2C 2004 display

void setup()
{
  lcd.init();                                            // initialize the lcd 
  lcd.noBacklight();                                     // turn off backlight 
  pinMode(PINfan, OUTPUT);                               // declare the PINfan as an OUTPUT
  digitalWrite(PINfan, HIGH);                            // and make sure it is off (HIGH = Off)
  pinMode(PINdisplaySwitch, INPUT);                      // declare the PINdisplaySwitch as an INPUT
  digitalWrite(PINdisplaySwitch, HIGH);                  // and make sure it is off
  TIMEcooldown = TIMEcooldown * TIMEconstant;            // set cooldown time and
  for (int k = 0; k < NUMboxes; k++) {                   // set each box's individual values
    pinMode(PINsprinkle[k], OUTPUT);                     // declare the PINsprinkle as an OUTPUT
  }
}

void loop() {
  NUMfancount += 1;
  for (int x = 0; x < NUMboxes; x++) {                              // iterate through each box
    Checkdisplay(0, 0, x);                                          // update display
    delay(1000);                                                    // slow things down a little
    VALUEmoisture = analogRead(PINmoisture[x]);                     // Read soil moisture
    Checkdisplay(1, VALUEmoisture, x);                              // update display
    int LEVELmoist = (LEVELmoisture[x] * 100);                      // convert LEVELmoisture[x] to a number 100-1000 called LEVELmoist
    if (VALUEmoisture < MAXsensor && VALUEmoisture > MINsensor) {   // Check for sensor errors and only turn on if the sensor is good
      if (VALUEmoisture < LEVELmoist){                              // if the soil moisture is below this level
        digitalWrite(PINsprinkle[x], LOW);                          // turn on sprinkler valve
        Checkdisplay(2, 1, x);                                      // update display
        delay(1000);                                                // slow things down a little
        digitalWrite(PINfan, LOW);                                  // turn on the cooling fan
        blnFan = true;                                              // indicate that the fan is on
        Checkdisplay(3, 1, 0);                                      // update display
        delay((TIMEsprinkle[x] * TIMEconstant));                    // leave sprinkler on for this long
      }
    }
    digitalWrite(PINsprinkle[x], HIGH);                             // turn off box sprinkler valve
    Checkdisplay(2, 0, x);                                          // update display
    if (blnFan == true) {                                           // if the cooling fan is on
      delay(TIMEcooldown);                                          // leave it on for this long
      digitalWrite(PINfan, HIGH);                                   // then turn it off
    }
    Checkdisplay(3, 0, x);                                          // update display
  }
  if (NUMfancount == NUMfancycle)  {                                // check to see if we need a cooling cycle
    digitalWrite(PINfan, LOW);                                      // if so turn on the cooling fan
    Checkdisplay(3, 0, 0);                                          // update display
    delay(TIMEcooldown);                                            // leave cooling fan on for this long
    digitalWrite(PINfan, HIGH);                                     // then turn off the cooling fan
    Checkdisplay(3, 0, 0);                                          // update display
    NUMfancount = 0;                                                // reset count
  }
}

void Checkdisplay(int NUMmessage, int NUMvalue, int NUMbox) {
  SWdisplay = digitalRead(PINdisplaySwitch);                        // check to see if the display switch is on
  if (SWdisplay == LOW) {                                           // LOW = On
    MSGdisplay(NUMmessage, NUMvalue, NUMbox);                       // if it is on then output the values to the LCD
  } 
  else {                                                            // if the switch is off
    lcd.init();                                                     // then make sure it is blank
    lcd.noBacklight();                                              // and turn off the backlight
  }
}

void MSGdisplay(int MSGmessage, int MSGvalue, int Box) {
  String strStatus;                        // convert the status to text
  if (MSGvalue == 1) {                     // 1 is ON
    strStatus = "ON ";                     // text to display if it is on 
  } 
  else {                                   // and 0 is OFF
    strStatus = "OFF";                     // text to display if it is on 
  } 
  lcd.backlight();                         // make sure the backlight is on
  switch (MSGmessage) {                    // Set up a switch/case where the case value corresponds to line number
  case 0:                                  // line 0 is the title line
    lcd.setCursor(3,0);                    // start on first line 3rd character (for centering purposes)
    lcd.print("Watering System");          // output text for the title line
    break;                                 // break out of the case
  case 1:                                  // line 1 shows moisture read by the sensor
    lcd.setCursor(0,1);                    // start on second line 1st character
    lcd.print("Box ");                     // output text for this line
    lcd.print(Box + 1);                    // fill in the value with the Box value + 1 since it starts at 0
    lcd.print(" Mositure = ");             // stay on this line and add some more text
    lcd.print(MSGvalue);                   // fill in the value with the MSGvalue which has the current system moisture value
    break;                                 // break out of the case
  case 2:                                  // line 2 shows the sprinkler status
    lcd.setCursor(0,2);                    // start on third line 1st character
    lcd.print("Sprinkler ");               // output text for this line
    lcd.print(Box + 1);                    // fill in the value with the Box value + 1 since it starts at 0
    lcd.print(" is ");                     // output some more text for this line
    lcd.print(strStatus);                  // fill in the value with the strStatus which will be ON or OFF
    break;                                 // break out of the case
  case 3:                                  // line 3 shows the cooling fan status
    lcd.setCursor(0,3);                    // start on forth line 1st character
    lcd.print("Cooling fan is ");          // output text for this line
    lcd.print(strStatus);                  // fill in the value with the strStatus which will be ON or OFF
    break;                                 // break out of the case
  }
}

// The geek language is Latin.

my design has monostable oscillators (like a 555 chip as a one-shot). Its output goes to the relay drive (or MOSFET drive).

the one-shot must be retriggered every x seconds or it will time out and turn off the valve.

Retrigger can come from microprocessor or a pulse from an bit out of an RF radio receiver or XBee DIO pin.

Fail-safe.

No floods

Wow, nice job! This is along the lines of what I had planned to do (need to get pipe in the ground first).

I intended to use a Mega as I have 6 acres and a many zones with different watering requirements. I was also going to track water use and base watering off of available water/water used and use the moisture sensors to keep all zones balanced to each other, rather than a minimum (though on second though perhaps I do need a set minimum saturation and a ‘dry zone’ that can be skipped/sacrificed if water supply drops out or the heat dries things out too much). My irrigation supply is sort of an ‘all or nothing’ type set up where water must be ordered, turned on or off while I’m at work, and billed according to water supplied, not water used (if the water at the ditch is open and I’m not using it, it still counts toward my total, even though no water is flowing).

Also, due to the fluctuation in supplied water due to a wide range of variables (plugged filters, debris in weir gate, supply shut down, etc), and a generally high pump flow to be expected (higher than supply), a pond or tank will probably be used to store water with a float switch, so actual water delivered (pump on-time and pump rate of flow) is more important to track than how long the sprinklers are on (or rather, the time the sprinklers are on is determined by supply of water, which fluctuates.) Sprinkler set ‘on’ time will be determined, in the end, by how dry the soil is in that zone, water supply, time of day (more water at night, when possible), temperature, wind speed, and even humidity if I work it in (things dry out irregularly based on these factors quite rapidly in our area.

I’ll post my code if I get there. At the rate the system is getting put together the lateral I’m on might be piped and pressurized before I finish the pipe!