ESP32 Irrigation System With Water Reservoir

by Giovanni Aggiustatutto in Circuits > Remote Control

887 Views, 5 Favorites, 0 Comments

ESP32 Irrigation System With Water Reservoir

00_01_00.60.jpg
00_00_31.58.jpg
00_00_12.17.jpg
00_00_06.04.jpg
Costava 200€ e l'ho costruito con 30€! Irrigazione automatica SENZA RUBINETTO con ESP32

Hi, I am Giovanni Aggiustatutto and welcome to this Instructable! In this guide I will show you how I built a WiFi plant irrigation system that uses a water reservoir and a pump to distribute water to the plants. Yes, you have heard right: to run this irrigation system, you don't need to have a water faucet on your balcony, and this means that you can put the system wherever you want! The irrigation system is controlled by an ESP32 board, which gives it the ability to connect to Home Assistant in order to control it via an app on your phone or using automations. Furthermore, I added a water level sensor to the water reservoir, that enables us to check the water level remotely from the Home Assistant app, and receive a notification when we need to fill the water tank. Lastly, I put the electronics and the ESP32 board in a 3D-printed control box, which includes a button to start and stop the irrigation as well as some LEDs which display the water level in a cool VU-meter style and the status of the pump.

As always, I’ve also made a video about this project, that you can find on my YouTube channel (it has English subtitles).

Supplies

00_08_26.51.jpg
00_03_36.89.jpg
00_02_50.60.jpg
00_04_37.63.jpg

To make this project I used:

  1. 12V water pump (link here)
  2. Water tank or plastic box (at least 30 liters) with lid
  3. ESP32 Wemos D1 mini board (link here)
  4. IRFZ44N mosfet
  5. Mini560 5V step-down voltage regulator (link here)
  6. RCWL-1670 ultrasonic sensor (link here)
  7. Logic level shifter (link here)
  8. 10k Ohms, 1k Ohm, 330 Ohms, 220 Ohms resistors
  9. 1N5817 diode
  10. Momentary button (link here)
  11. 5 mm LEDs: 2 yellow, 1 red, 1 green, 1 blue
  12. 6-pin JST XH 2.54 mm PCB connector with female connector and cables (link here)
  13. 2-pin JST XH 2.54 mm PCB connector with female connector and cables (link here)
  14. two 2-pin and one 4-pin PCB terminal blocks
  15. PCB male and female jumper 2.54 mm connectors (link here)
  16. A piece of perfboard (link here)
  17. 24 AWG wire with silicone sheating
  18. Solid copper wire
  19. 4-core 24 AWG cable (link here)
  20. Two core 18 AWG cable
  21. 4 PG7 cable glands (link here)
  22. 6 M3 brass threaded inserts (link here)
  23. 4 M3 10 mm-long bolts (link here)
  24. 2 M3 25 mm-long bolts (link here)
  25. Irrigation system drippers, hoses and fittings (buy from a local hardware store)
  26. 12V power supply
  27. Connector for the power supply

Tools:

  1. Soldering iron
  2. Hot glue
  3. 3D printer with white, black, green and red PLA
  4. Screwdrivers, pliers and other basic tools

Water Pump and Reservoir

00_03_20.66.jpg
00_00_46.18.jpg
00_03_29.91.jpg
00_03_40.16.jpg

The first step for this project was to prepare the actual irrigation system. So, I took the 12V water pump and connected a plastic hose of the appropriate diameter to it. Then I installed the pump at the bottom of the water tank, and drilled two holes at the top for the water hose and for the power cable. This way, the lid is going to be able to close perfectly without anything in the way, which is very important to prevent water from evaporating and to prevent mosquitoes from going to live into the water container.

As I'm going to water two plants with this system, I took two water drippers and connected them to some 1/4" plastic hoses. Using a three-way fitting, I connected the to dripper to the hose coming from the water pump. And just like that, our watering system is ready! Of course, in my tests I only used two plants, but you can add more and expand your system as long as the pump is able to send water to all drippers.

Controlling the Pump

00_04_40.46.jpg
00_04_25.74.jpg
00_04_39.52.jpg
00_04_51.62.jpg

Now that the irrigation system is ready, we can move onto the electronics part. First, we need to address how we are going to control the pump using the ESP32. I chose this board because it can connect to WiFi, which makes it a perfect choice for smart home projects. Using the WiFi connection, we will be able to connect the ESP32 to Home Assistant, and use it to control the irrigation system from our smart phone.

The pump uses almost 400 mA at 12V, which is way more than what an ESP32 pin can deliver. So, I decided to use an IRFZ44N mosfet, that allows us to control a big load from the pins of a microcontroller like our ESP32. Around the mosfet I built a simple circuit (the schematic is below), with a 330 Ohm resistor between the ESP32 pin and the gate of the mosfet and a 10k Ohms one between the gate and GND. I connected the source of the mosfet to GND, and the drain to the negative of the pump, while the positive of the pump is connected directly to the 12V positive of the power supply. Lastly, I added a flyback diode between the negative and the positive of the pump, which is very important to prevent our circuit from being destroyed by the current produced by the pump as it slows down. I built this circuit on a piece of perfboard in order to test it, and it worked very well on the first try!

Water Level Sensor

00_08_06.64.jpg
00_06_52.46.jpg
00_07_52.30.jpg
00_08_07.32.jpg

It is at this point, however, that I had a doubt: what happens if the water in the container runs out? Knowing me I would definitely forget to check the water level to eventually refill the container, and if the water runs out the pump runs dry, which risks damaging it. So I thought of two additional functions: when the water is over the pump does not turn on, and at the same time a notification is sent to my cell phone. At the moment, however, the system does not know how much water is left in the container, so first I needed a sensor that could measure the water level. To do this I chose to use an ultrasonic sensor, which is able to measure the distance to an object. My idea is that by placing it on top of the container we can measure the distance between the sensor and the surface of the water, and consequently know how much water remains.

After testing a few different sensors, I settled on the RCWL-1670, which has two separate transducers for transmitting and receiving and, opposed to similar sensors, has waterproof transducers. To install the sensor on the lid of the water container, I drilled two holes of the appropriate size to fit the two transducers. Then I took a four-pin cable and soldered its wires to the four terminals of the sensor, that include GND, ECHO, TRIGGER and +5V. In order to protect the PCB of the sensor from rain, I 3D printed a small case to which I added a cable gland to secure the cable coming from the sensor. I then glue the sensor inside the case using hot glue. Lastly, I installed the sensor on the lid of the water container using four M3 bolts and locking nuts to secure the case of the sensor to it.

Control Box

00_08_55.12.jpg
00_08_41.34.jpg
00_09_12.74.jpg
00_09_13.84.jpg

Now that we have installed both the pump and the ultrasonic sensor, we need to build the control box that will house the ESP32. I started from its enclosure, which I designed in Fusion 360 and 3D printed using white PLA. The enclosure is composed of two parts, a box and a lid. On the lid we are going to put some LEDs and a button, and to create some labels for them I tried to do multicolor printing by pausing the print at a specific height and swapping the filament from white to black. In the end, the result was amazing, so I could install the five LEDs on the lid. I decided to use a blue LED to indicate the status of the pump, and one red, two yellow and one green LEDs to show the water level in a VU-meter style. After inserting the LEDs in the holes on the lid, I put a strip of perfboard behind them. On the perfboard, I soldered together the negatives of all LEDs. Then, to the positive of each LED and to the common negative I connected the six wires coming from a six-pin JST connector. Lastly, I installed the momentary button in its hole on the panel, and soldered a two-pin JST connector to its two terminals.

Main Circuit Board

00_09_30.28.jpg
00_10_19.10.jpg
00_10_35.34.jpg
00_00_10.29.jpg

In the control box we are going to install a circuit board with the ESP32, the mosfet that controls the pump and connectors for the power supply, the pump and the ultrasonic sensor. So I got a 70x90 mm piece of perfboard, on which I soldered the ESP32 first. Then, the board will be divided into a few sections: we have the part that deals with controlling the pump, the connection of the ultrasonic sensor, the power section and the connections of the LEDs and the button.

For the part that controls the pump, I reported the simple circuit with a mosfet, two resistors and a flyback diode that we have seen in one of the previous steps. The ultrasonic sensor uses two pins to communicate with the ESP32, for echo and trigger. To connect it to the ESP32 we wouldn't need any additional components, but the sensor runs on 5V while on the ESP32 pins we have at most 3.3V. So I added a logic level converter to the board, which can take the signals from 3.3V to 5V and vice versa. The pump needs 12V to run, so the entire system will be powered with 12V. To get the 5V needed for the ESP32 and the ultrasonic sensor, I chose to use a small 5V voltage regulator, which I soldered to the board.

To connect the 12V power supply, the pump and the ultrasonic sensor I used some screw terminals, two 2-pin ones for the power supply and the pump and a 4-pin one for the ultrasonics sensor. Due to my obsession with precision I also created a label for the connections. To connect the LEDs on the front panel as well as the button I added one 6-pin and one 2-pin JST connectors on the board. The positive of each LED will be connected to a pin of the ESP32 with a resistor in between, while the button will be connected directly between one pin of the ESP32 and GND.

Now that all the components were installed, following the wiring diagram, and with a lot of patience, I made the connections under the board. For the connections I used some stiff copper wire to make some traces and wires with silicone sheathing for the intersecting traces.

If you prefer, instead of making the circuit board by hand you can design a simple PCB for this project and have it manufactured.

Downloads

Final Assembly

00_11_30.30.jpg
00_11_28.96.jpg
00_11_37.91.jpg
00_09_22.32.jpg

Now that the circuit board was ready, I needed to finish building the control box. First, I installed three cable glands in the holes on one of the sides, that make the project look way more professional. Then, I glued the circuit board inside the enclosure using some hot glue. To close the lid of the enclosure, I put four M3 threaded inserts in the holes at the corners of the box, heating them using a soldering iron. Lastly, I connected the JST connectors coming from the LEDs and button on the front panel to the main board.

Connecting the ESP32 to Home Assistant

00_10_55.81.jpg
00_00_31.58.jpg
05_18.80.jpg

Before closing the lid of the enclosure and making the last connections, I had to work on the software for the ESP32 that will enable it to be connected to Home Assistant. After the ESP32 is configured, we will be able to control the pump from Home Assistant and check the water level remotely. Home Assistant will also allow us to create automations to activate the pump at a specific time or receive a notification when the water level is too low.

Home Assistant is a very powerful open-source platform with which we can manage all of our smart devices from a single interface. Home Assistant works in our local network, so we need a device to run it: we can use a Raspberry Pi or, like I do, an old Windows PC with Home Assistant running on a virtual machine; nowadays, Home Assistant itself sells hardware to run Home Assistant onto. To access the interface you can log into the webpage from a computer or download the Home Assistant app on your smartphone. To connect to my Home Assistant from outside the local network I'm using the Nabu Casa Cloud, which is the simplest and safest solution but it's not free. There are other free solutions but they are not totally safe, so I would recommend them only if you know really well what you are doing.

To connect the ESP32 to Home Assistant we will use ESPHome. ESPHome is an really well-made add-on that allows us to connect ESP32 and ESP8266 boards to Home Assistant via WiFi. To connect the ESP8266 to ESPHome you can follow these steps:

  1. Install the ESPHome plugin in Home Assistant
  2. On ESPHome's dashboard, click on +NEW DEVICE
  3. Give your device a name (in my case it was "Smart Watering System")
  4. Once you are prompted to install the firmware onto your device, select SKIP THIS STEP: we will install the firmware later
  5. Select ESP32 and click NEXT
  6. Copy the encryption key that is given, we will need it later
  7. Once again, skip the installation of the firmware on the ESP32
  8. Once the device appears in the ESPHome dashboard, click on EDIT to see the device's code
  9. Under wifi, insert your wifi ssid and password:
wifi:
ssid: your_wifi_ssid
password: your_wifi_password


  1. To make the connection more stable, I strongly recommend setting a static IP address for the ESP32, with this code (https://pastebin.com/ECabTkfG):
wifi:
ssid: your_wifi_ssid
password: your_wifi_password

manual_ip:
# Set this to the IP of the ESP
static_ip: 192.168.1.253
# Set this to the IP address of the router. Often ends with .1
gateway: 192.168.1.1
# The subnet of the network. 255.255.255.0 works for most home networks.
subnet: 255.255.255.0


  1. At the end of the code given by ESPHome, paste the one you can find at this link: https://pastebin.com/cEcMSsnR
switch:
- platform: gpio
pin: GPIO16
name: "Water pump"
id: water_pump
on_turn_on:
then:
- if:
condition:
- binary_sensor.is_on: water_level_ok
then:
- light.turn_on: blue_led
else:
- delay: 1s
- switch.turn_off: water_pump
on_turn_off:
then:
- if:
condition:
- binary_sensor.is_on: water_level_ok
then:
- light.turn_off: blue_led

button:
- platform: template
name: Start Watering
id: button_start
icon: "mdi:power-on"
on_press:
then:
- if:
condition:
- binary_sensor.is_on: water_level_ok
then:
- switch.turn_on: water_pump
- delay: !lambda "return id(watering_time).state*60*1000;"
- switch.turn_off: water_pump
else:
- switch.turn_off: water_pump

- platform: template
name: Stop Watering
id: button_stop
icon: "mdi:power-off"
on_press:
then:
- switch.turn_off: water_pump

number:
- platform: template
name: Full Distance
icon: mdi:arrow-collapse-vertical
entity_category: config
id: full_distance_m
min_value: 0.01
max_value: 1.50
initial_value: 0.1
optimistic: true
step: 0.01
restore_value: true
unit_of_measurement: meters
mode: box

- platform: template
name: Empty Distance
icon: mdi:arrow-expand-vertical
entity_category: config
id: empty_distance_m
min_value: 0.01
max_value: 1.50
initial_value: 0.1
optimistic: true
step: 0.01
restore_value: true
unit_of_measurement: meters
mode: box

- platform: template
name: Safe Level Distance
icon: mdi:arrow-expand-vertical
entity_category: config
id: safe_distance_m
min_value: 0.01
max_value: 1.50
initial_value: 0.1
optimistic: true
step: 0.01
restore_value: true
unit_of_measurement: meters
mode: box

- platform: template
name: Watering Time
icon: mdi:clock-edit
id: watering_time
min_value: 1
max_value: 60
initial_value: 10
optimistic: true
step: 1
restore_value: true
unit_of_measurement: minutes
mode: box

output:
- platform: gpio
pin: GPIO26
id: red_led_output
- platform: gpio
pin: GPIO23
id: yellow_led_1_output
- platform: gpio
pin: GPIO18
id: yellow_led_2_output
- platform: gpio
pin: GPIO19
id: green_led_output
- platform: gpio
pin: GPIO27
id: blue_led_output

light:
- platform: binary
name: "Red LED"
id: red_led
disabled_by_default: True
output: red_led_output
internal: False
restore_mode: RESTORE_DEFAULT_OFF
effects:
- strobe:
name: Blink
colors:
- state: true
brightness: 100%
duration: 500ms
- state: false
duration: 500ms
- platform: binary
name: "Yellow LED 1"
id: yellow_led_1
disabled_by_default: True
output: yellow_led_1_output
restore_mode: RESTORE_DEFAULT_OFF
- platform: binary
name: "Yellow LED 2"
id: yellow_led_2
disabled_by_default: True
output: yellow_led_2_output
restore_mode: RESTORE_DEFAULT_OFF
- platform: binary
name: "Green LED"
id: green_led
disabled_by_default: True
output: green_led_output
restore_mode: RESTORE_DEFAULT_OFF
- platform: binary
name: "Blue LED"
id: blue_led
disabled_by_default: True
output: blue_led_output
effects:
- strobe:
name: Double Blink
colors:
- state: true
brightness: 100%
duration: 150ms
- state: false
duration: 200ms
- state: true
brightness: 100%
duration: 150ms
- state: false
duration: 500ms

binary_sensor:
- platform: gpio
pin:
number: GPIO33
mode:
input: true
pullup: true
inverted: true
name: "Pushbutton"
id: start_button
filters:
- delayed_on: 100ms
on_press:
then:
- if:
condition:
- switch.is_off: water_pump
then:
- button.press: button_start
- delay: 1s
else:
- button.press: button_stop
- delay: 1s

- platform: template
name: "Water Level OK"
id: water_level_ok
lambda: |-
if (id(ultrasonic_sensor_distance).state < id(safe_distance_m).state) {
//there is enough water
return true;
} else {
//there is not enough water
return false;
}
on_state:
then:
- lambda: |-
if (x == false) {
id(blue_led).turn_on().set_effect("Double Blink").perform();
id(water_pump).turn_off();
} else {
//do nothing
}
on_press:
then:
- light.turn_off: blue_led
- platform: template
name: "Pump Status"
id: pump_status
device_class: running
lambda: |-
return id(water_pump).state;

sensor:
- platform: ultrasonic
trigger_pin: GPIO22
echo_pin: GPIO21
name: "Level Sensor Distance"
id: ultrasonic_sensor_distance
device_class: distance
update_interval: 1s
pulse_time: 10us
timeout: 20m
filters:
- sliding_window_moving_average:
window_size: 15
send_every: 15

- platform: template
name: "Water Level"
id: water_level
lambda: |-
return id(ultrasonic_sensor_distance).state;
update_interval: 1s
accuracy_decimals: 0
unit_of_measurement: "%"
filters:
- lambda: return ((100 - 0)/(id(full_distance_m).state - id(empty_distance_m).state)) * x + (100 - (((100 - 0)/(id(full_distance_m).state - id(empty_distance_m).state)) * id(full_distance_m).state));
- clamp:
min_value: 0
max_value: 100
ignore_out_of_range: false
on_value_range:
- below: 20
then:
- light.turn_off: green_led
- light.turn_off: yellow_led_1
- light.turn_off: yellow_led_2
- light.turn_on:
id: red_led
effect: Blink
- above: 20
then:
- light.turn_on:
id: red_led
effect: None
- below: 40
then:
- light.turn_off: green_led
- light.turn_off: yellow_led_1
- light.turn_off: yellow_led_2
- above: 40
then:
- light.turn_on:
id: red_led
effect: None
- light.turn_on: yellow_led_1
- below: 60
then:
- light.turn_off: yellow_led_2
- light.turn_off: green_led
- above: 60
then:
- light.turn_on:
id: red_led
effect: None
- light.turn_on: yellow_led_1
- light.turn_on: yellow_led_2
- below: 80
then:
- light.turn_off: green_led

- above: 80
then:
- light.turn_on:
id: red_led
effect: None
- light.turn_on: yellow_led_1
- light.turn_on: yellow_led_2
- light.turn_on: green_led


  1. In the end, your code should look like the one you can find at this link (but don't copy this code directly into the ESPHome configuration).


Note: if you have indentation problems as you past the code into ESPHome, follow the provided links to open the properly formatted and ready to be copied code.

Final Install

00_12_20.96.jpg
00_11_42.92.jpg
00_11_58.80.jpg
00_12_06.18.jpg

Now that the ESP32 was programmed everything was ready for installing the control box on the irrigation system and finally testing the circuit. First, I connected the positive and negative of the pump to the two output terminals on the board, and the four wires coming from the ultrasonic sensor to their respective terminals. To power the system I connected two wires to the power input terminals on the board, and at the other end I installed a connector for the 12V power supply. Once the connections were made I could close the front panel of the control box, and the last thing to do was to mount it on the side of the container with water. To do this I had put two threaded inserts in two holes on the back of the control unit. Using these I could secure the control box on one side of the water reservoir, using two M3 screws and two 3D-printed spacers.

Downloads

Home Assistant Setup

00_12_43.24.jpg
Screenshot 2025-06-08 alle 09.05.24.png
00_01_08.44.jpg
00_02.28.jpg

Now that the irrigation system was completed, everything was ready to test it. After I plugged in the power supply, however, I needed to configure a few more things. Going into the setup page of the device, we need to calibrate the ultrasonic sensor to correctly display the water level. Starting with the container empty, we can read the measure value in meters and input it in the "Empty Distance" field. Then we can add water up to the level that is considered safe for the pump to run at, which usually means that the pump is covered with water. Again, we can check the measured value and input it in "Safe Level Distance". Lastly, we can fill the container (leaving around 5 cm of space under the sensor and again check the measured distance, that we can input in "Full Distance". These values will be stored in the ESP32 memory, so they have to be entered only once.

Now, we can see the correct water level both on the app and on the LEDs on the control panel. The pump will not start if the water level is below the safe level, and this will be indicated by the blinking blu LED on the front panel.

The irrigation system generates a few different entities in Home Assistant:

  1. Watering time: sets the time, in minutes, for which the pump has to run each time we start it
  2. Start button: starts watering the plants (pump is turned on) and stops once the time we have set has passed
  3. Stop button: stops the pump
  4. Water pump: enables to turn the pump on or off without setting a timer for it (useful in automations)
  5. Water level: water level in percentage
  6. Level sensor distance: distance measured by the ultrasonic sensor

To manage the irrigation system I created a simple dashboard in Home Assistant.

It Works!

00_00_45.48.jpg
00_00_31.58.jpg
00_02_58.66.jpg
00_00_46.18.jpg

Now, everything was ready to test the system. I set a time of 5 minutes and pressed start on the app. The pump immediately started and water started to flow from the drippers in each plant. After 5 minutes the pump stopped, but I could have stopped it earlier with the stop button if needed. When the pump is on, the blue LED on the front panel is lit up, while it blinks when the water level is too low. The other LEDs indicate the water level, that we can also see in percentage from the app. To activate the irrigation automatically, we can create a simple automation in Home Assistant that triggers at a specific time. Of course, you could make more fancy automations that adjust the watering time based on the weather and avoid watering if it rained: as always, the limit is immagination!

Overall, I am very happy with how this project turned out, especially considering how cheap it was to build. From now on, I will have the plants on my balcony watered even when I am on vacation, while monitoring the system remotely, which I think is quite cool! To see more details about this project, watch the video on my YouTube channel. Bye!