Build a smart plant monitor with Arduino Cloud

A soil moisture probe and a DHT11 watch a plant, Arduino Cloud shows how thirsty it is on a gauge and a chart, and a status light on the pot turns red when it needs water.
  • Level:Intermediate
  • Duration: 1 h
  • Ages 11+
  • Platform: ESP32

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Build a smart plant monitor with Arduino Cloud – EEMENTOR STEM™ lesson

Plants can't tell you they are thirsty, but a soil moisture probe can. In this lesson the ESP32 measures how wet the soil is and the temperature and humidity of the air around the plant. Arduino Cloud shows the moisture as a percentage on a gauge and a chart, and a dashboard slider sets the level at which the plant needs water. When the soil gets drier than that, a status LED on the pot turns red, and the dashboard says "Water me!".

What you'll learn

  • How a soil moisture probe measures water
  • How to turn a raw reading into a percentage with two calibration points
  • How a slider in the dashboard can change a setting on the ESP32
  • How to combine several readings in one Thing

What you need

  • ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
  • Soil moisture module with probe (45-in-1 Sensor Kit)
  • DHT11 temperature and humidity sensor (ESP32 IoT Starter Kit)
  • Red LED and 1 resistor, 220 Ω (ESP32 IoT Starter Kit)
  • A potted plant
  • A 2.4 GHz Wi-Fi network
  • The printed parts from the 3D printing box below (plant stake), or the cardboard version described there
  • Breadboard and jumper wires (ESP32 IoT Starter Kit)

Wiring

Breadboard wiring diagram: Build a smart plant monitor with Arduino Cloud
Breadboard view. Check each connection against the wiring table below.
From To
Soil module AO ESP32 GPIO 34
Soil module VCC and GND 3V3 and GND
Soil module DO Not used
DHT11 S (data) ESP32 GPIO 27
DHT11 + and − 3V3 and GND
ESP32 GPIO 25 220 Ω resistor, then the red LED's long leg
LED short leg GND

Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.

Set up Arduino Cloud

In Arduino Cloud, open Things, create a new Thing called Plant, link it to your ESP32 device and fill in its network settings. Then add these variables. Lesson 1 of this path shows each step with pictures in words.

Variable Type Permission Update
moisture Integer number (%) Read only Every 30 s
temperature Temperature sensor (°C) Read only Every 30 s
humidity Floating point number Read only Every 30 s
dryLimit Integer number Read & Write On change
status Character string Read only On change

Open Dashboards, create a dashboard and add a Gauge and a Chart (moisture), two Values (temperature, humidity), a Slider from 10 to 60 (dryLimit) and a Messenger or Value widget (status), each linked to its variable. The same dashboard appears in the free Arduino IoT Remote app on your phone.

Calibrate the probe

  1. Upload the sketch and open the Serial Monitor at 115200 baud. It prints the raw reading.
  2. Hold the probe in the air: note the number and put it into DRY_RAW.
  3. Put the probe in a glass of water up to the line: note the number and put it into WET_RAW. Upload again.

The sketch

Arduino Cloud writes the sketch's first version for you, with a thingProperties.h tab that lists your variables and a Secret tab with your Wi-Fi details and device key. In the Thing, open the Sketch tab, click Open full editor, replace the main sketch with the code below and upload. Leave the other tabs as they are.

Add the DHT sensor library by Adafruit under Libraries in the Arduino Cloud editor.

#include "thingProperties.h"
#include <DHT.h>

const int SOIL_PIN = 34, DHT_PIN = 27, LED_PIN = 25;
const int DRY_RAW = 3300;            // reading in the air
const int WET_RAW = 1300;            // reading in water
DHT dht(DHT_PIN, DHT11);
unsigned long lastRead = 0;

int soilPercent() {
  long sum = 0;
  for (int i = 0; i < 20; i++) sum += analogRead(SOIL_PIN);
  int raw = sum / 20;
  Serial.printf("Raw soil reading: %d\n", raw);
  return constrain(map(raw, DRY_RAW, WET_RAW, 0, 100), 0, 100);
}

void update() {
  moisture = soilPercent();
  float t = dht.readTemperature(), h = dht.readHumidity();
  if (!isnan(t)) temperature = t;
  if (!isnan(h)) humidity = h;
  bool thirsty = moisture < dryLimit;
  digitalWrite(LED_PIN, thirsty);
  status = thirsty ? "Water me!" : "I'm fine, thank you";
}

void setup() {
  Serial.begin(115200);
  delay(1500);
  pinMode(LED_PIN, OUTPUT);
  dht.begin();
  dryLimit = 30;                     // until the cloud sends the slider's value

  initProperties();
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  setDebugMessageLevel(2);          // connection messages in the Serial Monitor
  ArduinoCloud.printDebugInfo();
}

void loop() {
  ArduinoCloud.update();
  if (millis() - lastRead >= 10000) {
    lastRead = millis();
    update();
  }
}

void onDryLimitChange() {
  Serial.printf("Dry limit is now %d %%\n", (int)dryLimit);
  update();                          // react at once
}

For reference, this is the thingProperties.h that Arduino Cloud creates from the variables above. Yours has your own device ID, and may use an older but equally valid style such as addProperty(led, READWRITE, ON_CHANGE, onLedChange).

// Code generated by Arduino Cloud (yours has your own device ID)
#include <ArduinoIoTCloud.h>
#include <Arduino_ConnectionHandler.h>

const char DEVICE_LOGIN_NAME[] = "YOUR_DEVICE_ID";
const char SSID[] = SECRET_SSID;              // from the Secret tab
const char PASS[] = SECRET_OPTIONAL_PASS;
const char DEVICE_KEY[] = SECRET_DEVICE_KEY;

void onDryLimitChange();

int moisture;
CloudTemperatureSensor temperature;
float humidity;
int dryLimit;
String status;

void initProperties() {
  ArduinoCloud.setBoardId(DEVICE_LOGIN_NAME);
  ArduinoCloud.setSecretDeviceKey(DEVICE_KEY);
  ArduinoCloud.addProperty(moisture, Permission::Read).publishEvery(30);
  ArduinoCloud.addProperty(temperature, Permission::Read).publishEvery(30);
  ArduinoCloud.addProperty(humidity, Permission::Read).publishEvery(30);
  ArduinoCloud.addProperty(dryLimit, Permission::ReadWrite).onUpdate(onDryLimitChange);
  ArduinoCloud.addProperty(status, Permission::Read).publishOnChange(0);
}

WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);

How it works

The soil probe is two metal prongs: the wetter the soil, the better it conducts. On the module, that becomes a voltage on AO. On most modules a lower number means wetter, which is why map() goes from DRY_RAW to WET_RAW: it turns the raw number into 0 % (as dry as air) to 100 % (as wet as water), whichever way your module counts.

The slider shows how a dashboard can change a setting on the device. When you move it, Arduino Cloud sends the new dryLimit and onDryLimitChange() checks the plant again straight away.

This Thing uses all five variables that the free plan allows. Choosing what to send is part of designing an IoT device.

Try this next

  • Add a pump with the relay, and water automatically for three seconds when the soil is too dry.
  • Show how many days it has been since the last watering.
  • Monitor two plants with two probes.

Troubleshooting

  • The device stays "Offline": open the Serial Monitor at 115200 baud. Check the Wi-Fi name and password in the Secret tab (2.4 GHz only) and that the secret device key is the one you saved when you added the device.
  • Compile error about a variable: the variable names in the sketch must match the ones in your Thing exactly, including capital letters. Rename the Thing's variable or the sketch's to match.
  • The moisture is always 0 or 100 %: calibrate DRY_RAW and WET_RAW again with the Serial Monitor.
  • The probe corrodes: cheap probes wear out in wet soil. Pull it out when you aren't measuring, or only power it during a reading.
  • A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

ESP32 IoT Starter Kit | Arduino IDE Compatible | Free Tutorials $57.00

Shop the kit