A Guide on Collecting and Reporting Soil Moisture with ESP32 and Sensor through MQTT
Table of Contents
Introduction
The ESP32 is a powerful Wi-Fi and Bluetooth module that offers a highly integrated SoC solution. Its low power consumption, compact design, and high stability make it ideal for IoT applications. The ESP32 features comprehensive Wi-Fi and Bluetooth network capabilities, allowing it to function as a standalone application or as a slave device to a host MCU.
The MQTT protocol is a message transport protocol based on a publish/subscribe model. In MQTT, there are two roles: publishers and subscribers. Publishers send messages to a topic, and subscribers subscribe to a topic. When a publisher sends a message to that topic, the subscribers receive the message.
In this tutorial, we will explore how to use the ESP32 and the MQTT protocol to collect and report soil moisture data. We will use the ESP32 as a publisher to send soil moisture data to a topic, allowing a subscriber to receive and monitor this data remotely.
Project Initialization
Before starting the project, ensure you have the following hardware and software.
Hardware Preparations
- ESP32 development board
- Capacitive Soil Moisture Sensor v1.2
Sensor Overview
The capacitive soil moisture sensor v1.2 uses voltage to detect soil moisture levels. The sensor readings vary based on the supply voltage and the specific sensor. By placing the sensor in air and water, we can obtain calibration values that help determine whether the soil is dry or wet. For example, some capacitive sensors provide the following readings:
At 1.3V:
- In air: above 2000
- In water: below 1000
At 5V:
- In air: above 800
- In water: below 500
Software Preparations
- Arduino IDE: For writing and uploading code to the ESP32
- MQTTX Client or another MQTT client: For testing MQTT message publishing and subscribing
- Free Public MQTT broker provided by EMQX: Supports nearby sensor access and unified management. EMQX Public Broker server access information:
- Broker:
broker.emqx.io
- TCP Port:
1883
- TLS Port:
8883
- Websocket Port:
8083
- Websockets Port:
8084
- Broker:
Hardware Connection
Connect the ESP32 and the soil moisture sensor as follows:
- Sensor VCC to ESP32 VIN
- Sensor GND to ESP32 GND
- Sensor AOUT to ESP32 GPIO36 (ADC0): In this project, we will use the ESP32 pin GPIO36 (ADC0) to connect to the AOUT pin of the moisture sensor to read soil moisture data.
Connecting ESP32 to the MQTT Broker
Installing ESP32 Board Support
Open Arduino IDE, go to File -> Preferences. In the "Additional Boards Manager URLs" field, add the following URL:
https://dl.espressif.com/dl/package_esp32_index.json
Next, go to Tools -> Board -> Boards Manager, search for ESP32, and install it.
Installing PubSubClient Library
In Arduino IDE, go to Sketch -> Include Library -> Manage Libraries, search for PubSubClient
, and install it.
Initializing WiFi and MQTT Clients
To begin, we need to initialize the WiFi and MQTT clients. This will allow the ESP32 to connect to the internet and communicate with the MQTT server. First, include the necessary libraries and define the WiFi credentials:
#include <WiFi.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
// WiFi credentials
const char *ssid = "WIFI_SSID"; // Replace with your WiFi name
const char *password = "WIFI_PASSWORD"; // Replace with your WiFi password
// MQTT Broker configuration
const char *mqtt_broker = "broker.emqx.io";
const char *mqtt_topic = "emqx/esp32/moisture";
const char *mqtt_username = "emqx";
const char *mqtt_password = "public";
const int mqtt_port = 8883;
Next, initialize the WiFi and MQTT clients:
WiFiClientSecure esp_client;
PubSubClient mqtt_client(esp_client);
Defining the Sensor Pin
The GPIO36 (ADC0) pin on the ESP32 is used to connect to the AOUT pin of the soil moisture sensor. This pin is chosen because it supports analog-to-digital conversion, which is necessary for reading the sensor's analog output.
#define sensorPIN 36
Loading the CA Certificate
To establish a secure connection to the MQTT broker using TLS, we need to load a root CA certificate. This example uses the DigiCert Global Root G2 certificate, which is used by the EMQX public broker. If you are using a different server, replace this certificate with the appropriate one.
const char *ca_cert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
)EOF";
Connecting to the MQTT Broker
The connectToMQTT()
function is responsible for establishing a connection to the MQTT broker using TLS. It sets the CA certificate and then attempts to connect using the specified broker address, port, and credentials. The function also sets a keep-alive interval to ensure the connection remains active.
void connectToMQTT() {
esp_client.setCACert(ca_cert); // Set the CA certificate for secure connection
mqtt_client.setServer(mqtt_broker, mqtt_port); // Set the MQTT broker server and port
mqtt_client.setKeepAlive(60); // Set the keep-alive interval to 60 seconds
while (!mqtt_client.connected()) {
String client_id = "esp32-client-" + String(WiFi.macAddress()); // Generate a unique client ID
Serial.printf("Connecting to MQTT Broker as %s...\n", client_id.c_str());
if (mqtt_client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
Serial.println("Connected to MQTT Broker");
} else {
Serial.print("Failed to connect, rc=");
Serial.print(mqtt_client.state()); // Print the connection state for debugging
Serial.println(" Retrying in 5 seconds.");
delay(5000); // Wait for 5 seconds before retry
ing
}
}
}
Publishing Sensor Data
The publishSensorData()
function reads the soil moisture sensor data, formats it as a JSON object, and publishes it to the specified MQTT topic. This function ensures the data is correctly serialized and sent over the MQTT protocol.
void publishSensorData() {
StaticJsonDocument<200> json_doc;
int moistureValue = analogRead(sensorPIN); // Read the moisture sensor value
json_doc["moisture"] = moistureValue; // Add the sensor value to the JSON document
char json_buffer[512];
serializeJson(json_doc, json_buffer); // Serialize the JSON document to a string
mqtt_client.publish(mqtt_topic, json_buffer); // Publish the JSON string to the MQTT topic
Serial.printf("Published to %s: %d\n", mqtt_topic, moistureValue); // Print the published message for debugging
}
Complete Code
Here's the complete code with the functions and explanations combined:
#include <WiFi.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
// WiFi credentials
const char *ssid = "WIFI_SSID"; // Replace with your WiFi name
const char *password = "WIFI_PASSWORD"; // Replace with your WiFi password
// MQTT Broker configuration
const char *mqtt_broker = "broker.emqx.io";
const char *mqtt_topic = "emqx/esp32/moisture";
const char *mqtt_username = "emqx";
const char *mqtt_password = "public";
const int mqtt_port = 8883;
// WiFi and MQTT client initialization
WiFiClientSecure esp_client;
PubSubClient mqtt_client(esp_client);
// GPIO pin for Soil Moisture, ESP32 pin GPIO36 (ADC0) that connects to AOUT pin of moisture sensor
#define sensorPIN 36
// Root CA Certificate
const char *ca_cert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
)EOF";
// Function Declarations
void connectToWiFi();
void connectToMQTT();
void publishSensorData();
void setup() {
Serial.begin(115200);
connectToWiFi();
connectToMQTT();
}
void connectToWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected to WiFi");
}
void connectToMQTT() {
esp_client.setCACert(ca_cert); // Set the CA certificate for secure connection
mqtt_client.setServer(mqtt_broker, mqtt_port); // Set the MQTT broker server and port
mqtt_client.setKeepAlive(60); // Set the keep-alive interval to 60 seconds
while (!mqtt_client.connected()) {
String client_id = "esp32-client-" + String(WiFi.macAddress()); // Generate a unique client ID
Serial.printf("Connecting to MQTT Broker as %s...\n", client_id.c_str());
if (mqtt_client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
Serial.println("Connected to MQTT Broker");
} else {
Serial.print("Failed to connect, rc=");
Serial.print(mqtt_client.state()); // Print the connection state for debugging
Serial.println(" Retrying in 5 seconds.");
delay(5000); // Wait for 5 seconds before retrying
}
}
}
void publishSensorData() {
StaticJsonDocument<200> json_doc;
int moistureValue = analogRead(sensorPIN); // Read the moisture sensor value
json_doc["moisture"] = moistureValue; // Add the sensor value to the JSON document
char json_buffer[512];
serializeJson(json_doc, json_buffer); // Serialize the JSON document to a string
mqtt_client.publish(mqtt_topic, json_buffer); // Publish the JSON string to the MQTT topic
Serial.printf("Published to %s: %d\n", mqtt_topic, moistureValue); // Print the published message for debugging
}
void loop() {
if (!mqtt_client.connected()) {
connectToMQTT();
}
mqtt_client.loop();
publishSensorData();
delay(60000); // Delay between readings
}
Connecting and Testing
Uploading Code to ESP32
Copy the above code into the Arduino IDE, connect your ESP32 to your computer, select the appropriate board and port, and click the upload button to upload the code to the ESP32.
Reading Sensor Data and Publishing via MQTT
Once the code is uploaded, open the Serial Monitor. You will see logs indicating the ESP32's connection to the Wi-Fi and MQTT Broker, and soil moisture data will be reported every minute.
Using MQTTX to Subscribe to a Topic and Retrieve Soil Moisture Published by ESP32
We can use the MQTTX desktop MQTT client to connect to broker.emqx.io
and then subscribe to the emqx/esp32/moisture
topic to retrieve the soil moisture published by the ESP32.
Conclusion
In this tutorial, you learned how to use the ESP32 and a soil moisture sensor to collect soil moisture data and report it to a server via the MQTT protocol. This is just the tip of the iceberg for IoT applications. You can build on this project to explore more possibilities, such as adding more sensors to monitor different environmental parameters or integrating them into an agricultural automation system for smart irrigation.
You can delve deeper into the advanced features of MQTT, such as message retention, last will and testament messages, and explore other functionalities of the ESP32 to expand your IoT projects and achieve more complex application scenarios.
Resources
- MQTT on ESP32: A Beginner's Guide
- A Developer's Journey with ESP32 and MQTT Broker
- Using MQTT on ESP8266: A Quick Start Guide
- Remote control LED with ESP8266 and MQTT
- How to Use MQTT on Raspberry Pi with Paho Python Client
- MicroPython MQTT Tutorial Based on Raspberry Pi
- How to Deploy an MQTT Broker on Raspberry Pi