Arduino Controlled Automatic Indoor Plant Watering System
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!
Parts Needed
1x 1050-1024-ND, 1568-1768-ND, or 1528-1214-ND -Arduino Uno or Similar
1x 1528-1404-ND -Peristaltic Pump
1x 1738-1184-ND -Moisture sensor
2x BC33725TACT-ND -Transistors
1x IRF9540NPBF-ND -Mosfet
1x 1738-1385-ND -Servo
2x 1568-1214-ND -LEDs
2x CW181-ND -Pushbutton Switches
1x 160-1366-5-ND -Optoisolator
2x 1.00KXBK-ND -1k Ohm Resistors
1x 10.0KXBK-ND -10k Ohm Resistor
1x 150XBK-ND -150 Ohm Resistor
1x 493-1321-ND -470uF Capacitor
1x 493-13439-1-ND -100uF Capacitor
1x WM13976-ND -2 Position Connector
2x S1311EC-03-ND -3 Position Right Angle Male Header
12V power supply w/barrel connector for Arduino Uno
Water bottle - Really anything to hold water and insert the silicone tube from the pump will work.
Breadboard, Perfboard, or Custom PCB
Code
Both the Adafruit NeoPixel library and the Servo library must be included at the top of the Arduino Sketch.
/* 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
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
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!