Automatic Plant Watering with ESPHome and Home Assistant
A capacitive soil moisture sensor plus a relay-driven pump. All logic lives in an ESPHome YAML config: threshold watering, overflow protection and a threshold you can tweak from Home Assistant.
The concept
Instead of writing C++, we describe the device in YAML and ESPHome — an open-source firmware framework for ESP32/ESP8266 — generates and flashes the firmware. The config below is put together for this specific project: sensor, pump, threshold and watering logic. You get:
- soil moisture in % showing up in Home Assistant;
- a manual watering button;
- a moisture threshold slider right in HA;
- every 30 minutes the board checks the soil and, if it's dry, runs the pump for exactly 10 seconds.
Parts
- ESP32 DevKit
- Capacitive soil moisture sensor v1.2 (resistive ones corrode quickly)
- 5 V relay module or a MOSFET module (IRLZ44N/AO3400)
- 3–5 V submersible mini pump + silicone tubing
- Separate supply for the pump (5 V ≥ 1 A)
Wiring
| Part | ESP32 |
|---|---|
| Sensor VCC | 3V3 |
| Sensor GND | GND |
| Sensor AOUT | GPIO 34 (ADC1) |
| Relay IN | GPIO 26 |
| Relay VCC / GND | 5V (VIN) / GND |
The pump is powered through the relay contacts (COM/NO) from the separate 5 V supply.
Only use ADC1 pins (GPIO 32–39) — ADC2 doesn't work while Wi-Fi is on.
Installing ESPHome
Easiest is the ESPHome Device Builder add-on in Home Assistant. Or locally:
pip install esphome
esphome wizard plant-waterer.yaml
Configuration
esphome:
name: plant-waterer
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
ota:
- platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
sensor:
- platform: adc
pin: GPIO34
id: soil
name: "Soil moisture"
attenuation: 12db
update_interval: 60s
unit_of_measurement: "%"
accuracy_decimals: 0
filters:
- median:
window_size: 5
send_every: 1
# Calibration: voltage in dry air → 0 %, in a glass of water → 100 %
- calibrate_linear:
- 2.80 -> 0.0
- 1.20 -> 100.0
- clamp:
min_value: 0
max_value: 100
switch:
- platform: gpio
pin: GPIO26
id: pump
name: "Water pump"
restore_mode: ALWAYS_OFF
# inverted: true # uncomment if your relay is active-low
on_turn_on:
- delay: 10s
- switch.turn_off: pump
number:
- platform: template
id: threshold
name: "Moisture threshold"
optimistic: true
min_value: 10
max_value: 80
step: 1
initial_value: 35
restore_value: true
unit_of_measurement: "%"
interval:
- interval: 30min
then:
- if:
condition:
lambda: 'return id(soil).state < id(threshold).state;'
then:
- logger.log: "Soil is dry, watering"
- switch.turn_on: pump
Flash it:
esphome run plant-waterer.yaml
The first time over USB; after that updates go over Wi-Fi (OTA).
Calibrating the sensor
- Temporarily remove the
calibrate_linearfilter and watch the raw voltage in the log. - Note the value in dry air and in a glass of water (submerged to the line).
- Put both numbers into
calibrate_linear. Typical values are ≈2.8 V and ≈1.2 V.
Safety
- The pump can't run longer than 10 s at a time — even when switched on manually.
- If the sensor disconnects and reports
NaN, the comparison isfalse, so no watering happens. - Checking only every 30 minutes gives water time to soak in, making overwatering unlikely.
Reference
Every option is described in the official ESPHome documentation: the ADC sensor, GPIO switch and Template number components, plus the automations guide.
Ideas
- A water tank level float switch on a GPIO + an HA "refill the tank" notification.
- Multiple pots: more sensors on GPIO 32/33/35 and more pumps.
- Solar power and deep sleep.