Keeping plants alive with AWS IoT & Lambda
My colleagues and I once faced a recurring problem: we’re terrible at keeping plants alive. But as software developers in an open-plan, plant-plentiful office, something needed to be done, and it needed to be cheap. My love of DIY IoT and Serverless led me to a vision of a swarm of ESP8266 modules monitoring plants, publishing data with MQTT to AWS IoT, which in turn triggers a Lambda Function, writing and comparing data in DynamoDB, and finally, notifying us on Slack when a given plant needs watering.
That was a lot of links and buzzwords, so I’m going to break it down to the hardware side and the software side.
Hardware
First, a capacitive soil moisture sensor. I say capacitive because the alternative is resistive, which is cheaper, but resistive sensors are prone to corrosion. I don’t have a single model number to share out, as they tend to be unbranded. If you’re in Australia, Core Electronics have a suitable one available.
Next, the chip. The ESP8266 is a WiFi equipped (802.11b/g/n) SoC, usually boasting an 80MHz CPU, 80KB of user-data RAM, and 4MB Flash memory (ROM). Compared to a modern day credit card sized computer, like a Raspberry Pi, it sounds utterly pathetic. But we don’t need four cores, or HDMI, or any of that fancy stuff. We just need an ADC (something to take a voltage and turn it into a digital representation of a number), enough power to constuct a string of JSON containing a single property, and a network connection with which to send the JSON. In fact, even the ESP8266 is overkill. But it’s wildly popular, and I don’t know of a better alternative off the top of my head.
And of course, “it [needs] to be cheap.” An ESP-12 (a board containing an ESP8266, WiFi antenna, and solder points) can be bought for as low as $2-4. If you’re just prototyping, and don’t want to go soldering, I recommend a NodeMCU board, plus some pin header cables, over a standalone ESP mdoule. That said, cheap hardware presents a different cost: low-level programming.
Software
The Sensors
I wrote the sensor’s software in Arduino code, which is effectively C++ with some additions. As a TypeScript developer, it’s a little unsettling having to deal with things like memory management again, but given the scope of the code is so small, it was still a relatively pleasant piece of code to write. I won’t go over every line of it (you can check out the code on GitHub), but its functionality can be broken down as follows:
- Connect to WiFi
- Connect to the MQTT broker (AWS IoT)
- Get a reading from the soil moisture sensor
- Send that as a JSON string to the MQTT broker
- Shutdown for 20 minutes, then back to step 1.
The Servers
A common solution to handling the MQTT payloads would be to have an always-on server running an MQTT broker, with some sort of server application connected as a client, listening out for any messages. You could do this with AWS for around $5/month using EC2 and the lowest tier instance available. We currently monitor three plants at the office while we’re prototyping the physical modules, with plans to expand to 10-15. But even with 15 plants reporting once every 20 minutes, that’s only 45 requests per hour. It takes a few hundred milliseconds at best to process everything and send a notification, so for the most part, you’d be paying for a computer to sit there doing nothing.
Enter Lambda.
Lambda is AWS’s serverless computing service. With it, we can trigger our application logic only when we need it, saving countless hours of idle computing time (and money). And keeping to the rule of cheap, Lambda wins over EC2 very easily: it’s free.
Free, within the free tier limits anyway. The most limiting factor here is AWS IoT, which is the message broker receiving messages from the sensors, triggering Lambda functions in turn. You can send up to 500,000 messages per month for free. But to put that in perspective, that’s over 230 sensors reporting every 20 minutes. If you have that many plants in your office, you can afford to exceed the free tier threshold.
Again, I won’t detail every line of the Lambda function code (see the repo for that), but I’ll break it down to a process flow:
- Write the sensor value received to DynamoDB
- Get the last 24 hours of readings for that plant from DynamoDB
- Check if the moisture level has consistently been below the plant’s specified threshold value for long enough.
- If the above is true, and we haven’t sent a notification for that plant in the last eight hours, send a notification.
Sending the notification is a simple HTTP request to a Slack Webhook. Each plant is given a name, along with numbers to specify their minimum moisture level, and how long they can be below that level before notifying. We store these as individual JSON files in the repo:
{
"name": "Andy Kelp",
"threshold": {
"value": 505,
"hours": 48
}
}
But does it work?
Yes! Mostly, anyway. We’re starting with three plants because that’s how many spare NodeMCU’s I had, and even that has revealed some issues. Notably:
- It’s hard to get the
threshold.value
right for a plant: These numbers are set as a result of first logging them for a while, and then eventually setting some value that seems about right for the plant, given its history. We’ve been running with three plants for a number of months now, and yet I still find myself tweaking the values every now and again. - Sometimes they stop publishing: I’m yet to work this one out — sometimes, with no obvious pattern to it, a sensor module will stop publishing its data, and it won’t start publishing again until it’s physically restarted. My “fix” is probably going to be to automatically trigger a reboot when a module exceeds a timeout period during startup.
But what’s the point?
Learning, really. Yeah, I could just set a reminder on Slack or something saying “water the plants” every couple of days. But where’s the fun in that?