Introduction: Build a Self Watering System (Soil Moisture Sensor - Water Pump - Water Level Sensor - MOSFET - Circuit - Code)

About: Maker 101; Beginner and intermediate level Maker projects! You can find projects such as "How to" and "DIY" on programmable boards such as Arduino, ESP8266, ESP32 and Raspberry Pi on this channel. The projects…

This project shows how to build a self-watering system whose values can be monitored through an app. The project includes both a breadboard circuit and a printed circuit board design. The project measures both the water level sensor and the soil moisture sensor value and runs the system within the specified time interval, and the self-watering function is executed.

Step 1: Breadboard Circuit

Although the breadboard circuit of the project looks complicated you can build the circuit by following the shared circuit diagram, and you can test that all components work correctly. The circuit uses a Neopixel LED for ultraviolet light, a water level measurement sensor, a soil moisture measurement sensor, a MOSFET that acts as a switch to activate the motor, and of course the water pump motor.

  • Wemos D1 Mini (ESP8266) x1
  • 16V 100uF Capacitors x2
  • Soil Moisture Sensor x1
  • Water Level Sensor x1
  • Water Pump Motor DC 3.3V - 5V x1
  • 330ohm Resistor x1
  • 10K Resistor x1
  • IRFZ44N Mosfet x 1
  • 5V 1A Power Supply
  • Jumper Wires
  • Breadboard

An important warning, leave the power connection of the water pump motor wire before connecting the circuit to your computer to upload the source code.

Step 2: Source Code

In the source code part, the necessary libraries need to be installed and added first. Then, the "Blynk" application was preferred to instantly view the sensor data on a dashboard. The unique "Authtoken" address generated by the application is added to the source code, and then the network information with internet access must be entered.

#include <ESP8266WiFi.h>
#include <FastLED.h>
#include <BlynkSimpleEsp8266.h>

#define BLYNK_PRINT Serial
#define BLYNK_AUTH_TOKEN "YOUR_TOKEN"

char ssid[] = "YOUR_SSID";
char pass[] = "PASSWORD";

Remember that the pin numbers to which the components are connected must be defined as GPIO. Also, the features of the Neopixel LED should be added. The power pins are controlled via the source code to prevent continuous power consumption of the sensors and for their long-term use, and the water pump is turned on/off via the MOSFET pin.

#define LED_PIN     15  // D8 pin - Wemos D1 Mini
#define LED_COUNT 12 // Number of LEDs in your NeoPixel ring
#define BRIGHTNESS 60 // LED brightness (0-255 range)

CRGB leds[LED_COUNT];

// Sensor pins
#define WATER_PWR 14 // D5 pin - Wemos D1 Mini
#define WATER_PIN A0 // A0 pin
#define MOISTURE_PWR 4 // D2 pin
#define MOISTURE_PIN 5 // D1 pin
#define WATER_PUMP 12 // D6 pin

Define how long the water pump motor will be active, currently set to 3 seconds. Also, the threshold value should be defined according to the values received at the moment of high and low signals of the water level sensor.

/* Define how long the pump will run (3 seconds) */
#define PUMP_TIME 3000

/* Change these values based on your calibration values */
#define LVL_HIGH 520 // Define max value water level
#define LVL_LOW 470 // Define min value of water level

A time interval value for sensor measurements is defined, for now, it is set to 10 seconds. Then a variable was created to initialize and reset the defined time interval.

/* Define the time range for sensor measurements */
const int measurementInterval = 10000;
/* Time variable to keep track of the time range */
unsigned long previousMillis = 0;

In the "Setup" section, the basic Wi-Fi and "Blynk" settings are defined, as well as the output and input modes of the sensor pins. At the beginning of the circuit, all power pins were defined as low signals, and the basic definitions required for the Neopixel LED were specified.

void setup() {
Serial.begin(115200);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Wi-Fi connected: ");
Serial.println(WiFi.localIP());

Blynk.config(BLYNK_AUTH_TOKEN);

pinMode(WATER_PWR, OUTPUT);
pinMode(WATER_PIN, INPUT);
pinMode(MOISTURE_PWR, OUTPUT);
pinMode(MOISTURE_PIN, INPUT);
pinMode(WATER_PUMP, OUTPUT);

/* Initially keep the sensors and motor OFF */
digitalWrite(WATER_PWR, LOW);
digitalWrite(MOISTURE_PWR, LOW);
digitalWrite(WATER_PUMP, LOW);

FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, LED_COUNT);
FastLED.setBrightness(BRIGHTNESS);
FastLED.show();
FastLED.clear();
}

In the "Loop" section, the "Blynk" application and the LED function are executed first. Then, the defined time interval period is calculated and started to receive the sensor values and run the functions. From the functions, the soil moisture sensor value is read first, and if the soil moisture value is high, the circuit is on standby and the current status of the circuit is printed on the "Blynk" dashboard. In the Blynk application, these values are defined as "String" for the moisture sensor and pump and as "Integer" for the water level sensor.

void loop() {
// Run the Blynk app.
Blynk.run();
// Run the LED function
ultravioletEffect();

/* Get current time. If the defined time range
has not passed, terminate the loop */
unsigned long currentMillis = millis();
if (currentMillis - previousMillis < measurementInterval) {
return;
}
/* If the defined time range is complete, update the time */
previousMillis = currentMillis;

/* Get the value from the soil moisture sensor function
when the defined time range is complete */
int MOISTURE_LEVEL = readMoisture();

/* If the soil moisture is HIGH, report everything as perfect! */
/* The sensor value works inversely! */
if (MOISTURE_LEVEL == LOW) {
Serial.println("Moisture is perfect");
Blynk.virtualWrite(V1, "Moisture is perfect");
Blynk.virtualWrite(V3, "The water pump is on standby!");

If the soil moisture value is low, the "Blynk" dashboard values are updated, and the water pump motor is activated for a defined time interval, after which the motor goes into standby mode again until the next sensor measurement.

/* If the soil moisture value is LOW, report it and 
run the water pump motor for the defined pump time,
then stop the pump */
} else {
Serial.println("Low moisture! Time to water!");
Blynk.virtualWrite(V1, "Low moisture! Time to water!");
Blynk.virtualWrite(V3, "Water pump started!");
digitalWrite(WATER_PUMP, HIGH);
Serial.print("Water pump started!");
delay(PUMP_TIME);
digitalWrite(WATER_PUMP, LOW);
Serial.print("The water pump is on standby!");
}

After the water pump function is executed, the water level sensor values are read and the current water level value is sent to the "Blynk" dashboard. The water level values are read in analog converted to percentage format and displayed.

/* Get the value from the water level sensor function 
when the defined time range is complete */
int WATER_LEVEL = readWaterLevel();
/* Convert the received water level value to the percent value */
float percentLevel = map(WATER_LEVEL, 300, 600, 0, 100);
Serial.print("Water Level: ");
Serial.print(percentLevel);
Serial.println(" %");
/* Report and print the received water level status */
if (WATER_LEVEL > LVL_HIGH) {
Serial.println("Water level is too");
} else if (WATER_LEVEL >= LVL_LOW && WATER_LEVEL < LVL_HIGH) {
Serial.println("Water level is perfect");
} else {
Serial.println("Water level is low! Time to add water!");
}
Blynk.virtualWrite(V2, percentLevel);

/* It is the last line of the Loop Function,
the Loop Function is executed from the beginning... */
Serial.println();
}

Finally, the loop is terminated and restarted. Also, the functions of the sensors and LED are located outside the "Loop" section.

/* This function returns the analog water level measurement */
int readWaterLevel(){
digitalWrite(WATER_PWR, HIGH); // Turn the sensor ON
delay(10); // Allow power to settle
int sensorValue = analogRead(WATER_PIN); // Read the analog value from sensor
digitalWrite(WATER_PWR, LOW); // Turn the sensor OFF
return sensorValue; // Return analog water value
}

/* This function returns the digital soil moisture measurement */
int readMoisture(){
digitalWrite(MOISTURE_PWR, HIGH); // Turn the sensor ON
delay(10); // Allow power to settle
int sensorValue = digitalRead(MOISTURE_PIN); // Read the digital value from sensor
digitalWrite(MOISTURE_PWR, LOW); // Turn the sensor OFF
return sensorValue; // Return digital moisture value
}

/* Ultraviolet color function (You can adjust the values here to get the desired color) */
void ultravioletEffect() {
CRGB uvColor(138, 43, 226);
// Turn on all LEDs with the ultraviolet color
fill_solid(leds, LED_COUNT, uvColor);
FastLED.show();
}

Step 3: Breadboard Circuit Test

After uploading the code, make the water pump motor power connection, then supply 5-6 volts of external power to the circuit. The sensor data is read every 10 seconds, and the specified functions are executed. As you can see the circuit is working perfectly... Next, we will take a look at the printed circuit board design.

Step 4: Printed Circuit Board

The Altium Designer system was preferred for printed circuit board design. Altium Designer offers engineers a unified environment that provides a comprehensive view of the entire PCB design process. This includes schematic, PCB layout, harness design, and documentation, empowering engineers to access all necessary tools in one place.

Get a free trial of Altium Designer with 365 the world’s most trusted PCB design software, and 25% off your purchase https://www.altium.com/yt/maker101io

A more detailed video will be shared for the full design process of the PCB for the project. For now, the highlights are that a Wemos D1 mini library was created first. Then the Altium Designer component library and Octopart, a free component search engine, were used to find and include the necessary electronic parts and components for the circuit. Octopart provides part documentation, including datasheets & CAD models. Once all components have been added to the circuit diagram, the circuit connection phase begins.

Altium Designer has a friendly interface and provides users with many useful tools. After the circuit diagram design, the PCB design process begins. The one-touch 2D and 3D model viewing switch in Altium Designer is a great convenience during the design process. After the components were placed on the board, PCB routes were created easily and quickly using Auto Route, another very useful tool of Altium Designer. The PCB routes took about 3 seconds to complete and the board is ready!

In the meantime, if you need support during the design process and teamwork is required, Altium 365 is the best solution. You have full control over project sharing and management with centrally stored design data, configurable access rights, and roles. You get instant notifications about comments and design updates. You save time reviewing your electronic designs from any browser, on any device.

Introducing Altium 365, the world’s only cloud platform for printed circuit board design and realization. Try Altium 365 for FREE! https://www.altium.com/altium-365

Step 5: Ordering and Soldering the PCB

After exporting the PCB files with a single click, the ordering process begins! PCB printing quality is as important as board design, I prefer PCBWay, which offers an affordable price and high-quality PCB printing service. All you need to do is upload the PCB files you created and complete the order. In addition, PCBWay meets many needs with its wide range of services.

Cheap & Quick PCB, 3D Printing, CNC machining, and fabrication services from PCBWay https://pcbway.com/g/v8fQIG

Once the printed circuit board is delivered, the necessary components are prepared and the soldering process begins. The components are placed on the board according to the shared reference designator and then fixed with a soldering iron and soldering wire.

  • C1, C2 - 16V 100uF Capacitor
  • U2, U3 - 5.0-2P Connector for Power and Pump
  • U4, U5, U6 - 5.0-3P Connector for Sensors and NeoPixel
  • U1 - 2.54-1*16P Female Header for Wemos D1 Mini
  • H1 - 2.54-1*4P Male Header for External Pins
  • H2 - 2.54-1*6P Male Header for External Pins
  • R1 - 330ohm Resistor for NeoPixel Digital Pinout
  • R2 - 10K Resistor for MOSFET
  • MOSFET1 - TO-220-3 IRFZ44N MOSFET

Schematic & Gerber Files: https://www.pcbway.com/project/shareproject/Build_a_Self_Watering_System

Step 6: Printed Circuit Board Test

The printed circuit board is ready... In this section, the connections of the sensors and the motor are made and the board is tested to see if it runs smoothly. The source code is already installed and the test is started by powering the circuit. As can be seen, the circuit is working smoothly, the next step is to assemble the 3D parts.

Step 7: Assembly of 3D Parts

3D parts were designed by Skulbl4k4 and shared on Thingiverse. The parts have been revised for this project, the revised parts are shared below. You can get the original parts from this link. The parts in the project were scaled down to 80% and printed at the lowest quality. The nozzle part was reduced to 70%, actually, the nozzle size depends on the inner diameter size of the pump hose used. By the way, you don't have to use a nozzle, it is completely optional. The assembly of the 3D parts is shown step by step. 

Step 8: End of Project

With the assembly of the 3D parts completed, we came to the end of another project. At the end of the project, we added some plant soil, water, and of course plant seeds or seedlings. The self-watering system continued to work successfully and the current data was displayed on the dashboard. If you have any questions about the project or have any ideas about the project, please let me know in the comments section. Thank you for reading the project.