知識がなくても始められる、AIと共にある豊かな毎日。
AI Coding

Reading a Soil Moisture Sensor with ESP32 and ESPHome: Calibration and the Limits of Waterproofing

swiftwand

Bring pots indoors and the watering interval changes from what it was outside. Touching the soil works, but as pots multiply the daily rounds become a chore. Plenty of build guides connect a soil moisture sensor to an ESP32 and send the value to Home Assistant with ESPHome. At Japanese retail prices the sensor and board cost 2,442 yen including tax (about 16 US dollars at 150 yen per dollar): the DFRobot SEN0193 at Akizuki Denshi for 1,320 yen and the Seeed XIAO ESP32C3 at Switch Science for 1,122 yen. The configuration is a dozen or so lines of YAML. But the first thing you see after assembly is a voltage in V, or a percentage with no stated reference. Whether that number means dry or wet is unknowable until you calibrate each sensor individually.

This article contains affiliate links. As an Amazon Associate, we earn from qualifying purchases.

This article goes through the places where it is easy to go wrong when using a soil moisture sensor with an ESP32 and ESPHome: resistive versus capacitive sensors, ADC pins and attenuation, two-point calibration with calibrate_linear, filters, deep sleep for battery power, 3D printed cases and the limits of waterproofing, and defective units among the generic boards printed “v1.2.” It also covers how to use an AI assistant to write the YAML and which parts of its output to verify in the official ESPHome documentation.

One caveat up front: we did not build this setup. The procedures and settings are based on official documentation from ESPHome, Espressif and the sensor makers, plus third-party build guides. Each section links to its sources, so please confirm values and behavior there as well. We assume ESPHome 2026.9.0, the latest stable release as of 2026-09-28 (released 2026-09-16).

忍者AdMax

Before you start: resistive versus capacitive sensors

Cheap soil moisture sensors come in two main types, resistive and capacitive. Both look like a long board you push into the soil, but they measure differently and last differently.

A resistive sensor pushes current between two metal electrodes in the soil and measures resistance. Wetter soil conducts more easily. It is simple and cheap. The problem is that the electrodes are exposed. Adafruit explains that such sensors work at first but the exposed metal oxidizes over time, even when gold plated. As it corrodes, readings keep drifting and you have to recalibrate. SparkFun also recommends not powering the sensor continuously to slow electrode corrosion.

A capacitive sensor seals its electrodes inside the board and reads changes in the dielectric properties of the surrounding soil as changes in capacitance. As Adafruit notes, the electrodes are not exposed and no direct current passes through the plant. The Switch Science product page says that unlike resistive sensors the electrodes do not corrode, which improves durability for long outdoor use. DFRobot claims on its product page that its SEN0193 lasts two to three times longer than resistive sensors. That is the maker’s own claim, not an independent test result, so read it with some discount.

If a sensor will stay in an indoor pot for months, choose capacitive. Everything below assumes a capacitive sensor.

Why you cannot trust the raw number

The SEN0193 and the generic v1.2 boards covered here output an analog voltage read by the ESP32 ADC (analog to digital converter). Some capacitive sensors, such as the Adafruit STEMMA Soil Sensor, return a value over I²C instead, in which case the ADC settings below do not apply. There are three traps.

First, the direction is counterintuitive. Most capacitive sensors output a lower voltage as moisture increases: high voltage when dry, low when wet. The esp32.co.uk guide says to map it in reverse for that reason, while adding that this direction is common but not universal and should be confirmed with your own sensor. If you assume higher voltage means wetter, the reading will drop every time you water.

Second, the output range is not fixed. DFRobot gives different output voltage ranges for the SEN0193 on its wiki and its product page. Generic boards are even less predictable. Treat the upper and lower limits as depending on the unit and the supply voltage, and determine them by measuring the one you have.

Third, the ESP32 ADC cannot read the full sensor output with default settings. According to the ESPHome ADC documentation, the ESP32 default attenuation is 0db, which saturates at about 1.1 V. Capacitive sensor output can go higher, so without changing attenuation the dry reading sticks near 1.1 V.

You also need to decide what the calibrated percentage means. It is a relative value showing where you are between the dry and wet calibration points, not volumetric water content. The same voltage can mean different actual moisture depending on soil type, salts and compaction, as esp32.co.uk also notes. Whenever this article says percent, it means that relative value.

Splitting the work when an AI writes your ESPHome YAML

ESPHome configuration is YAML, so if you tell a chat assistant “I connected a capacitive soil moisture sensor to GPIO32 on an ESP32; write the ESPHome config,” you get something plausible right away. As a starting template that is genuinely useful. You can also ask it to explain how filters are ordered, or paste your own config and ask it to point out mistakes.

Before flashing the YAML it returns, though, check at least these four points against the official ESPHome documentation.

  • Attenuation notation. In the ESPHome source, attenuation: 11db is deprecated and triggers a warning to use 12db. Third-party guides still use the old 11db; for example the Home Hacker Lab ESP32-C3 guide does. Since published articles still carry the old notation, AI output may too.
  • Pin choice. Did it assign an ADC2 pin in a Wi-Fi setup? ADC1 pin numbers differ by chip (covered in the next section).
  • Option combinations. Do not combine attenuation: auto with samples (multiple readings per update). The ESPHome docs say samples is ignored with auto, while the source (adc/sensor.py) rejects multiple samples at config validation.
  • Calibration direction. Does calibrate_linear map the dry voltage to 0% and the wet voltage to 100%?

The check itself is simple: search the relevant ESPHome page for each key in the generated YAML and confirm the same name and value format exist. Do not flash keys you cannot find there. As for calibration numbers, the AI does not know your sensor, so any voltages it gives are placeholders. Using them as is amounts to not calibrating.

The same applies when using the AI as a reviewer: do not accept “this config is correct” until you have checked the four points against the docs yourself. Use the AI for templates and catching oversights, and the documentation for deciding what is right.

Choosing an ESP32 board and an ADC1 pin

For reading one or two sensors and sending them over Wi-Fi, any ESP32 variant will do. The pin choice is what matters; get it wrong and readings fail intermittently or entirely.

ESP32 chips have ADC1 and ADC2. According to the ESP-IDF documentation, on the original ESP32 and the ESP32-S3 Wi-Fi also uses ADC2. The driver arbitrates between them, but reads can fail while ADC2 is in use by another driver. On the ESP32-C3 it is stricter: oneshot reads on ADC2 are not supported by default because hardware limitations make the results unreliable. ESPHome nodes normally connect over Wi-Fi, so connect the sensor to an ADC1 pin.

ADC1 pins listed in the ESPHome documentation:

ChipADC1 pinsADC2 pins (avoid)
ESP32 (original)GPIO32–GPIO39GPIO0, 2, 4, 12–15, 25–27
ESP32-C3GPIO0–GPIO4GPIO5
ESP32-S3GPIO1–GPIO10GPIO11–GPIO20

Pin labels printed on development boards (D0, A1 and so on) often do not match GPIO numbers. Check the board pinout for the GPIO number before writing YAML. Among third-party guides, SmartHomeScene uses GPIO32 on an ESP32-WROOM, Home Hacker Lab uses GPIO3 on an ESP32-C3 and esp32.co.uk uses GPIO34, all within ADC1.

For boards, the Seeed Studio XIAO ESP32C3 and XIAO ESP32S3 are small and easy to fit in a case. Note that the product names drop the hyphen (ESP32C3) while chip names keep it (ESP32-C3). The ESP32-C3 is a RISC-V chip, and the ESP32-S3 is a dual-core Xtensa LX7 running at 240 MHz. Reading soil moisture does not need the extra processing power, so choose by the number of usable ADC1 pins and whether it fits your case.

On power, the DFRobot SEN0193 runs at 3.3–5.5 V and has an onboard regulator, so it connects directly to 3.3 V microcontrollers. Operating current is 5 mA.

The XIAO ESP32C3 mentioned above is a compact board that fits small cases. On this board only A0 to A2 are ADC1, so it can read up to three sensors reliably.

Configuring the ESPHome adc sensor: 12db versus auto

Build the base of the config (Wi-Fi, the Home Assistant API, OTA updates) following the ESPHome setup steps, then add the sensor below it. Start without calibration, with a config that shows the raw voltage.

sensor:
  - platform: adc
    pin: GPIO32
    name: "Soil Moisture Voltage"
    id: soil_voltage
    attenuation: 12db
    update_interval: 5s
    unit_of_measurement: "V"
    accuracy_decimals: 3

attenuation is available only on the ESP32 (ADC docs) and accepts 0db, 2.5db, 6db, 12db or auto. The default is 0db, which saturates at about 1.1 V as noted above. To measure a capacitive sensor, specifying 12db is the straightforward choice.

auto combines all ranges; the documentation says ESPHome testing showed a usable range of about 0.075 V to 3.12 V. That is from ESPHome testing and varies between chips. With auto, samples (how many reads per update) is not available, so if you want to average several reads to reduce noise, specify 12db and use samples.

The default update_interval is 60s, but during calibration you want to see changes quickly, so the example uses 5s. Set it back when you finish. Since version 2021.11 ESPHome reads voltage using the factory calibration stored in the chip, so the value shown is already calibrated. To see the raw ADC value, use raw: true.

Flash this and if the voltage appears in Home Assistant or the ESPHome logs, the wiring and pin are correct. If the value never moves from about 1.1 V, attenuation is not taking effect. If readings come and go, suspect an ADC2 pin.

Two-point calibration with calibrate_linear

Once the raw voltage is readable, take two points. The DFRobot wiki describes calibrating with the value in air and the value submerged in water. You take two reference points and connect them with a straight line.

For a pot, you can also measure in the soil you will actually use. The following steps combine the official material and build guides cited above.

  1. Finish waterproofing the sensor first (see below). Coatings and potting change the capacitance, so esp32.co.uk recommends calibrating after the final waterproofing.
  2. Take the dry point. Hold the sensor in air or push it into dry soil up to the limit line, wait for the voltage to settle and record several readings.
  3. Take the wet point. Submerge it in water or push it into the same soil fully soaked, and record the same way.
  4. If you have several sensors, take two points for each one separately. Do not reuse values.

Whether the dry point is air or dry soil changes what 0% means. With air as the reference, even very dry soil rarely drops to 0%. Either choice is fine, but record which you used or the display will be meaningless later.

Then put the values into calibrate_linear. The filter documentation lists each point as “measured -> true” and requires at least two points. Capacitive sensors read higher voltage when dry, so map the high voltage to 0% and the low voltage to 100%.

sensor:
  - platform: adc
    pin: GPIO32
    name: "Soil Moisture"
    attenuation: 12db
    update_interval: 60s
    unit_of_measurement: "%"
    device_class: ""
    accuracy_decimals: 0
    filters:
      - median:
          window_size: 5
          send_every: 5
          send_first_at: 1
      - calibrate_linear:
          method: least_squares
          datapoints:
            - 2.62 -> 0.0
            - 1.38 -> 100.0
      - clamp:
          min_value: 0
          max_value: 100

Do not drop the device_class: “” line. The ESPHome adc sensor defaults to the voltage device class (unit V), per the source. The Home Assistant voltage device class accepts units such as V and mV, not %. Changing only the unit to % leaves a voltage-class sensor claiming to be a percentage. The ESPHome docs say setting device_class to “” removes the default class. We remove it here because the percentage is a relative value between two points. Home Assistant also has a moisture class with % as its unit, so you may set device_class: moisture for display consistency; the value is still relative, not volumetric water content.

The 2.62 V and 1.38 V in the example are the calibration values from the esp32.co.uk article, used as is. In that article, 2.62 V is the dryness at which you would want to start watering and 1.38 V is the soil after thorough watering and drainage, not air and water. They are not your sensor’s values; always replace them with your own two points. As another example, the Home Hacker Lab guide uses 2.2 V dry and 1.8 V wet. That values differ this much between guides is itself evidence that they depend on the unit, the supply and how the reference points are taken.

method can be least_squares or exact, with least_squares as the default. least_squares fits one straight line through all points; exact connects each pair of points precisely as a polyline. With two points they are identical. The difference appears with three or more, for example dry, middle and wet. If you want the display to match exactly at the middle point, use exact.

Settling the value with filters: the order of median and clamp

ADC readings wobble slightly even in a steady state. In ESPHome you stack filters, which the documentation says are applied in the order written. The example above uses median, then calibrate_linear, then clamp; the SmartHomeScene guide uses the same order.

median takes the middle value of the last few readings. The defaults are window_size 5, send_every 5 and send_first_at 1: take the median of five readings and send once every five. Unlike an average, it is not dragged by a single outlier. With update_interval at 60s, Home Assistant receives a value every five minutes, which is usually plenty for watering decisions. To shorten it, reduce update_interval or send_every.

clamp after calibrate_linear limits the value to between min_value and max_value. Outside the calibration range, such as drier than at calibration, the math gives negative percentages; clamp pins them to 0%. To discard out-of-range values instead, use ignore_out_of_range: true.

The order matters. median first removes outliers at the voltage stage before conversion. clamp last limits only the converted value to 0–100. Putting clamp before calibrate_linear would clamp the voltage to 0–100, which is meaningless. Whether an AI swapped the filter order is an easy thing to miss.

Combine with deep sleep for battery power

If there is no USB power near the pot, you will run on batteries. Deep sleep is the ESPHome feature for saving power on battery nodes: run_duration sets how long the node stays awake and sleep_duration how long it sleeps.

deep_sleep:
  id: deep_sleep_1
  run_duration: 20s
  sleep_duration: 30min

Twenty seconds and thirty minutes are illustrative, not recommendations from the docs. Within those 20 seconds the node has to join Wi-Fi, measure and send.

This collides with the filters from the previous section. With update_interval at 60s and median send_every at 5, the node goes to sleep before five readings are collected. With send_first_at at 1 the first value is sent, but it is a single reading, not a median. When combining with deep sleep, shorten update_interval (for example to 2s) so that window_size × update_interval fits within run_duration. In the example, 5 readings × 2 seconds is 10 seconds, inside the 20.

Another point is updating firmware. While the node sleeps it cannot receive OTA updates. The docs provide the deep_sleep.prevent action to keep the ESP awake during data transfer or OTA updates, called with the deep sleep id.

then:
  - deep_sleep.prevent: deep_sleep_1

What triggers that action (a switch in Home Assistant, for example) depends on your setup. Battery life varies widely with the board, battery and Wi-Fi connection time. We have no measurements, so we give no numbers.

There are also ready-made battery units. The Seeed Studio XIAO Soil Moisture Sensor carries an XIAO ESP32C6 (ESP32-C6 chip), runs on one AA battery and ships with ESPHome flashed. It is a useful comparison against the effort of building your own.

3D printed cases and the limits of waterproofing

Even a capacitive sensor should not necessarily be buried whole. The DFRobot wiki says the part inserted into soil must not go past the limit line on the board. esp32.co.uk also warns that cheap boards often have exposed components at the top and should not be fully buried. The limit line is an instruction about insertion depth, not the result of a waterproofing test. How much water your particular product tolerates is something to check in the maker’s documentation.

Many people 3D print a case to protect the top circuitry. Published models and their licenses:

ModelAuthorLicenseNotes
Case for Capacitive Soil Moisture Sensor v1.2 v2 (MakerWorld)BallyMcBallfaceCC BY-NC-SAThe case used in the SmartHomeScene guide
Capacitive Soil Moisture Sensor V1.2 case waterproof (Printables)danielkrahCC BY-SAThe yellow part is printed in TPU/TPE; the author says no screws are needed
Capacitive Soil Moisture Sensor v1.2 – Waterproof Encasing (Printables)TinkerVisionCC BY-NC-NDTwo-part case; modified versions may not be distributed
Capacitive soil moisture sensor case (Printables)coxxCC BY-SAFor v1.2 and v2.0

Licenses constrain use. According to the Creative Commons summaries for version 4.0 (BY-NC-ND, BY-NC-SA, BY-SA), NC models may not be used commercially, so you cannot sell printed cases. ND models may not be distributed in modified form. SA models, if modified and distributed, must use the same license. The model pages do not show a license version, so we refer to the 4.0 summaries. For your own pots this is rarely a problem, but check each page before redistributing a resized version. This is a general description of license terms, not legal advice.

“Waterproof” is a name the authors chose; on the model pages and build guides we consulted we found no IP rating or waterproofing test results. SmartHomeScene states plainly that the case it used is not waterproof. In our guide to enclosure lids and hinges we set a policy of not claiming IP ratings for untested boxes, and we treat sensor cases the same way: as cases that claim to be waterproof.

Where the case falls short, seal it. The Cave Pearl Project describes covering the electronics with heat shrink tubing and epoxy. Sealing materials change the capacitance, so, as noted above, calibrate after sealing. If you want to design your own case, our electronics enclosure design guide starts from measuring the board. For the SEN0193, the official 98 × 23 mm (a long probe, so 98 mm long and 23 mm wide) is the starting point.

If you print the case yourself, PETG handles water and humidity well. Changing the material does not earn the case a waterproof rating, as noted above.

USD 12.99 on Amazon.com (as of 2026/09/21)

Defective units among generic “v1.2” boards

Boards sold online as “Capacitive Soil Moisture Sensor v1.2” are a generic nickname, not a specific maker’s part number. Units that look the same can carry different components.

In a 2020 article, The Cave Pearl Project reported variations among these generic boards:

  • Some units use an NE555 timer instead of the CMOS TLC555.
  • Some have the voltage regulator (the part marked 662K) and some do not.
  • On some, the 1 MΩ resistor on the output (R4) is not connected to ground, so the output does not behave as intended.

The NE555 issue is about supply voltage. On the Texas Instruments product pages, the NE555 is specified from 5 to 15 V, while the TLC555 runs from 2 to 15 V. Powered from the ESP32’s 3.3 V, an NE555 unit runs outside its specification. The Cave Pearl Project also recommends boards with a regulator and a TLC555.

How often such units turn up is unknown. What you can check is the markings on the chip and whether the voltage moves clearly between the dry and wet points in the calibration step. A unit whose two points barely differ, or whose value drifts when not inserted, cannot be rescued by calibration. Choosing a product like the DFRobot SEN0193, whose maker states that it has a regulator, avoids part of this problem.

Choosing parts, and prices in Japan

Prices checked at Japanese retailers on 2026-09-28, including tax (US dollar figures at 150 yen per dollar are approximate):

PartRetailerPrice
DFRobot SEN0193 capacitive soil moisture sensorAkizuki Denshi1,320 yen (about $8.80)
Seeed Studio XIAO ESP32C3Switch Science1,122 yen (about $7.50)
Seeed Studio XIAO ESP32S3Switch Science1,456 yen (about $9.70)

To measure one pot and send it to Home Assistant, the XIAO ESP32C3 plus SEN0193 covers two-point calibration and the ESPHome config. The ESP32-C3 chip has ADC1 on GPIO0–4, but only three of those reach the XIAO ESP32C3 pins. According to the Seeed pin table, among analog pins A0–A3, A0 (D0 = GPIO2), A1 (D1 = GPIO3) and A2 (D2 = GPIO4) are ADC1, while A3 (D3 = GPIO5) is ADC2. Seeed also says to use A0–A2 on ADC1 for stable readings. That caps you at three sensors.

Wiring for the XIAO ESP32C3: connect the sensor signal to one of A0–A2 (for example A1 = GPIO3), power to the 3V3 pin and ground to GND. The SEN0193 runs at 3.3 V, so the 3V3 pin can power it. In the YAML, change pin: GPIO32 in the examples to pin: GPIO3. If you plan to add heavier work later, such as a camera, choose the XIAO ESP32S3.

Seeed’s Grove capacitive moisture sensor (corrosion resistant) reduces wiring with a Grove connector; it was sold out when we checked. Generic v1.2 units are cheap and easy to buy in quantity, but assume they may include the defective variants above and verify each with two-point calibration.

On the receiving side, Home Assistant has an official ESPHome integration that connects devices directly over the ESPHome native API. Both ESPHome and Home Assistant are free and open source. Automations that use the readings, such as notifications or pump control, are configured on the Home Assistant side.

Soldering headers onto a small board like the XIAO and making tidy sensor leads is much easier with a temperature-controlled iron.

USD 13.99 on Amazon.com (as of 2026/09/21)

Summary: give the calibrated value a meaning

Reading a soil moisture sensor with an ESP32 and ESPHome takes a dozen lines of YAML. The hard part is giving the number meaning. The decisions covered here:

  • For sensors left in indoor pots, choose capacitive sensors whose electrodes are not exposed.
  • Use ADC1 pins because of Wi-Fi: GPIO32–39 on the ESP32, GPIO0–4 on the ESP32-C3 (only A0–A2 on the XIAO ESP32C3), GPIO1–10 on the ESP32-S3.
  • Use 12db or auto attenuation. 11db is deprecated, and auto cannot be combined with samples.
  • After waterproofing, take dry and wet points for each sensor and map them in reverse with calibrate_linear.
  • Order filters as median, calibrate_linear, clamp, and reset device_class if you switch the unit to %.
  • The percentage is the position between two points, not volumetric water content.
  • With deep sleep, make sure the filter window fits inside the awake time.
  • Cases that claim to be waterproof do not necessarily show an IP rating or test results. Check the NC, ND and SA license terms too.
  • Screen generic v1.2 boards by the chip markings and how far the two calibration points move.

An AI assistant helps with YAML templates and catching oversights, but check attenuation notation, pin choice, option combinations and calibration direction against the official ESPHome docs. Neither the AI nor build guides know your sensor’s values. Only the two points you measure on your own sensor define 0% and 100% for that pot. Start by flashing a config that shows the raw voltage and see how far it moves between dry and wet.

Sources

ブラウザだけでできる本格的なAI画像生成【ConoHa AI Canvas】
ABOUT ME
swiftwand
swiftwand
AIを使って、毎日の生活をもっと快適にするアイデアや将来像を発信しています。 初心者にもわかりやすく、すぐに取り入れられる実践的な情報をお届けします。 Sharing ideas and visions for a better daily life with AI. Practical tips that anyone can start using right away.
記事URLをコピーしました