The best tools to make your project dreams come true

Login or Signup
USD


By Cody Walseth

Arduino Controlled Automatic Indoor Plant Watering System

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 Digi-Key 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

Key Parts and Components

Add all Digi-Key Parts to Cart
  • 1050-1024-ND
  • 1528-1404-ND
  • IRF9540NPBF-ND
  • 1738-1385-ND
  • 1568-1214-ND
  • CW181-ND
  • 160-1366-5-ND
  • 1.00KXBK-ND
  • 10.0KXBK-ND
  • 150XBK-ND
  • 493-1321-ND
  • 493-13439-1-ND
  • WM13976-ND
  • S1311EC-03-ND
  • BC33725TACT-ND