USD

How to Build an ESP8266 Based Connected Flower Monitor

By Maker.io Staff

Taking care of a plant can be a troublesome process, especially for beginners. This article discusses a connected flower monitoring system that regularly records the temperature and light level and how much water the plant has left. This project allows its user to check these values on a simple website that users can access from any device within the same network. The finished device also stores data from the last hour to allow a user to track changes. These features make this project the perfect addition to any home or urban garden!

Required Components

Product / Where to buy

Note that you can substitute the BME280 sensor with a different one, for example, a DHT-11 if required. However, I had better results with the BME280, and changing the sensor means that you’ll also have to change some parts of the firmware code for this project. You can also use a different ESP8266-based development board or even one that employs an ESP32. It’s also possible to add other sensors, such as a rain detector or an oxygen sensor.

The Schematic

 

Note that two versions of the light sensor are available. One has a black solder mask, while the other one comes with a blue solder mask. The schematic refers to the version with a black solder mask. The pins on the blue one are slightly different. Please consult the part’s datasheet for details.

In this scenario, the ESP8266 interfaces both sensor modules via I2C. The BME280 module, however, also supports SPI if required. In this project, the two module boards have distinct I2C addresses, and I, therefore, kept it simple and connect both of them to the same bus. The soil moisture sensor connects to the single analog input of the Adafruit Feather Huzzah board.

Installing the Necessary Libraries and Board-support Files

The upcoming code is what ties together all the individual parts of the project. For that, it employs the Adafruit_BME280 and Adafruit TSL2591 libraries. You can install both of them with the library manager that is included with the Arduino IDE. To do that, select the ‘Manage Libraries’ option in the ‘Tools’ section of the IDE, and then search for the two libraries individually:

Libraries_2

Furthermore, you need to install support for the ESP8266. First, add the following additional board manager URL in the preferences window of the Arduino IDE:

Support_3

Once you have finished that step, open the board manager window and search for ESP8266 just like you did with the two libraries. You can access the board manager from the main menu bar of the Arduino IDE:

Manager_4

The Firmware Code

The following Arduino sketch reads the sensor values, stores them in arrays at a certain interval, and serves a simple website for clients to check on their plant:

Copy Code
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#include "Adafruit_TSL2591.h"

#define STORAGE_SIZE 60
#define WAIT_TIME 60000

double moisture_history[STORAGE_SIZE];
double temperature_history[STORAGE_SIZE];
double humidity_history[STORAGE_SIZE];
double pressure_history[STORAGE_SIZE];
double luminosity_history[STORAGE_SIZE];

const char* ssid = "YOUR_SSID";
const char* pass = "PASSWORD";

WiFiServer server(80);
Adafruit_BME280 bme;
Adafruit_TSL2591 tsl = Adafruit_TSL2591(1);

int error = 0;
int next = 0;
long lastCapture = 0;

// Function prototypes
void findMinMaxAvg(double* res, double* input, int len);
void printArrayAsTableRow(WiFiClient c, String description, double* arr, int len);

void setup()
{
// Open the serial monitor for debugging
Serial.begin(9600);

while(!Serial)
delay(50);

// Check whether the BME sensor is available
if(!bme.begin())
{
Serial.println("Couldn't find a BME280 board!");
error = 1;
}

// Configure the light sensor. These are medium settings for
// well-lit environments. Check the official demo for more
// configuration options and a detailed explanation.
tsl.setGain(TSL2591_GAIN_MED);
tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);

Serial.println("");
Serial.print("Connecting to ");
Serial.print(ssid);

// Try to connect to the supplied WiFi network
WiFi.begin(ssid, pass);

pinMode(A0, INPUT);

while(WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(500);
}

Serial.println("Done!");

Serial.println("Starting server...");
server.begin();

Serial.print("Server started with address ");
Serial.print("http://");
Serial.print(WiFi.localIP());
Serial.println("/");
}

void loop()
{
// Check for incoming connections
WiFiClient c = server.available();

// Read the sensor values periodically

double moisture_sensor_reading = 1024.0 - analogRead(A0);
double moisture_percentage = (moisture_sensor_reading / 1024.0) * 100.0;
double temperature = bme.readTemperature();
double humidity = bme.readHumidity();
double pressure = bme.readPressure() / 100.0;
int luminosity = tsl.getLuminosity(TSL2591_VISIBLE);

// Store the current sensor values in the historic data arrays
// after a certain time has passed
if(abs(millis() - lastCapture) > WAIT_TIME)
{
moisture_history[next] = moisture_percentage;
temperature_history[next] = temperature;
humidity_history[next] = humidity;
pressure_history[next] = pressure;
luminosity_history[next] = luminosity;

// Reset the counter once the arrays are full
// With the standard values, this happens every hour
if(++next >= STORAGE_SIZE)
next = 0;

lastCapture = millis();
}

if(c)
{
while(!c.available())
delay(10);

// Read the first line of the HTTP request
// It contains something similar to the following line:
// METHOD /requested_url HTTP_VERSION
// for example:
// GET /index HTTP/1.1
// However, for the sake of simplicity this device only accepts
// GET requests as any web browser can send them.
// Furthermore, this program ignores the requested page and it
// always returns the same page for each request.

// Read the request and empty the buffer
// Even though we aren't interested in the request text itself
// we should still remove the bits from the buffer.
String request = c.readStringUntil('\r');
c.flush();

// Return the response
// If no error occurred, send an HTML page that lists the current sensor values
// and the historic data.

if(error == 0)
{
// Return a response header
c.println("HTTP/1.1 200 OK");
c.println("Content-Type: text/html");
// The HTTP response body is separated from the header by an empty line
// (actually a line containing \r\n, but this will work)
c.println("");

// Create arrays to hold the results from the
// accumulation operations (min, max, avg)
double moisture_acc[3];
double temperature_acc[3];
double humidity_acc[3];
double pressure_acc[3];
double luminosity_acc[3];

// Perform the accumulation operations (min, max, avg) for
// all historic data arrays
findMinMaxAvg(moisture_acc, moisture_history, next);
findMinMaxAvg(temperature_acc, temperature_history, next);
findMinMaxAvg(humidity_acc, humidity_history, next);
findMinMaxAvg(pressure_acc, pressure_history, next);
findMinMaxAvg(luminosity_acc, luminosity_history, next);

// Return the response body (an html page)
c.println("<html>");
c.println("<head>");
c.println("<title>IoT Flower Monitor</title>");
c.println("</head>");
c.println("<body>");
c.println("<h1>Current data</h1>");
c.println("<table border=\"1px\">");
c.print("<tr><td>Temperature</td><td>");
c.print(temperature);
c.println("&deg;C</td></tr>");
c.print("<tr><td>Humidity</td><td>");
c.print(humidity);
c.println("%</td></tr>");
c.print("<tr><td>Air Pressure</td><td>");
c.print(pressure);
c.println("hPa</td></tr>");
c.print("<tr><td>Light</td><td>");
c.print(luminosity > 750 ? "Bright" : (luminosity > 500 ? "OK" : (luminosity > 300 ? "Dim" : "Dark")));
c.println("</td></tr>");
c.print("<tr><td>Soil Moisture</td><td>");
c.print(moisture_percentage);
c.println("%</td></tr>");
c.println("</table>");
c.println("<h1>Historic Data</h1>");

// Only print the historic data if something is available
if(next > 0)
{
c.println("<table border=\"1\">");
c.println("<tr><th>Value</th><th>Min.</th><th>Max.</th><th>Avg.</th></tr>");

printArrayAsTableRow(c, "Temperature", temperature_acc, 3);
printArrayAsTableRow(c, "Humidity", humidity_acc, 3);
printArrayAsTableRow(c, "Air Pressure", pressure_acc, 3);
printArrayAsTableRow(c, "Light", luminosity_acc, 3);
printArrayAsTableRow(c, "Soil Moisture", moisture_acc, 3);

c.println("</table>");
}
// Otherwise show a warning
else
{
c.println("No data to display. Wait a few seconds and then reload the page!");
}

c.print("<p>Data accumulated from ");
c.print(next);
c.println(" samples.<br/>");
c.print("Samples taken every ");
c.print(WAIT_TIME / 1000.0);
c.print(" seconds.</p>");
c.println("</body>");
c.println("</html>");
}
// Otherwise, send an error message
else
{
// Return a response header
c.println("HTTP/1.1 500 Internal Server Error");
c.println("Content-Type: text/html");
c.println("");
c.println("<html>");
c.println("Sensor Error! Check if the sensors are connected correctly!<br/>");
c.println("<a href=\"/\">Back to main page</a>");
c.println("</html>");
}
}
}

void printArrayAsTableRow(WiFiClient c, String description, double* arr, int len)
{
c.print("<tr><td>");
c.print(description);
c.println("</td>");

for(int i = 0; i < len; i++)
{
c.print("<td>");
c.print(arr[i]);
c.print("</td>");
}

c.println("</tr>");
}

void findMinMaxAvg(double* res, double* input, int len)
{
double maximum = -1000000;
double minimum = 10000000;
double sum = 0;

for(int i = 0; i < len; i++)
{
maximum = max(maximum, input[i]);
minimum = min(minimum, input[i]);
sum = sum + input[i];
}

res[0] = minimum;
res[1] = maximum;
res[2] = sum / len;
}

As you can see, the first few lines of the sketch load the required libraries, some of which you installed in the last step. Then, the code defines a few variables and the arrays that hold the data collected in the past hour. This data gets sampled periodically. The default frequency is once every minute. The setup function opens the serial monitor for displaying debug messages, and then it tries to establish a connection to the given Wi-Fi network. The loop method is where the actual work happens.

This function first reads all sensor values, and then it stores them in the previously mentioned arrays if a certain amount of time has passed. Next, the loop method checks whether a client is available. If there is a client, the function accumulates the previously collected values to determine the minimum, maximum, and average values of each array. This functionality was implemented in the findMinMaxAvg function towards the end of the sketch. The loop method then proceeds to serve the connected client an HTML website that lists the most recent sensor values and the accumulated values. If an error occurred, the program servers an error page instead. The result looks like this:

Error_5

Summary

This article demonstrates that building a connected flower monitor is a fun project for beginners and more experienced makers. With only a handful of cost-effective components, you can assemble a useful addition to your home or urban gardening ecosystem. The finished project allows you precise monitoring of various environmental sensor readings. Doing so ensures that you can more easily determine the needs of your plants.

In this project, an ESP8266 based development regularly reads the output of several sensors via the I2C bus. This project demonstrates how multiple devices can use the same bus to communicate as long as they have a unique address. This method requires only two signal lines and two additional power lines to function. The simple software ensures that you can use any smartphone or computer to view the sensor output, as long as the device is connected to the same Wi-Fi network as the flower monitor.

Key Parts and Components

Add all Digi-Key Parts to Cart
  • 1528-1530-ND
  • 1738-1184-ND
  • 1528-1037-ND
  • 1528-1359-ND
  • 438-1045-ND
  • 1528-1155-ND