Automatic Watering System

An automatic watering system with 2 modes: water every day/every 2 days/once a week OR water once the soil gets dry enough.

Parts:

  • Arduino Nano
  • Analog capacitive moisture sensor 1.2
  • 2 x 5K Ohm rotary potentiometers (with knobs)
  • Push button
  • On/off switch (either for the whole system or just the LCD display)
  • Azdelivery HD44780 1602 LCD Module 2X16 (with I2C control circuit)
  • Anself USB Power Water Pump for Fountain Aquarium and Model 220L/H
  • AZDelivery Real Time Clock RTC DS3231
  • JBL Air Hose 3 m 9/12 mm
  • ~150 Ohm resistor
  • 1N4004 diode (or something similar)
  • 2N2222 transistor (or something similar)
  • USB power cord

The LCD shows the current time, how many seconds the pump will operate at a time, and the last watering time. Rotary knobs can adjust the dryness trigger-level for watering and how much water is pumped in seconds. The switch turns the backlight on and off, while the button switches between watering modes.

 

Construction:

Connection diagram is shown below. Quite a lot of wires, so it might be a good idea to use also some kind of circuit board for stability. The motor (water pump) has a flyback diode, which protects the rest of the circuit from sudden voltage spikes. The transistor 2N2222 was chosen because it has a max current of 800 mA, which should be enough for the 2.3 W pump (P=UI, I=0.46 A). Arduino Nano pins have a max current output of 40 mA, so a current limiting resistor of about 150 Ohms is needed (R=U/I=5V/0.04A=125 Ohms). The potentiometers need to be small enough, 5 kOhms is ok; if you use too large (say, 100k as I first tried), the readings will be very inaccurate due to too little current going into the input pin.

Edit: The push button should be connected to the (-), not the (+) node, and uses the Arduino’s internal pull-up resistor.

Both the LCD (with the driver board) and RTC use I2C connection, so they use the same pins on the Arduino (SCL to SCL and SDA to SDA). One of the potentiometers is used to adjust how long the water pump operates (=how much water per one watering is pumped) and the other is used to adjust the dryness level where the pump starts to operate if the watering mode is set to trigger once the soil is dry. The push button is used to switch between watering modes. “CAP” in the picture above is the capacitive moisture sensor. By using a capacitive moisture sensor, problems related to corrosion are mostly avoided, which are typical with resistive moisture sensors.

The RTC (real time clock) is used to show time on the LCD and keep track of time for the first watering mode; i.e., pump water once a certain time has passed.

The water pump is an aquarium pump and is put into a water bucket (under water). The hose (from the bucket to the pot) is connected to the pump, which may need some duct tape or something similar to tighten the connection.

Afterthoughts

After a couple of months in use, the watering system has worked without problems. However, the moisture sensor mode has not been very reliable at least so far. The sensor works OK, but it measures the dryness only on the surface and at one point of the pot, so I feel it does not “see” the whole picture. Time-based watering is foolproof, but does not take into account if the day(s) have been hot or cold, i.e., has the soil dried up faster or slower. I guess an optimal solution would be to take both measures, time and dryness, into account.

The Code

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include <RTClibExtended.h>
#include <String.h>

#define BUTTON 2
#define DRIVER 3
#define SWITCH 5
#define GND2 6   // new dedicated gnd node
#define MEASURE 16
#define POTENTIOMETER 17
#define VCC3 11  // new dedicated +5V node
#define GND3 12  // new dedicated gnd node
#define POTENTIOMETER2 15

String printTime(bool last_time);
void checkInputs();
void checkIfWateringTime();
void doWatering();

LiquidCrystal_I2C lcd(0x27,2,1,0,4,5,6,7,3,POSITIVE);
RTC_DS3231 RTC;
int button_counter=0;
bool button_down=false;
int last_watering_day=-1;   // actually, last day watering was attempted
int days_to_water=0;
int watering_mode=0;
int moisture_limit=2000;
int moisture_now=0;
int delay_counter=10;
String last_watering_str="Last: never";
int water_amount=0;

void setup() {
 lcd.begin(16, 2);
 lcd.clear();
 lcd.setCursor(0,0);
 lcd.print("Starting up."); 
 lcd.setBacklight(HIGH);
 
 Wire.begin();
 RTC.begin();
 //RTC.adjust(DateTime(__DATE__, __TIME__));
 RTC.writeSqwPinMode(DS3231_OFF);

 pinMode(BUTTON, INPUT_PULLUP); 
 pinMode(DRIVER, OUTPUT);
 digitalWrite(DRIVER, LOW);
 pinMode(SWITCH, INPUT_PULLUP); 
 pinMode(GND2, OUTPUT);
 digitalWrite(GND2, LOW);
 pinMode(MEASURE, INPUT);
 pinMode(POTENTIOMETER, INPUT);
 pinMode(GND3, OUTPUT);
 digitalWrite(GND3, LOW);
 pinMode(VCC3, OUTPUT);
 digitalWrite(VCC3, HIGH);
 pinMode(POTENTIOMETER2, INPUT);
}


void loop() {
  delay(500);
  delay_counter--;

  lcd.clear();
  lcd.setCursor(0,0);
  String first_row=printTime(false).c_str();
  first_row.concat("     ");
  first_row.concat(String(water_amount,DEC));
  first_row.concat("s");
  lcd.print(first_row);

  // Main functionality
  checkInputs();
  checkIfWateringTime();

  if(delay_counter==0)
    delay_counter=12;

  // Print info to lcd
  String to_lcd="";
  if(watering_mode==0) 
    to_lcd="Water 1/d (";
  else if(watering_mode==1) 
    to_lcd="Water 1/2d ("; 
  else if(watering_mode==2) 
    to_lcd="Water 1/week("; 
  if(watering_mode!=3){
    to_lcd.concat(String(days_to_water, DEC));
    to_lcd.concat("d)");
  }
  else{
    to_lcd="Dry: ";
    to_lcd.concat(String(moisture_now,DEC));
    to_lcd.concat(" (max ");
    to_lcd.concat(String(moisture_limit,DEC));
    to_lcd.concat(")");
  }
  if(delay_counter < 6) 
    to_lcd=last_watering_str; 
  lcd.setCursor(0,1); 
  lcd.print(to_lcd.c_str()); 
} 

void checkIfWateringTime(){ 
  DateTime now = RTC.now(); 
  if(now.day() != last_watering_day){ // watering max once per day 
    if(now.hour()==7 && now.minute()==20){ 
      if(days_to_water>0){
        days_to_water--;
      }
      else{
        doWatering();
        if(watering_mode==1)
          days_to_water=1;
        else if(watering_mode==2)
          days_to_water=6;
        //if(watering_mode==0), days_to_water=0, although not set here
      }
      last_watering_day=now.day();     
    }

    if(watering_mode==3 && delay_counter==0){
      moisture_now=analogRead(MEASURE);
      moisture_limit=analogRead(POTENTIOMETER);
      if(moisture_now > moisture_limit){
        doWatering();
        days_to_water=9;  // water at least once every 10 days
        last_watering_day=now.day(); 
      }      
    }    
  }
}

void checkInputs(){
  
  // BUTTON -- switch between watering modes
  int btn_val = digitalRead(BUTTON);  
  if (btn_val == HIGH) { // button is up    
    if(button_down){
      button_down=false;      
    }
  } else { // button down         
    if(!button_down){ // button was pressed down just now
      button_down=true;
      watering_mode++;
      if(watering_mode>3)
        watering_mode=0;
      
      if(watering_mode==0)
        days_to_water=0;
      else if(watering_mode==1)
        days_to_water=1;
      else if(watering_mode==2)
        days_to_water=6;
      else if(watering_mode==3){
        days_to_water=9;
        delay_counter=20;
      }
    }
  }  
  
  // SWITCH -- backlight on/off
  int switch_val = digitalRead(SWITCH);
  if (switch_val == HIGH) { // switch off    
    lcd.setBacklight(LOW);
  } else { // switch on    
    lcd.setBacklight(HIGH);
  }

  // POTENTIOMETER + MEASURE -- watering_mode=3
  if(watering_mode==3){
    moisture_now = analogRead(MEASURE);
    moisture_now = map(moisture_now, 0, 1023, 0, 99);
    int limit = analogRead(POTENTIOMETER);
    limit = map(limit, 0, 1023, 0, 99);
    if(moisture_limit != limit){
      delay_counter=20;
      moisture_limit = limit;
    }
  }

  // POTENTIOMETER2 i.e. HOW MUCH TO WATER
  int val = analogRead(POTENTIOMETER2);
  water_amount = map(val, 0, 1000, 1, 11);
}

String printTime(bool last_time)
{
  DateTime now = RTC.now();
  String time_str="";  
  if(last_time){
    time_str="Last ";
    time_str.concat(String(now.day(), DEC));
    time_str.concat('.');
    time_str.concat(String(now.month(), DEC));    
    time_str.concat(". ");   
  }
  int num=now.hour();
  if(num<10)
    time_str.concat('0');
  time_str.concat(String(num, DEC));
  time_str.concat(':');
  num=now.minute();
  if(num<10)
    time_str.concat('0');
  time_str.concat(String(num, DEC));
  if(!last_time){
    time_str.concat(':');
    num=now.second();
    if(num<10)
      time_str.concat('0');
    time_str.concat(String(num, DEC));
  }
  return time_str;
}

void doWatering(){
  digitalWrite(DRIVER, HIGH);
  delay(water_amount*1000);
  digitalWrite(DRIVER, LOW);
  last_watering_str=printTime(true);
}



 

 

You May Also Like

Leave a Reply

Your email address will not be published.

Note: comments are moderated.