In this post we describe a simple plant monitor that utilises an Arduino and a number of sensors to monitor environmental data relevant to the plant’s wellbeing. In particular, we use a soil moisture sensor, a combined humidity and temperature sensor and a light sensor. We will discuss calibrating the sensors, wiring up the circuit, programming the Arduino and aggregating the data and visualising it using a remote service.


Soil Sensor

The most important sensor for monitoring the plant’s environment is a soil moisture sensor. Soil moisture sensors can be categorised into two types: resistive sensors and capacitive sensors (see Fig. 1).

Fig. 1 Resistive (left) and capacitive (right) soil moisture sensors

As the name implies, resistive sensors operate by measuring the resistance of the soil between the two prongs (electrodes), by applying a voltage across the electrodes and measuring the resulting current and then applying Ohm’s law to calculate the resitance. This means the sensor has to be in physical contact with the moist soil, which results in gradual corrosion of the electrodes. In contrast, capacitive soil moisture sensors rely on the electric field generated by the electodes to determine the capacitance of the surrounding medium (the soil) and can operate even when the electrodes are not in direct physical contact with the soil, reducing the potential for corrosion of the electrodes. Typically a thin layer of lacquer or varnish is applied to the electrodes to prevent corrosion. The pros and cons of the two types of soil moisture sensor is discussed in more detail in the following YouTube video:

In this project we tried both types of sensor but opted for capacitive sensors for the final prototype, which although more expensive, provide a superior performance over time for the reasons discussed above.

Combined Humidity and Temperature Sensor

We used the DHT22 combined humidity and temperature sensor (see Fig. 2) in this project. The DHT22 is a four pin (although only three are used) digital sensor employing a fairly complex data protocol, however there are number of third-party libraries available that provide a simple API to access humidity and temperature measurements.

Light Sensor

A simple light dependent resistor (LDR) was employed as the light sensor (see Fig. 2). LDRs are cheap semiconductor devices whose resistance varies inversely with light intensity (more strictly irradiance in W/m2).

Fig. 2 A light dependent resistor (left) and a DHT22 combined humidity and temperature sensor (right).


The plant monitor was based around an Arduino microcontroller and a schematic of the circuit is shown below in Fig.3.

Fig 3. Schematic of the Plant Monitor circuit

The DHT22 sensor data terminal is connected to digital pin 2 of the Arduino microcontroller. The data output of the soil moisture sensor is connected to analog pin 1. The LDR is wired as a voltage divider, in series with a 10 kilo Ohm resistor, with the junction between the LDR and the 10 kilo Ohm resistor being connected to analog pin 2 of the microcontroller.

The power rail of the breadboard is fed by the 5V output pin of the microcontroller.


DHT22 Sensor

The DHT22 humidity and temperature digital sensor is already calibrated and outputs humidity and temperature data directly.

Soil Sensor

Soil moisture sensors are typically calibrated by measuring the voltage output obtained from the sensor in air (dry) and from the sensor immersed in water (wet). These two extreme values can then be mapped to a range of 0-100, so that the actual value obtained from the sensor when embedded in soil represents a soil moisture percentage. The actual soil moisture value can then be obtained from the sensor voltage output by using the Arduino mapfunction as follows:

float soilVolts = Vcc*analogRead(moisturePin)/1024;
int moisturePercent = map(soilVolts, airVolts, waterVolts, 0, 100);

Light Dependent Resistor

The light dependent resistor needs some form of calibration to convert its resistance into a light level. The first thing we need to determine, in order to estimate the light level, is the instantaneous resistance of the LDR.

We can use the voltage measured at the junction of the LDR and 10 kiloOhm resistor to estimate the resistance of the LDR by applying Ohm’s law applied to the voltage divider formed by the LDR in series with the 10 kiloOhm resistor (see Fig. 4).

Fig 4. Light dependent resistor voltage divider circuit

If V0 is the voltage measured at the junction of the LDR and 10 kiloOhm resistor, then

V0 = I R2


I = Vcc /(RLDR + R2)

Substituting for I and rearranging we get

RLDR = R2(Vcc – V0)/V0   =  (R2Vcc/V0) – R2

Since Vcc = 5 Volts and R2 = 10 kOhms, then

RLDR = (50/V0) – 10

where RLDR is the resistance of the LDR in kiloOhms.

Since LDRs are semiconductor devices, the relationship between resistance and light level is highly nonlinear and it’s common to represent it as a Log v Log response. To obtain an absolute calibration would require using a calibrated Lux meter as a reference. However, we can perform an approximate calibration using datasheets (we used a GL5528 LDR) and empirical observations. In order to display our light on the same axis as our moisture readings we used a scale of 0% to 100% and assumed a resistance of 10 Ohms at high light levels (100%) and 1 Mega Ohm at low light levels (0%). We can map the Log of RLDR onto the 0-100% range using the Arduino mapfunction (resistance being measured in kiloOhms):

int lightPercentage = map(log10(Rldr), 3, -2, 0, 100);

Since the Arduino mapfunction uses only integer arithmetic you will get better results by solving the relationship by hand in which case the linear relationship can be represented by:

double lightPercentage  = 20.0*(3.0 - log10(Rldr));

Basic Sketch

The code below is a basic standalone Arduino sketch that monitors the three sensors, incorporating the calibration described earlier.

#include <math.h> 
#include <DHT.h>

#define moisturePin A1
#define lightPin A2
#define DHTPIN 2 //digital
#define DHT_TYPE DHT22  

const float Vcc = 5.0f;

const float airVolts = 2.9f;
const float waterVolts = 1.4f;


void setup() {
  dht.begin(); //Initialise the DHT sensor
  Serial.begin(9600); // Initialise Serial debug output
  while (!Serial && millis() < 10000)
    ; //Wait a maximum of 10s for Serial to open

void loop() {
  float soilVolts = Vcc*analogRead(moisturePin)/1024;
  int moisturePercent = map(soilVolts, airVolts, waterVolts, 0, 100);
  Serial.print("moisture = ");

  int humidity = dht.readHumidity();
  Serial.print("humidity = ");
  int temperature = dht.readTemperature();
  Serial.print("temperature = ");

  double ldr = Vcc*analogRead(lightPin)/1024.0;
  Serial.print("ldr voltage: ");

  double Rldr = (Vcc*10.0/ldr) - 10.0;
  Serial.print("LDR resistance (kOhms): ");

  double lightPercent = 20.0*(3.0 - log10(Rldr));
  Serial.print("Light levels in percent: ");

This sketch could be extended to provide automatic watering of the plant by monitoring the soil moisture sensor and activating an irrigation system when the soil moisture value falls below a certain threshold. This enhancement is left as an exercise for the reader.

Data Aggregation and Visualisation

There are a number of portals that will store and visualise sensor data, some offering a free subscription for rate-limited data uploads. These typically support HTTP as a protocol and also MTTQ – a more lightweight protocol specifically designed for transporting sensor data.

It is also possible to build your own server stack using MQTT for transport, a time series database such as InfluxDB for storing data and a visualisation package such as Grafana for creating elegant graphics. However, this is beyond the scope of the current post. Instead, in this example we use one of the ready made solutions;

The final sketch is available in GitHub and was written for the Arduino UNO WiFi Rev 2. It uses the WiFiNINA library for WiFi connectivity and the Arduino HTTP Client library to upload data via the Adafruit IO REST API. This requires a JSON payload, an example of which is shown below.

if (millis() - lastConnectionTime > postingInterval) {
    char payload[200];
    sprintf(payload, "{\"feeds\":[{\"key\":\"humidity\", \"value\":%d}, {\"key\":\"light\", \"value\":%d}, {\"key\":\"soil-moisture\", \"value\":%d}, {\"key\":\"temperature\", \"value\":%d}]}", humidity, (int)lightPercent, (int)moisturePercent, temperature);

Access to the Adafruit IO service is rate-limited for free subscriptions, so in this example we only perform one upload a minute. The actual POST request is shown below:

void httpPostRequest(const char payload[]) {
  client.sendHeader("X-AIO-Key", "<YOUR-ADAFRUIT-IO-API-KEY");"/api/v2/<your-username>/groups/<your-feed-name>/data", "application/json", payload);

  // read the status code and body of the response
  int statusCode = client.responseStatusCode();
  String response = client.responseBody();

  Serial.print("Status code: ");
  Serial.print("Response: ");

  lastConnectionTime = millis();

Adafruit IO uses a Grafana-based visual interface which is easy to customise. The resulting visualisation of our plant monitor data is shown below (Fig. 5).

Fig. 5 Visualising the plant monitor sensor output using Adafruit IO

Wrapping Up

In this post we’ve discussed a simple Arduino-based plant monitor that senses the plant’s environment, in particular soil moisture, humidity, temperature and light levels. We have described calibration of the sensors and the aggregation and visualisation of the sensor data as well as the basic circuit. The final prototype is shown below in Fig. 6.

Fig. 6 Prototype plant monitor

If you are interested in creating your own plant monitor it is worth doing some research as there are a numerous online examples, featuring a variety of approaches to monitoring the health of your plant, including self-irrigating systems and integrated plant pots.