Connected Pot Plant

Our friend Nicholas challenged us to grow a maidenhair fern successfully. After only a few days of proud parenting our efforts had already started to unravel - leaves were turning brown. It wasn't clear if the plant didn't like the air conditioning, or if it was too hot and humid in the bathroom, and if we were mis-watering it. Naturally, the best solution is to connect it to the Internet of Things and monitor it from a safe distance.

Parts list

  • Maidenhair fern, ideally in healthy condition
  • ESP32 development board. I used the M5stack because I'm a sucker for development boards with built-in displays. For a finished solution, a plain ESP32 module breakout would be fine.
  • Soil moisture sensor. I used the "capacitive soil moisture sensor v1.2", available on AliExpress for a couple of dollars
  • DHT12 temperature and humidity sensor. I bought one that came with an M5stack proto board and was marked as compatible, but any sensor would be fine. This one has a Grove connector, so it connected straight to the M5stack.

Connections

The soil moisture sensor has an analog output, so it can be connected directly to the M5stack's analog input. It took me an embarrassingly long time to figure out the pinout of the M5stack's base board, but the photo on this page made it click: https://github.com/m5stack/M5Stack/issues/71 . Each pin is available on opposite sides of the case, with both male and female pins. You can use either AD 35 or 36 - just set that number in the AnalogRead call. The connector cannot be directly connected to the M5stack, but mine came with some male-to-female jumper leads. Connect the power to 3.3v and ground also.

The DHT12 sensor simply connects to the Grove connector in the M5stack.

IoT Infrastructure

At work I use Microsoft Azure IoT Hub extensively and was keen to use it for this project too. It works as an MQTT broker and has nice (in my opinion) APIs and tools. The free tier is more than sufficient for a reasonably large garden. Create one device through the Azure Portal and record its primary key and device name. I also like using IoT Hub explorer to monitor incoming messages.

On its own, IoT Hub doesn't do much with messages (though it can route them into queues or files) so I used Splunk Enterprise to fetch incoming messages and save them into an index. The free license should be enough for a modest botanical garden. Splunk can't directly read from an IoT Hub on its own, so I installed the Event Hubs Modular Input for Splunk, which coincidentally I also wrote. Add a new input and point it at your IoT Hub's Event Hub-compatible endpoint. You can set a cron schedule for fetching new messages, so you can synchronise this with whatever your device's wakeup schedule is.

Code

I was very pleasantly surprised at how easy it was to connect the ESP32 to WiFi and to IoT Hub's MQTT broker. After spending the last year and a half at work writing software for Linux IoT devices with proprietary SDKs, the ESP32 and Arduino libraries were incredibly refreshing and made me wonder why I don't use them for everything. I used the Esp32MQTTClient library, and pinched the DHT12.cpp and DHT12.h files from the M5stack's DHT12 sample sketch.

The code is incredibly simple at the moment:

  • Connect to WiFi
  • Connect to MQTT
  • Read the sensors
  • Print the sensor readings on the screen
  • Send the readings to IoT Hub as JSON
  • Go to sleep for 5 minutes
#include <M5Stack.h>
#include <WiFi.h>
#include "Esp32MQTTClient.h"
#include "DHT12.h"
#include <Wire.h>

DHT12 dht12;

static const char* conn_string = "HostName=youriothubname.azure-devices.net;DeviceId=maidenhair001;SharedAccessKey=xxxx";

#define uS_TO_S_FACTOR 1000000
#define TIME_TO_SLEEP  60*5

void go_to_sleep() {
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  esp_deep_sleep_start();
}

void setup() {
  m5.begin();
  Wire.begin();
  M5.Lcd.setTextSize(4);

  esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
  Serial.println("Wakeup reason: " + wakeup_reason);

  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("Connecting..");

  WiFi.mode(WIFI_MODE_STA);
  WiFi.begin("your_wifi_ssid", "your_wifi_password");

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

  if (!Esp32MQTTClient_Init((const uint8_t*)conn_string)) {
    Serial.println("Initializing IoT hub failed.");
    delay(500);
    go_to_sleep();
  }

  int soil = 0;
  float temperature = dht12.readTemperature();
  float humidity = dht12.readHumidity();

  soil = analogRead(35);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println("S: " + String(soil) + "  ");
  M5.Lcd.println("T: " + String(temperature) + "  ");
  M5.Lcd.println("H: " + String(humidity) + "  ");

  char buf[100];
  snprintf(buf, 100, "{\"soil\":%d,\"temp\":%f,\"humid\":%f}", soil, temperature, humidity);

  if (Esp32MQTTClient_SendEvent(buf)) {
    M5.Lcd.println("Sent data");
    Serial.println("Sent data");
    delay(2000);
  } else {
    M5.Lcd.println("Failure");
    Serial.println("Failure!");
    delay(20000);
  }

  go_to_sleep();
}

void loop() {
}

Results

Figure 1. Moribund fern

It's simple to extract the data in Splunk using a search term like:

sourcetype=potplant "iothub-connection-device-id"=maidenhair001
| rename data.humid as Humidity, data.soil as Moisture, data.temp as Temperature
| table _time, Humidity, Moisture, Temperature

Then you can create a basic dashboard:

Future work

Once I have some more information about how the fern behaves during the day, it would be worth reducing the frequency of the data reporting so that the internal battery lasts longer. For now I've got it connected to a USB battery pack.

I've found that while on battery, the M5stack will only wake up about ten times before never waking up again, though it switches on if you press the power button. On my battery pack it's necessary to press a button to activate power output, so maybe the M5stack doesn't draw enough power while charging to keep this going. I'll try switching to a different pack which doesn't turn off automatically, and/or maybe get a longer USB cable and leave it powered from a charger.

I found that my access point eventually stops accepting connections and the program gets stuck in the 'Connecting' state. It needs a simple timeout for the connection step (maybe after several failed attempts it could log in to the access point and reboot it!)

When we have established a baseline for ideal or at least non-fatal moisture, humidity, and temperature levels, I can use Splunk's predict command to determine in advance when the plant needs watering, and use alert emails to notify us about emergency watering or temperature change needs.

Ideally the plant should also be able to tweet about its own condition periodically, and order a replacement plant from Amazon if it detects that the plant is dead.