← All projects
Automation Intermediate

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.

Difficulty
Intermediate
Time
2–3 h
Category
Automation

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

  1. Temporarily remove the calibrate_linear filter and watch the raw voltage in the log.
  2. Note the value in dry air and in a glass of water (submerged to the line).
  3. 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 is false, 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.