IoT Projects

Soil Moisture Monitoring with ESP32 and MQTT

A basic IoT soil moisture monitor needs an ESP32, a capacitive soil moisture sensor and a way to report readings, such as MQTT. The hardest part is not the code but wiring the sensor correctly and calibrating it for your soil.

What you need

  • ESP32 development board
  • Capacitive soil moisture sensor (analog output, 3.3 V compatible)
  • Jumper wires, breadboard, USB power
  • Wi-Fi network and an MQTT broker (a local Mosquitto instance is fine)

Why capacitive, not resistive?

Resistive probes pass current through the soil, which accelerates corrosion and makes readings drift. Capacitive sensors measure a change in capacitance without exposed metal electrodes in contact with the soil and last much longer, so they are the better choice for anything that stays in the ground.

Wiring

Sensor pin ESP32 pin Note
VCC 3V3 Use 3.3 V so the output never exceeds the ADC range
GND GND
AOUT GPIO34 An ADC1 input; ADC2 pins cannot be used while Wi-Fi is on

Calibration

Raw ADC values differ between sensors and soils. Print the raw reading with the sensor in dry air, then in a glass of water (up to the line marked on the sensor), and use those numbers as your dry and wet limits. Then check in actual soil, because air and water are only the extremes.

Code

#include <WiFi.h>
#include <PubSubClient.h>

const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_PASSWORD";
const char* MQTT_HOST = "broker.local";
const int   SENSOR_PIN = 34;   // ADC1
const int   DRY_RAW = 3000;    // replace after calibration
const int   WET_RAW = 1300;    // replace after calibration

WiFiClient net;
PubSubClient mqtt(net);

int readMoisturePercent() {
  long sum = 0;
  for (int i = 0; i < 16; i++) { sum += analogRead(SENSOR_PIN); delay(5); }
  int raw = sum / 16;                        // average to reduce noise
  int pct = map(raw, DRY_RAW, WET_RAW, 0, 100);
  return constrain(pct, 0, 100);
}

void ensureConnections() {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.begin(WIFI_SSID, WIFI_PASS);
    while (WiFi.status() != WL_CONNECTED) delay(500);
  }
  while (!mqtt.connected()) {
    if (!mqtt.connect("soil-node-01")) delay(2000);
  }
}

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
  mqtt.setServer(MQTT_HOST, 1883);
}

void loop() {
  ensureConnections();
  mqtt.loop();
  static unsigned long last = 0;
  if (millis() - last > 30000) {
    last = millis();
    char msg[8];
    snprintf(msg, sizeof(msg), "%d", readMoisturePercent());
    mqtt.publish("farm/field1/soil/moisture", msg, true);
  }
}

Common mistakes

  • Using an ADC2 pin and getting erratic values once Wi-Fi starts.
  • Powering from 5 V and driving the signal above 3.3 V.
  • Skipping calibration and trusting a percentage that is only a guess.
  • Controlling a pump from a single noisy reading: add averaging and hysteresis.
  • Leaving the sensor’s electronics exposed to water; seal the top edge.

Next steps

Add a relay for irrigation with hysteresis, move to deep sleep for battery operation and display the data in Node-RED. These are covered in the Smart Agriculture IoT course and the ESP32 course. Background on the protocol is in MQTT explained.

References

Related reading

IoT Communication and Cloud

MQTT Explained with Examples

MQTT is the lightweight publish/subscribe protocol behind much of IoT messaging. Learn brokers, topics, QoS, retained messages and last will with examples.

Arduino and ESP32

Arduino Uno vs ESP32 for IoT Projects

Which board should you use for an IoT project? A practical comparison of Arduino Uno and ESP32 on connectivity, power, I/O and learning curve.

Learning from your city?

City guides show how online IoT training works from where you are, with project themes for your area.

Browse all city guides

Call WhatsApp