Soil Moisture Sensor Tutorial for Arduino and ESP8266

Soil Moisture Sensor Tutorial for Arduino and ESP8266

In this tutorial we want to measure if the soil is wet or dry with a soil moisture sensor.

First we describe the basics how the measurement is done.

In the second part of this tutorial I show you how you can expand the lifetime of the soil moisture sensor because due to oxidization, the sensor will be damaged.

Soil Moisture Sensor

The last part of this tutorial show a practical example with an Arduino Uno and ESP8266 NodeMCU microcontroller. In this example I measured the soil moisture of an orchid plant over 2 weeks.

How the sensor measures the soil moisture?

The 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 following picture and table show the wiring between the soil moisture sensor and an Arduino Uno. The wiring for a NodeMCU is provided in the MQTT example at the end of this article.

Arduino Moisture Sensor

Moisture Sensor

Arduino microcontroller

ESP8266 microcontroller

VCC (3.3V…5V)

  

Ground

GND

GND

Digital Output

Digital I/O pin (D3)

Digital I/O pin (D3)

Analog Output

Analog I/O pin (A0)

Analog I/O pin (A0)

The operation voltage of the sensor is between 3.3V and 5V and therefore suitable for Arduino and ESP8266 microcontrollers. The sensor itself is connected to a sensor module which consists of the outputs mentioned in the table above and the following components:

  • Power LED to indicate that the module is working
  • Digital Output active LED indicating that the threshold defined by the level potentiometer is exceeded.
  • Digital Output level potentiometer to define the threshold when the digital output switches from 0 to 1 and the digital output LED is activated.

Depending on your use case you can connect the digital or analog output to your microcontroller. I always use the analog output to get sensor values between 0 and 255. This gives me more information and if I only want a digital signal, I always can convert the analog signal to a digital signal inside the program sketch of the microcontroller.

 

Expand the Lifetime of Soil Moisture Sensors

The current flow through the anode which have contact with water is a perfect environment for electrolysis which damages the sensor and makes the sensor inaccurate. How strong the electrolysis will be depends on three different factors:

  1. the quality of your sensor,
  2. the amount of water in the soil,
  3. how often and how much current is passed through electrodes

If the electrolysis taken place, the sensor is damaged and the sensor values can not be used to measure the moisture in the soil. But there is a simple solution because you can reduce the current by connecting the sensor to 3.3V and not 5V. Moreover you can measure the resistance from the soil only every hour to extend the lifespan of the soil moisture sensor.

To ensure that the current flow through the sensor is zero when you do not want to measure the sensor value, you have different options depending on the microcontroller you use

ESP8266 based Microcontroller

If you are using an ESP8266 based microcontroller you can use the deep-sleep function and waken up the sensor when you want to read the sensor value. If you do not know how to set the ESP8266 in the deep-sleep mode, I wrote a tutorial in this article. The main drawback in this solution is that the maximum sleep time is 71 minutes.

The following picture shows you the fritzing sketch for the NodeMCU using deep-sleep.

NodeMCU Moisture Sensor

The following script reads the analog value of the soil moisture sensor on pin A0 and prints the value to the serial monitor. After the value is displayed the NodeMCU will sleep for 1 minute and waken up again. Therefore the whole script has to be inside the setup function.

#define SensorPin A0 

void setup() { 
  Serial.begin(9600);
  float sensorValue = analogRead(SensorPin);
  Serial.println(sensorValue);

  ESP.deepSleep(1*60e6); // Deep Sleep for 1 minute
}

void loop() {
} 

Arduino based Microcontroller

If you want to read the sensor value only once or twice a day using a large delay inside the script you can use the following NPN based circuit to ensure that the sensor is voltage-free if you do not read the sensor value. You can not use the deep-sleep function with an Arduino. Therefor you have to use this solution with the NPN MOSFET.

Arduino Moisture Sensor MOSFET

The program script does not differ much compared to that for the NodeMCU. The only two differences is that we use the loop function to read the moisture sensor value every 30 seconds. Also we use die digital pin4 as output to enable and disable the MOSFET. Therefore we enable the MOSFET (enable the current flow on the gate) before we want to read the sensor value and disable the MOSFET (disable the current flow on the gate) after the value is read.

#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(30000);
} 

MQTT Example for Long Term Monitoring

I use the following parts for this tutorial

  • A microcontroller of your choice (Arduino, ESP8266, ESP32)
  • A Raspberry Pi
  • Soil Moisture Sensor

You find all links to the parts on the Components and Parts page

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

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

At the end of this article you can download all scripts, compressed as zip file, for this example with the blue download button.

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.

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.

Again the script is nearly the same I used for the indoor weather stations. You find the corresponding article here. You can copy the following python script for your MQTT bridge script or download the python file with the following download button.

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. Therefor 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.

6 Responses

Leave A Comment