Building a fully local soil moisture monitor for houseplants with ESPHome and Home Assistant Home Automation

Building a fully local soil moisture monitor for houseplants with ESPHome and Home Assistant

by Joule P. Kraft · July 29, 2026

As an Amazon Associate I earn from qualifying purchases. No affiliate relationship influences my recommendations.

I killed a lot of houseplants before I admitted the problem was me, not them. I would either forget to water for two weeks or, overcorrecting, drown a pot that was still damp three inches down. The finger-in-the-soil test only checks the top layer, which is exactly the part that dries out first and tells you nothing about the root zone. So I did what I do with every recurring annoyance in this house: I wired a sensor to it. This is a fully local ESPHome soil moisture monitor that reports every plant’s actual root-zone moisture straight into Home Assistant, costs a few dollars per plant, and never touches the cloud. Here is exactly how I built it.

Why not just buy a plant sensor

There are plenty of bluetooth plant monitors on Amazon, and I owned two of them before this build. The problem is always the same: they route through a vendor app, the bluetooth range is measured in feet, the battery dies in a season, and getting the data into Home Assistant means fighting a flaky bluetooth proxy or scraping a cloud API that could vanish in a firmware update. For a device whose entire job is to sit in a pot and report one number, that is an absurd amount of dependency.

An ESPHome build inverts all of that. The sensor is wired, so there is no battery to die and no bluetooth to drop. It reports over WiFi directly to Home Assistant with no app in between. The firmware is yours, the calibration is yours, and if Nabu Casa disappeared tomorrow the whole thing would keep running on your LAN. Once you have built one ESPHome sensor node, adding plant monitoring to it is almost free.

The hardware

The parts list is short and cheap, and most of it you may already have in a parts bin if you have done any ESPHome work.

  • An ESP32 dev board. I use the ESP32 over an ESP8266 here because it has several usable ADC pins and I2C, which matters when you want more than one or two plants per node. They come cheap in multi-packs.
  • One capacitive soil moisture sensor v2.0 per plant. Insist on the capacitive v2.0 boards. The resistive ones with two exposed metal prongs corrode within weeks and are a waste of money for a permanent install.
  • Optionally, an ADS1115 16-bit ADC module if you want to monitor more than three or four plants from one ESP32. It reads four analog channels over I2C with far less noise than the ESP32’s built-in ADC.
  • A can of conformal coating spray to protect the top of each sensor board from humidity.
  • A JST connector assortment kit if you want tidy, removable plugs so you can lift a plant out for repotting without desoldering anything.

The capacitive sensor is a three-pin device: VCC, ground, and an analog output that swings roughly between 1.2 volts (wet) and 3.0 volts (dry). You power it from the ESP32’s 3.3 volt rail. Powering it at 5 volts pushes the analog output above the ESP32’s 3.3 volt ADC ceiling and can damage the pin, so keep it on 3.3 volts and everything stays in range.

Wiring it up

For a small setup, wire each sensor’s output straight to an ADC-capable GPIO. On the ESP32 the ADC1 pins (GPIO32 through GPIO39) are the ones to use, because ADC2 pins conflict with WiFi and will give you garbage readings the moment the radio is active. That single detail trips up almost everyone the first time. Stick to ADC1 pins and your readings stay clean.

For my six-plant setup I wired three sensors to ADC1 GPIOs directly and the other three to an ADS1115. The ADS1115 sits on the I2C bus (SDA and SCL, plus power and ground) and gives me four extra channels with 16-bit resolution, which is dramatically steadier than the ESP32’s jittery internal ADC. If you only have two or three plants, skip the ADS1115 entirely and wire straight to the GPIOs.

The ESPHome configuration

Here is the core of the config for a single plant on an ADC1 pin. Put your real WiFi and API values in secrets.yaml and reference them with !secret so nothing sensitive lands in the YAML itself.

esphome:
  name: plant-monitor

esp32:
  board: esp32dev

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

api:
  encryption:
    key: !secret api_key

ota:
  platform: esphome

sensor:
  - platform: adc
    pin: GPIO32
    name: "Fiddle Leaf Fig Moisture"
    update_interval: 60s
    attenuation: 12db
    unit_of_measurement: "%"
    filters:
      - median:
          window_size: 7
      - calibrate_linear:
          - 3.0 -> 0.0
          - 1.2 -> 100.0
      - lambda: |-
          if (x < 0) return 0;
          if (x > 100) return 100;
          return x;

A few things are doing real work here. The median filter with a seven-sample window throws out the electrical spikes the bare ADC loves to produce, so your reading does not jump around by ten points between updates. The calibrate_linear block is where your two-point calibration lives: 3.0 volts maps to 0 percent (dry) and 1.2 volts maps to 100 percent (saturated). The closing lambda just clamps the output to a clean 0 to 100 range so a slightly out-of-range voltage does not print a moisture of 104 percent.

For the ADS1115 channels the pattern is nearly identical, except you declare the ads1115 hub once and then use the ads1115 sensor platform with a multiplexer line instead of a raw pin. Everything downstream, the filters and calibration, stays the same.

Calibrating each probe

Calibration is the step people skip, and it is the step that decides whether your numbers mean anything. Every sensor and every soil mix reads slightly differently, so a shared calibration will lie to you. It takes two minutes per probe.

First, get the dry reading. Hold the probe in open air or push it into completely dry soil and note the voltage ESPHome reports before you apply the calibrate_linear filter. Comment the filter out temporarily so you see raw volts. That number, usually right around 3.0 volts, is your 0 percent anchor.

Second, get the wet reading. Stand the probe in a glass of water up to (but not past) the marked line, or push it into freshly saturated soil, and note the voltage. That is typically near 1.2 volts and becomes your 100 percent anchor.

Plug those two exact numbers into the calibrate_linear block, re-enable the filter, and reflash. Now the percentage tracks real moisture in your actual pots. Do this once per probe and you never think about it again.

Sealing the boards so they survive

The single most common failure with these cheap capacitive sensors is corrosion at the top of the board, where the header pins and the little voltage regulator sit. The capacitive sensing area is fine buried in soil, but the electronics up top hate constant humidity.

The fix is thirty seconds of work. Mask off the sensing zone below the printed waterline, then hit the top third of the board, the pins, and the solder joints with a couple of light coats of conformal coating spray. If you do not have coating, a generous blob of hot glue over the header and regulator works nearly as well. Leave the sensing area completely bare. Coated up top and bare down low, these boards last for years instead of months.

The Home Assistant automation

Raw moisture numbers are nice on a dashboard, but the payoff is automation. In Home Assistant I keep it simple: each plant gets a threshold, and when moisture drops below it for a sustained period, I get a notification naming the exact plant.

automation:
  - alias: "Plant needs water"
    trigger:
      - platform: numeric_state
        entity_id: sensor.fiddle_leaf_fig_moisture
        below: 25
        for:
          hours: 2
    action:
      - service: notify.mobile_app_phone
        data:
          title: "Thirsty plant"
          message: "The fiddle leaf fig is at low moisture. Time to water."

The for: hours: 2 is important. Without it, a single noisy dip below the threshold would fire a false alert the moment you brushed the pot. Requiring the reading to stay low for two hours means you only get pinged when the soil is genuinely dry, not when a stray sample twitched. Set the below value per plant based on what each species actually likes: I water the fiddle leaf fig around 25 percent but let a snake plant coast down to 10 percent before it cares.

The thresholds are the fun part to tune. After a couple of weeks of watching the graphs in Home Assistant, you learn each plant’s real drydown curve, and you can set thresholds that match how the plant behaves rather than a generic number. A pothos that drinks fast gets a higher trigger; a succulent that sulks in wet soil gets a low one.

What this changed in practice

I have not lost a plant since I built this, which after years of steady casualties feels close to miraculous. More than that, it changed how I think about watering. I stopped watering on a schedule and started watering on data. Some plants that I used to hit every Sunday turned out to need water twice a week in summer and barely once every ten days in winter. The graphs made the seasonal shift obvious in a way my calendar never did.

The dashboard is genuinely useful too. A single Home Assistant view with a gauge card per plant, color coded green to red, tells me at a glance which pots are fine and which are creeping toward the threshold. It takes the guesswork out entirely, and because it is all local, it loads instantly and works even when the internet is down.

The Bottom Line

For the price of a few capacitive sensors and an ESP32 you probably already own, you can turn plant care from a guessing game into a solved problem, with every reading living on your own hardware and nothing depending on a vendor app. Buy the capacitive v2.0 boards, never the resistive ones. Power them at 3.3 volts and wire only to ADC1 pins. Calibrate each probe with a two-point dry-and-wet reading so the percentages mean something. Seal the tops with conformal coating so they survive the humidity. And add an ADS1115 the moment you outgrow three or four plants.

Do those five things and you get a monitor that outlasts and out-integrates anything you can buy, for a fraction of the cost. My plants are healthier, my watering is finally rational, and I have not scraped a dead fern out of a pot in over a year. That is a good return on a weekend and twenty dollars of parts.

Frequently Asked Questions

Why use a capacitive soil moisture sensor instead of the cheap resistive ones?+
Resistive probes pass current through the soil, which corrodes the exposed traces within weeks and gives you drifting, eventually useless readings. Capacitive sensors measure the dielectric of the soil without any exposed metal in contact with the moisture, so they last for years. The v2.0 capacitive boards cost barely more than the resistive ones and are the only sensible choice for a permanent install.
Does this work without any cloud service or app?+
Yes, entirely. ESPHome runs the firmware on the ESP32 locally, talks straight to Home Assistant over your LAN, and every automation and alert runs on your own hardware. No vendor account, nothing leaves your network, and it keeps working if your internet drops. That is the whole reason to build it instead of buying a bluetooth plant sensor that phones home.
How many plants can one ESP32 monitor?+
An ESP32 has multiple ADC-capable pins, so you can wire three or four sensors directly. Past that, the built-in ADC gets noisy and pins get scarce, so add an ADS1115 16-bit ADC over I2C to cleanly read four more channels per module. I run six plants off one ESP32 plus one ADS1115 and it is rock solid.
How do I calibrate the sensor for my specific soil?+
Take two readings: one with the probe in bone-dry soil (or dangling in air) and one with it in a glass of water or fully saturated soil. Those become your 0 percent and 100 percent anchor points in ESPHome's calibrate_linear filter. Every soil type and sensor is slightly different, so calibrate each probe once and you get meaningful percentages instead of raw voltage.
Will the sensor survive being buried in wet soil for months?+
The sensing element survives fine because it is capacitive, but the exposed header pins and the little onboard regulator at the top of the board can corrode from humidity. Coat everything above the marked waterline with conformal coating or hot glue before you bury it, leave the sensing zone bare, and it will last for years.