Soil Moisture Sensor Tutorial for Arduino, ESP8266 and ESP32

In this tutorial we measure the soil moisture with a soil moisture sensor.

Because there are two different types of sensors, we describe the functionality of each one in the first chapter.

After you learn how to wire the sensor to your Arduino, ESP8266 or ESP32 microcontroller board, we create a basic Arduino script.

Soil Moisture Sensor

The last part of this tutorial I show you a practical example with Arduino, ESP8266 or ESP32 microcontrollers. In this example I measured the soil moisture of an orchid plant over 2 weeks.

Table of Contents

How Does the Soil Moisture Sensor Works?

As describes in the introduction, there are two different types of soil moisture sensors. Therefore the first step of this tutorial is to describe the functionality of each one and also to make sure that you buy the right sensor, if you not already have a soil moisture sensor.

The following two pictures show a photo of each sensor.

Capacitive Soil Moisture Sensor

Capacitive Soil Moisture Sensor

Resistive Soil Moisture Sensor

Resistive Soil Moisture Sensor

Capacitive Soil Moisture Sensor

The capacitive soil moisture sensor does not measure moisture directly but measures the changes in capacitance caused by the changes in the dielectric contrast between water and soil. Dry soils have a relative permittivity between 2-6 and water has a value of roughly 80.

The major advantage of the capacitive sensor is that there is no direct exposure of the metal electrodes. Therefore there is no electrolysis that damages the sensor through corrosion.

The operation voltage of the capacitive soil moisture sensor is 5V from my experience. In some datasheets you find the declaration that the sensor also works for 3.3V microcontrollers but in the sub chapter Influence of the Power Supply on the Analog Sensor Value, you see that I got invalid sensor values.

If you want to buy a soil moisture sensor, make sure that you buy a capacitive one.

Resistive Soil Moisture Sensor

The resistive soil moisture sensor consists of 2 probes with are put in the soil. Depending on the current direction one probe will function as the cathode and the other one as anode. Generally which probe is the anode or cathode is irrelevant for the functionality of the sensor, because the sensor only measures the resistance and is therefore independent of the direction of the current flow.

The electrical circuit is closed over the soil which functions as resistance for the current flow. This resistance is measured and depends on the amount of water in the soil because water is a natural conductor for electricity. The lower the measured resistance, the higher is the amount of water in the soil.

The current flow through the anode of the resistive soil moisture sensor, which has contact to water, is a perfect environment for electrolysis and therefore electroplating. This electrolysis damages the sensor and makes the sensor inaccurate. How strong the electrolysis will be depends on how often and how much current is passed through the electrodes.

Wiring between Soil Moisture Sensor and Microcontroller

The following pictures show the wiring between the soil moisture sensor and an the most used microcontroller from Arduino, ESP32 and ESP8266. If you are missing your favorite microcontroller, let me know in the comment section and I will add the wiring also your this microcontroller board.

Wiring between Capacitive Soil Moisture Sensor and Arduino

The following pictures show the wiring between the capacitive soil moisture sensor and different Arduino boards. We use a 5V power supply for the sensor and read the analog sensor values with pin A0 of the Arduino board.

Wiring between Capacitive Soil Moisture Sensor and ESP8266

For the wiring between the capacitive soil moisture sensor and the ESP8266, we can either use the 5V pin of the ESP8266 board or the 3.3V pin of the microcontroller, because the sensor is able to operate with both voltages. The ESP8266 reads the soil sensor value on analog pin A0. The following picture shows the wiring between the capacitive soil moisture sensor and the ESP8266 NodeMCU as well as the ESP8266 WeMos D1 Mini.

Wiring between Capacitive Soil Moisture Sensor and ESP32

The following picture shows the wiring between the capacitive soil moisture sensor and the ESP32 ESP-WROOM-32. For the power supply of the sensor, we can use the 5V or 3.3V output pin of the ESP32 board. The analog sensor value can be read with any digital pin of the ESP32 that is connected internally with an analog to digital converter.

If you are not sure which pins of the ESP32 board are able to read analog sensor values, I recommend to download my Microcontroller Datasheet eBook with detailed pinouts of several Arduino, ESP8266 and ESP32 boards.

Influence of the Power Supply on the Analog Sensor Value

The capacitive and also the resistive soil moisture sensor works with supply voltages between 3.3V and 5V. Therefore you can use the Arduino (5V operating voltage) and also the ESP (3.3V operating voltage) based microcontroller to measure the soil moisture.

But you have to keep in mind that the analog sensor value is dependent on the operating voltage. The following table shows the analog sensor value for all different combination of operating voltage, moisture and type of soil moisture sensor.

Operating Voltage3.3V5V
Dry/WetDryWetDryWet
Capacitive Soil Moisture Sensor2...814...21821...824490...549
Resistive Soil Moisture Sensor1024483...5051023344...358

Theoretically you can use any of these combinations, but you have to calibrate your sensor before you declare that your soil is wet or dry.

For the capacitive soil moisture sensor you see also from the table that the difference in the sensor value for an operating voltage of 3.3V is very low. Therefore your measuring range is very close and I recommend to use an operating voltage of 5V for the capacitive sensor.

Microcontroller Datasheet eBook

The 35 pages Microcontroller Datasheet Playbook contains the most useful information of 14 Arduino, ESP8266 and ESP32 microcontroller boards.

Arduino Code to read the Analog Value of the Soil Moisture Sensor

The following Arduino script reads the analog sensor value of the soil moisture sensor. The script is prepared for Arduino, EPS8266 and ESP32 microcontroller boards. You only have to comment the parts that you do not need based on my comments in the script.

#define SensorPin A0  // used for Arduino and ESP8266
//#define SensorPin 4  // used for ESP32

void setup() { 
  Serial.begin(9600);
}

void loop() {
  float sensorValue = analogRead(SensorPin);
  Serial.println(sensorValue);
  delay(30000);
} 

In the first part of the script we define the analog pin that connects the microcontroller with the soil moisture sensor. For the Arduino and ESP8266 we use the A0 pin. Because every digital I/O pin of the ESP32, that is not used for a communication interface, can be an analog input we have to choose one pin as analog input. In my case I use the pin 4.

If you are not sure what pins of the ESP32 board can be used as analog inputs, I recommend to download the Microcontroller Datasheet Playbook where you find detailed pinouts of several microcontroller boards, including the ESP32.

In the setup function we define the baud rate to 9600 that has to match the baud rate of the serial monitor of the Arduino IDE.

The loop function starts with reading the analog sensor value of the analog pin that we defined that the beginning of the script. The sensor value is stored in a variable from the type float. Now we print the sensor value to the serial monitor and wait for 30 seconds to start to loop function all over again.

The following picture shows the sensor values in the serial output.

Serial Monitor Capacitive 5V Dry

How to Expand Lifetime of the Resistive Soil Moisture Sensor

As described in the functionality chapter of this tutorial, electrolysis of the resistive soil moisture sensor is caused by the current flow through the anode. To avoid the current flow to the sensor, we use a N-Channel MOSFET circuit to disconnect the sensor from the power supply. Also we read the sensor value only once per hour.

Because we do not want to wait one hour to get a new sensor value, we use a delay of 30 seconds in the following example. Therefore you only have to change the delay for your project.

The following picture shows the N-Channel MOSFET circuit with the soil moisture sensor for the different microcontroller boards.

#define SensorPin A0 

void setup() { 
  Serial.begin(9600);
  pinMode(4, OUTPUT);
}

void loop() {
  digitalWrite(4, HIGH);
  delay(1000);

  float sensorValue = analogRead(SensorPin);
  Serial.println(sensorValue);
  delay(1000);
  digitalWrite(4, LOW);
  delay(28000);
} 

At the beginning of the Arduino script we define the analog pin of the microcontroller. For the Arduino and ESP8266 boards we use the A0 pin and for the ESP32 we define pin 4 as analog input.

In the setup function we set the baud rate to 9600, that have to match the baud rate of the Arduino IDE to see the sensor values that we want to print on the serial connection between microcontroller and PC.

Also we define the digital output pin that is connected to the gate of the MOSFET to switch the sensor on and off. For the Arduino boards we use digital pin 4, for ESP8266 pin D4 and for ESP32 pin 0 as digital output pin. You only have to comment the lines for the microcontroller that you do not need.

In the loop function we read the moisture sensor value every 30 seconds. Therefore we use die digital pin to enable the current flow on the gate of the MOSFET. This closes the circuit of the soil moisture sensor and we read the analog sensor value after a short delay of 1 second. After the sensor value is printed to the serial monitor, we wait for 1 second and disable the current flow through the sensor by pulling the Gate of the MOSFET LOW.

Because we want to read the sensor value every 30 seconds in this example and already have two 1 second delays in the Arduino script, we wait for 28 seconds at the end of the program code.

MQTT Example for Long Term Monitoring

In the following example I want to observe the soil moisture for a plant for a long time and see the course of the moisture as a line-chart. Therefore I build a MQTT system including the following components:

  • NodeMCU to read the analog soil moisture sensor values and send them every hour via MQTT to a MQTT broker
  • Raspberry Pi as MQTT broker which saves the moisture values to an Influx database and visualize the soil moisture of the plan via Grafana.
Moisture Sensor Foto

The following table gives you an overview of all components and parts that I used for this whole tutorial. For the MQTT example you only need one ESP8266 or ESP32 microcontroller and only one Raspberry Pi. I get commissions for purchases made through links in this table.

ComponentAmazon LinkAliExpress Link
Arduino Nano AmazonAliExpress
Arduino Pro Mini AmazonAliExpress
Arduino Uno AmazonAliExpress
Arduino Mega AmazonAliExpress
ESP32 ESP-WROOM-32AmazonAliExpress
ESP8266 NodeMCU AmazonAliExpress
ESP8266 WeMos D1 Mini AmazonAliExpress
Raspberry Pi 4 Model B Kit AmazonAliExpress
Raspberry Pi 4 Model B AmazonAliExpress
Raspberry Pi 3 B+ Kit AmazonAliExpress
Raspberry Pi 3+ AmazonAliExpress
Soil Moisture Sensor AmazonAliExpress

This example relates strongly on two articles I wrote the last month. Therefore I will speed up this example because you find a step by step tutorial in the following two articles:

First we build the part of the NodeMCU to send the sensor values to the MQTT broker. The following picture shows the wiring for the NodeMCU.

NodeMCU Moisture Sensor

The program code is nearly exactly the same as I used to send the temperature and humidity to the MQTT broker. I only changed the MQTT topic, MQTT clientID and read the moisture values to send them via MQTT to the same existing broker. The main code is inside the setup function, because I use the deep-sleep function of the NodeMCU to reduce the electrolysis on the soil moisture sensor.

Make sure you add the delay before entering the deep-sleep. I had some problems, that the NodeMCU shutting down while the MQTT message was not completely send.

#include "ESP8266WiFi.h" // Enables the ESP8266 to connect to the local network (via WiFi)
#include "PubSubClient.h" // Allows us to connect to, and publish to the MQTT broker

#define SensorPin A0 

// WiFi
const char* ssid = "KabelBox-0174";
const char* wifi_password = "943476385562******";

// MQTT
// Make sure to update this for your own MQTT Broker!
const char* mqtt_server = "192.168.0.8";
const char* plant_topic = "plant";
const char* mqtt_username = "cdavid";
const char* mqtt_password = "cdavid";
// The client id identifies the ESP8266 device. Think of it a bit like a hostname (Or just a name, like Greg).
const char* clientID = "client_plant_test";


// Initialise the WiFi and MQTT Client objects
WiFiClient wifiClient;
PubSubClient client(mqtt_server, 1883, wifiClient); // 1883 is the listener port for the Broker


void connect_MQTT(){
  Serial.print("Connecting to ");
  Serial.println(ssid);

  // Connect to the WiFi
  WiFi.begin(ssid, wifi_password);

  // Wait until the connection has been confirmed before continuing
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // Debugging - Output the IP Address of the ESP8266
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // Connect to MQTT Broker
  // client.connect returns a boolean value to let us know if the connection was successful.
  // If the connection is failing, make sure you are using the correct MQTT Username and Password (Setup Earlier in the Instructable)
  if (client.connect(clientID, mqtt_username, mqtt_password)) {
    Serial.println("Connected to MQTT Broker!");
  }
  else {
    Serial.println("Connection to MQTT Broker failed...");
  }
}


void setup() { 
  Serial.begin(9600);
  connect_MQTT();
  Serial.setTimeout(2000);

  float sensorValue = analogRead(SensorPin);
  Serial.println(sensorValue);

  // PUBLISH to the MQTT Broker
  if (client.publish(plant_topic, String(sensorValue).c_str())) {
    Serial.println("Moisture sent!");
    Serial.println(plant_topic);
  }
  // Again, client.publish will return a boolean value depending on whether it succeded or not.
  // If the message failed to send, we will try again, as the connection may have broken.
  else {
    Serial.println("Moisture failed to send. Reconnecting to MQTT Broker and trying again");
    client.connect(clientID, mqtt_username, mqtt_password);
    delay(10); // This delay ensures that client.publish doesn't clash with the client.connect call
    client.publish(plant_topic, String(sensorValue).c_str());
  }
  
  delay(1000);
  ESP.deepSleep(0.2*60e6);
}

void loop() {
} 

Now the NodeMCU sends the current soil moisture every hour to the MQTT broker. The next task is to write a bridge script which reads the MQTT payload and writes it to the Influx Database. For this task, you find a step by step guide in the Visualize MQTT Data with InfluxDB and Grafana article.

I created a new Influx database called “soil_moisture” and a new Influx user “mqtt_moisture” with the password “mqtt_moisture”. Also I granted all rights for this new database to the new user.

import re
from typing import NamedTuple

import paho.mqtt.client as mqtt
from influxdb import InfluxDBClient

INFLUXDB_ADDRESS = '192.168.0.8'
INFLUXDB_USER = 'mqtt_moisture'
INFLUXDB_PASSWORD = 'mqtt_moisture'
INFLUXDB_DATABASE = 'soil_moisture'

MQTT_ADDRESS = '192.168.0.8'
MQTT_USER = 'cdavid'
MQTT_PASSWORD = 'cdavid'
MQTT_TOPIC = 'plant'
MQTT_CLIENT_ID = 'MQTTInfluxDBBridge_moisture'
influxdb_client = InfluxDBClient(INFLUXDB_ADDRESS, 8086, INFLUXDB_USER, INFLUXDB_PASSWORD, None)


def on_connect(client, userdata, flags, rc):
    """ The callback for when the client receives a CONNACK response from the server."""
    print('Connected with result code ' + str(rc))
    client.subscribe(MQTT_TOPIC)


def on_message(client, userdata, msg):
    """The callback for when a PUBLISH message is received from the server."""
    print(msg.topic + ' ' + str(msg.payload))
    sensor_data = float(msg.payload)
    if sensor_data is not None:
        _send_sensor_data_to_influxdb(sensor_data)


def _send_sensor_data_to_influxdb(sensor_data):
    json_body = [
        {
            'measurement': 'soil_moisture',
            'tags': {
                'location': 'livingroom'
            },
            'fields': {
                'value': sensor_data
            }
        }
    ]
    influxdb_client.write_points(json_body)


def _init_influxdb_database():
    databases = influxdb_client.get_list_database()
    if len(list(filter(lambda x: x['name'] == INFLUXDB_DATABASE, databases))) == 0:
        influxdb_client.create_database(INFLUXDB_DATABASE)
    influxdb_client.switch_database(INFLUXDB_DATABASE)


def main():
    _init_influxdb_database()

    mqtt_client = mqtt.Client(MQTT_CLIENT_ID)
    mqtt_client.username_pw_set(MQTT_USER, MQTT_PASSWORD)
    mqtt_client.on_connect = on_connect
    mqtt_client.on_message = on_message

    mqtt_client.connect(MQTT_ADDRESS, 1883)
    mqtt_client.loop_forever()


if __name__ == '__main__':
    print('MQTT to InfluxDB bridge Moisture')
    main()

Now the sensor values are stored in the Influx database so that we can create a dashboard in Grafana. In Grafana you can create a new data source. Use the Influx database and the username and password you set before.

Grafana Configuration Data Sources

Now you can create a new dashboard and visualize the soil moisture of your plants at home. The following picture is the one I collected the data over several days. You see clearly how the sensor values are rising to 980 over the days. Between the 12.01. and 13.01 the plant got some water and the sensor values drop to around 750. In the following days the sensor values rise again and now I know exactly when my plant needs water.

Grafana Soil Moisture Dashboard

Conclusion

I hope with this tutorial you now have a good understanding of the soil moisture sensor. I tried to keep the theory of the sensor as short as possible because the functionality of the sensor is not very complex to understand. Therefore I tried to concentrate on a good practical example using MQTT, InfluxDB and Grafana to build a pretty monitoring system for your plants at home.

I hope you like this article. If you have any questions regarding the moisture sensor or the MQTT example please use the comment section below to ask your questions. I answer them as soon as possible.

14 thoughts on “Soil Moisture Sensor for Arduino, ESP8266 and ESP32”

  1. Hi there,

    Thank you very much for your well-written tutorials! I have been having fun following along from here in America :).

    Through the articles you have written, I was able to get mostly set up, aside from one issue.

    My device only seems to report when I first plug it in. Do you have any ideas why?

    Also, the code (I believe) is missing some information here:

    #include // Enables the ESP8266 to connect to the local network (via WiFi)
    #include // Allows us to connect to, and publish to the MQTT broker

    I made it the below. Is this correct?

    #include // Enables the ESP8266 to connect to the local network (via WiFi)
    #include // Allows us to connect to, and publish to the MQTT broker

    Reply
    • Hi Jarrod,
      yes there is a missing part in the include line because the editor of this website has problems to print <>. Therefore I changed the include lines to #include “ESP8266WiFi.h” and #include “PubSubClient.h” that also works fine in Arduino.
      Regarding your problem, that your device only reports when you plug it in: What is the content of your loop function?

      Reply
        • Hi Jarrod,
          ok your ESP should wake-up every 0.2*60 = 12 seconds.
          Do you connect the D0 and RST pin after flashing the microcontroller?

          Reply
          • I got it… I didn’t have a wire going from D0 to RST! Thanks for your help! I am now getting data every 12 seconds.

            So, if I wanted a reading every 30 minutes to preserve power, I should change

            0.2*60 to 0.2*9000?

          • Perfect! If you want to send the data every 30 minutes you have to replace the 0.2*60 with 30*60, because the ESP.deepSleep function get as argument the time in microseconds. By multiply your time with 60e6 you get the time in minutes. Therefore you need 30*60e6 as argument.

  2. Hi!
    I got everything to work as described above with very little problems. It seems as I even had the same or very similar soil probe as in your images.
    My problem is that there is still current to the soil probe during the deep sleep, so the probe is consumed after ~3 weeks.
    I’m using a NodeMcu V3 with your example program.
    I put in a few extra lines in the code to assure the NodeMcu goes into deep sleep, and it seems so.
    I can measure ~73 mA (with a very old digital multimeter), to the probe sensor logic board during deep sleep, the led is still bright. Since there is no metal left on one leg of the probe I guess it’s more or less correct.
    Any suggestions?

    Regards

    Reply
    • Hi, I could think that maybe your NodeMCU is not going to deep sleep. Do you measured the current consumption for the NodeMCU during the deep sleep mode? Also you could use the ESP.getResetReason() function to see if the ESP comes back from deep sleep.

      Reply
  3. hello Admin
    I Have a problem when I used Hassio for MQTT broker.
    When Flashed code to ESP 8266, I saw data that send to broker is zero all the time
    Could you please explain to me?
    thanks

    Reply
    • Hi Mr Cuong,
      thx for the reply, a general failure in the sensor is not so easy to detect. Nice to hear that now everything is working well.

      Reply
  4. Thanks a lot for this work. It really helped me get the concept well. Can I ask that you please guide me further on how to calibrate the capacitancor soil mois ture sensor?

    The values I got were below your suggestions, so I felt probably it is because I have not calibrate the sensor. Thanks

    Reply
  5. Hi Jarrod,

    Your explanation is truly wonderful and clears a lot of fundamentals.

    How can this scrip MQTT be migrated to integrate with Thingsboard Platform without using MQTT.

    My long term aim is to try to get soil moisture reading in Thingsboard and based on the moisture conditions I want the Raspberry Pi running Thingsboard to start watering the plants. The Raspberry Pi and the Sensor will have to talk in Wireless Mode.

    Reply

Leave a Comment