Maker.io main logo

Arduino Controlled Automatic Indoor Plant Watering System

573

2018-08-22 | By Cody Walseth

License: None

Overview

This project is an Arduino Uno controlled automatic plant watering system.  Using an Adafruit Peristaltic pump, DF Robot capacitive soil moisture sensor, addressable LEDs and a micro servo with a couple 3D printed parts. The way it works, is if the sensed moisture level falls below the desired value, the pump and servo will turn on.  The servo will move the pump hose back and forth accross the plant evenly distributing the water.  The pump (and servo) will run for 10 seconds and stop for 2 minutes or until the desired moisture level is reached.  If after 15 attempts of watering the desired moisture is not reached, one of the LEDs will flash Red and the pump will not run again until the reset button is pressed.  This is to alert you when the water source is empty. During normal operation, one of the LEDs will update with the sensor value changing from Green (wet) to Red (dry) giving you an estimated moisture level at a glance.  If you would like to reset the desired moisture, simply water the soil until it gets to the moisture level you would like it to maintain, and hold the set button for 3 seconds.  Many variables are defined so that you can adjust them to fit the requirements of your plant or soil.

Circuit and Explanation

The capacitive moisture sensor and micro servo are connected to analog I/O pins while also being connected to transistors between the three-pin connector and the GND pin.  This was done to use a digital pin to disable the sensor/servo when not in use. Eliminating servo "chatter" and reduce corrosion if a resistive moisture sensor is used.  Analog pins were used to make them interchangeable as they both have the same pinout.  A third set of components was added for potential future use of a second moisture sensor. The servo is used to distribute water from side to side as it is watering.

The buttons use internal pullup resistors.  The purpose of the two buttons is for one to manually turn on the water and the other to reset the "out of water" alarm along with resetting the desired moisture level if the button is held in for a set length of time.

The LEDs are addressable 5mm through hole WS2812s.  The Adafruit NeoPixel library found HERE is used to set these to the desired color.

Finally, the pump being used is an Adafruit Peristaltic 12V pump.  With a peristaltic pump, no part of the pump contacts the water. The pump "squishes" the silicone tube to create the water flow.  The pump is connected to the two-pin connector at the bottom right of the schematic. An optocoupler was added to the circuit for protection.  A mosfet is used to allow digital motor control.

Schematic

The Schematic was drawn in KiCad.  Many of the components used in this project are available in the DigiKey KiCad Library!

indoor plant Schematic

Parts Needed

Code

Both the Adafruit NeoPixel library and the Servo library must be included at the top of the Arduino Sketch.

Copy Code
/*  Desk Plant Watering System 
 * 
 *  Be sure to look through the settings and test your sensor value on 
 *  your plant and make adjustments as needed. Different soils and plants 
 *  may require modifications to the variables below.
 */
 
#include <Servo.h>
#include <Adafruit_NeoPixel.h>

//#define servoWaterUsed //comment out if servo is not used.
const bool debug = 0; //set to 1 to enable serial debugging

/***** pin assignments *****/
const int LEDPIN         = 6;
const int NUMPIXELS      = 2;
const int sensorPin      = A4;
const int sensorEn       = 4;
const int setButton      = 8; 
const int waterButton    = 9;
const int pumpPin        = 2;  
const int servoPowerPin  = 5;
const int servoPin       = A5;

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, LEDPIN, NEO_GRB + NEO_KHZ800);

#ifdef servoWaterUsed
  Servo waterServo;
#endif

/***** Global Constants *****/
//color values = switch-case value
const int flashRed = 2;
const int flashGreen = 3;
const int green=4;
const int blue =5;
const int amber =6;
const int led1 = 0;   
const int led2 = 1;   

/***** Global Variables *****/
float filteredMoisture =0; 
int moistureValue = 0;  //initialize at max value to keep the water from turning upon power-up
int prevMoisture = 600; //this will initialize the beginning average higher - water won't start until it gets a 
chance to settle
int sensorValue = 0;  // variable to store the value coming from the sensor
int attempt = 0;      //variable used to count how many times the plant has tried to water
int flashDelay = 250; //delay used for flashing the led
int moistureMax = 1000; //moisture value mapped from 0 to this value
int rawMoistureMax = 600; //max raw value used to scale sensor reading
int rawMoistureMin = 200; //min raw value  used to scale sensor reading
int menuPressTime=3000; //time in milliseconds the button needs to be pressed before resetting 
moisture value
int maxServoAngle = 30; //servo starts at 90 and will move to this position in both directions
int servoCenter = 90; //center position for the servo

/***** Boolean Flags *****/
boolean waterNeeded=false;
boolean waterOut=false;
boolean firstPress=true;
boolean waterButtonState=true;

/***** Timer Variables *****/
unsigned long buttonStartTime = 0;
unsigned long moistureTimer = 0;
unsigned long waterTimer = 0;

/************ Settings ***************/
unsigned long moistureCheckTime = 10000; //time in milliseconds between moisture checks
unsigned long waterCheckTime = 120000;//time in milliseconds between waterings - when needed

int flowTime = 10000; //how long the pump will run each cycle/attempt
int maxAttempts =15;   //max number of times the water pump will run before the 'no water' shut off 
will trigger

/***** Custom Plant-dependant Variables *****/
int desiredMoisture = 750; // this can be changed by holding the button (set moisture function)
int moistureDiff = 10;    //pump will run when moisture reading is less than desired moisture - this value

Setup, Main Loop, and Functions

Copy Code
void setup() 
{
  pinMode(pumpPin, OUTPUT); 
  pinMode(servoPowerPin, OUTPUT);
  pinMode(setButton,INPUT_PULLUP);
  pinMode(waterButton,INPUT_PULLUP);
    
  pixels.begin(); //initialize neopixels
  pixels.setPixelColor(led1, pixels.Color(0,0,0)); //Turn LED off
  pixels.setPixelColor(led2, pixels.Color(100,100,100)); //Set LED2 to White
  pixels.show();
  moistureTimer=millis(); //Begin moisture timer at current millis value
  waterTimer=millis();  //Begin water timer at current millis value
  checkMoisture();  //get first moisture reading
  #ifdef servoWaterUsed
    waterServo.attach(servoPin);
    digitalWrite(servoPowerPin,HIGH);  //turn on survo
    waterServo.write(servoCenter); //rotate servo to the center position (90)
    delay(1000); //give the servo time to move to the center.
    digitalWrite(servoPowerPin,LOW); //turn off servo
  #endif
  if(debug) Serial.begin(9600); //turn on serial for debugging or monitoring values
  digitalWrite(pumpPin,LOW);
}

void loop() 
{
  //check moisture if the set amount of time has passed
  if(millis()-moistureTimer>=moistureCheckTime)
  {
    checkMoisture();
    moistureTimer=millis();//reset moisture timer to millis;
  }
  //If left button is pressed water the plant
  if(digitalRead(waterButton)==!waterButtonState)   
  {
    water();
    waterButtonState=!waterButtonState;
  }
  if(digitalRead(setButton)==LOW)  //if right button is pressed reset the water out alarm.  If held, 
reset desired moisture level to current moisture
  {
    if(firstPress==true) 
    {
      waterOut=false; //reset the waterOut alarm
      attempt=0;      //reset watering attempt count
      setLED2(amber); 
      buttonStartTime=millis(); //Start timer to count how long the button is held
      firstPress=false; //set flag to skip this if statement until after the button has been released
    }
    //if button is held for more than menuPressTime (in milliseconds) reset desired moisture value to 
current moisture
    else if(digitalRead(setButton)==LOW && (millis()-buttonStartTime)>menuPressTime && firstPress == false) 
    {
        desiredMoisture=checkMoisture(); //reset desired moisture value
        if(debug) Serial.println("Desired Moisture set to: ");
        if(debug) Serial.println(desiredMoisture);
        setLED2(flashGreen);
    }
  }
  else firstPress=true; //reset button flag 
  
  if(attempt>=maxAttempts)  
  { //if watering attemps has exceded maxAttempts, set waterOut alarm and flash red LED
    waterOut=true;
    setLED2(flashRed);
  }
  else if(moistureValue<desiredMoisture-moistureDiff) waterNeeded=true; //if moisture is lower than
 desired moisture minus differential set waterNeeded flag
  else if(moistureValue>=desiredMoisture+moistureDiff) 
  {
    waterNeeded=false; //if moisture is greater than desired moisture plus differential reset waterNeeded
 flag
    attempt=0;
  }

  if(!waterOut && waterNeeded==true && millis()-waterTimer>waterCheckTime)   
  {  //water the plant if the water is not out, the waterNeeded flag is set, and the water check time has
 passed
    water();
    attempt++;
    waterTimer=millis();
  }
  else setLED1(); //led1 will fade with the moisture reading.
}

/***** Water function to run pump without servo *****/
void water(void)
{
  #ifdef servoWaterUsed
  {
    int positionDelay=flowTime/4;
    //setLED2(blue);
    //digitalWrite(pumpPin, HIGH);  //turn on pump
    digitalWrite(servoPowerPin,HIGH); //turn on servo power
    for(servoCenter; servoCenter<(90+maxServoAngle); servoCenter++)//move servo from center to max
 servo position
    {
      waterServo.write(servoCenter);
      delay(positionDelay/maxServoAngle);
    }
    for(servoCenter; servoCenter>(90-maxServoAngle); servoCenter--)//move servo to min servo position
    {
      waterServo.write(servoCenter);
      delay(positionDelay/maxServoAngle);
    }
      for(servoCenter; servoCenter<90; servoCenter++) //move servo to center
    {
      waterServo.write(servoCenter);
      delay(positionDelay/maxServoAngle);
    }
    delay(1000);
    digitalWrite(servoPowerPin,LOW); //turn off servo power
    digitalWrite(pumpPin, LOW); //turn off pump
    }
  #else
  setLED2(blue);
  digitalWrite(pumpPin, HIGH);
  delay(flowTime);
  digitalWrite(pumpPin, LOW);
  #endif
}

/***** Water function to run pump with servo *****/

/***** Check Moisture Value *****/
int checkMoisture()
{
  float weight=0.5;   //weight used to average the reading - lower value=slower changing average
  digitalWrite(sensorEn,HIGH); //turn on sensor
  delay(250); //wait here for a bit
  filteredMoisture=(weight*analogRead(sensorPin))+(1-weight)*prevMoisture; //filter -weighted average
 with new reading and previouse reading
  moistureValue= map(filteredMoisture,rawMoistureMin,rawMoistureMax,moistureMax,0);
 //constrain moisture value.

    if(debug)Serial.print("Desired Moisture: ");
    if(debug)Serial.println(desiredMoisture);
    if(debug)Serial.print("Filtered Value: ");
    if(debug)Serial.println(filteredMoisture);
    if(debug)Serial.print("Moisture Value: ");
    if(debug)Serial.println(moistureValue);
    

  prevMoisture=filteredMoisture;
  return moistureValue;
}

/***** Set Upper LED *****/
void setLED1(void)
{
  int colorVal,gColorVal,rColorVal;
  //use map function to set colorVal to usable range (0-255) from current moisture value
  colorVal=map(moistureValue,desiredMoisture-moistureDiff,desiredMoisture+moistureDiff,0,255);  
  gColorVal=colorVal;
  if(gColorVal>255) gColorVal=255;
  rColorVal=255-gColorVal;
  if(rColorVal<0) rColorVal=0;
  pixels.setPixelColor(led1, pixels.Color(gColorVal,rColorVal,0));//set led1 to match moisture
  pixels.setPixelColor(led2, pixels.Color(0,0,0));//turn off led2
  pixels.show();
}

/***** Set Lower LED *****/
void setLED2(int mode)
{
  switch(mode)
  {
    case flashRed:
      for(int x=0;x<5;x++)
      {
        pixels.setPixelColor(led2, pixels.Color(0,180,0));
        pixels.show();
        delay(flashDelay);
        pixels.setPixelColor(led2, pixels.Color(0,0,0));
        pixels.show();
        delay(flashDelay);
      }
    break;
    case flashGreen:
      for(int x=0;x<5;x++)
      {
        pixels.setPixelColor(led2, pixels.Color(180,0,0));
        pixels.show();
        delay(flashDelay);
        pixels.setPixelColor(led2, pixels.Color(0,0,0));
        pixels.show();
        delay(flashDelay);
      }
    break;
    case green:
        pixels.setPixelColor(led2, pixels.Color(180,0,0));
        pixels.show();
    break;
    case blue:
        pixels.setPixelColor(led2, pixels.Color(0,0,180));
        pixels.show();
    break;
    case amber:
        pixels.setPixelColor(led2, pixels.Color(150,150,0));
        pixels.show();
    break;    
  }
}

Downloadable 3D parts

Main Bracket: https://a360.co/2LfCHO9

Servo Arm: https://a360.co/2vZIQcu

Plant Watering.zip

Conclusion

Keeping a plant watered is not a challenging task, although for me, it is easy to forget. There is also the problem of being away for multiple days at a time and not wanting your plant to go thirsty.  Creating an automatic indoor watering system solved both these problems and made for a fun project!  The Arduino code can be modified to work with different plant and soil types. Once the desired moisture is set, the water container needs to be filled once it gets low or when the Red light starts flashing.  Otherwise just rest easy knowing your plant will stay hydrated!

Plant update

Mfr Part # A000066
ARDUINO UNO R3 ATMEGA328P BOARD
Arduino
£20.62
View More Details
Mfr Part # 1150
PERISTALTIC PUMP W/SILICON TUBE
Adafruit Industries LLC
£18.64
View More Details
Mfr Part # IRF9540NPBF
MOSFET P-CH 100V 23A TO220AB
Infineon Technologies
Mfr Part # SER0006
SERVOMOTOR RC 4.8V
DFRobot
Mfr Part # 12999
ADDRESS LED DISCR SERIAL RGB 5PC
SparkFun Electronics
Mfr Part # GPTS203211B
SWITCH PUSHBUTTON SPST 1A 30V
CW Industries
Mfr Part # LTV-817
OPTOISOLATOR 5KV 1CH TRANS 4-DIP
Lite-On Inc.
Mfr Part # MFR-25FBF52-1K
RES 1K OHM 1% 1/4W AXIAL
YAGEO
Mfr Part # MFR-25FBF52-10K
RES 10K OHM 1% 1/4W AXIAL
YAGEO
Mfr Part # MFR-25FBF52-150R
RES 150 OHM 1% 1/4W AXIAL
YAGEO
Mfr Part # UVZ1V471MPD
CAP ALUM 470UF 20% 35V RADIAL TH
Nichicon
Mfr Part # 0398890042
TERM BLK 2P SIDE ENT 5.08MM PCB
Molex
Mfr Part # PRPC003SGAN-M71RC
CONN HEADER R/A 3POS 2.54MM
Sullins Connector Solutions
Mfr Part # BC33725TA
TRANS NPN 45V 0.8A TO-92-3
onsemi
Mfr Part # 13975
REDBOARD ATMEGA328 EVAL BRD
SparkFun Electronics
£16.81
View More Details
Mfr Part # 2488
METRO ATMEGA328 W/HDR EVAL BRD
Adafruit Industries LLC
£13.07
View More Details
Mfr Part # SEN0193
GRAVITY: ANALOG CAPACITIVE SOIL
DFRobot
Mfr Part # 16PX100MEFCTA5X11
CAP ALUM 100UF 20% 16V RADIAL TH
Rubycon
Add all DigiKey Parts to Cart
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.