ESP and MongoDB, sitting in a tree.

Jim Blackhurst
6 min readMar 11, 2021

Part 2: Talk to me!

Photo by Marat Gilyadzinov on Unsplash

Apologies for the delay in posting this, I didn’t think it would have so much interest! In this instalment of the series, I’ll talk about what I laughingly refer to as the ‘code’. It should become extremely obvious very quickly that I’m not a professional programmer, so I offer this only as a prototype and work in progress and would be beyond thrilled if anyone saw fit to build something more robust on these ideas.

As discussed in part 1, the hardware component of this project is an ESP32, an amazing piece of engineering that has lifted the whole maker movement, and the Arduino-based hardware that has powered it for so long, into stratospheric levels of opportunity.

The ESP32 is responsible for taking a reading from a bunch of sensors, principally in this project, a capacitive moisture sensor, but also a light sensor and humidity/temperature sensor, and then pushing these into a database (MongoDB Atlas, free tier).

The conventional and perfectly correct way of doing this (so that it can scale and the internet people don’t shout at you) would be to use MQTT as a transport protocol from the edge (the plant pot in my case) to a broker (probably a Raspberry Pi) which then takes the MQTT stream, packages it up and pushes them into a database.

However, I really can’t be bothered with all that nonsense because:

  • Scale isn’t an issue for me and never will be on this project. Even if I instrumented every single house plant in my house, all my neighbour's house plants, most of the plants in my garden, and then increased the reporting frequency to once per second, I still wouldn't be anywhere near having ‘Scaling issues’, even remaining in free tiers.
  • MQTT is boring, I’ve never fully understood it, and right now it’s not in my sphere of interesting technologies. It’s not necessary for keeping my plants alive.
  • The ESP32 (and the ESP8266, should you chose to use one) is perfectly capable of forming rich JSON packets and managing its own connectivity to an endpoint for POSTing them.

I have the code that runs on the ESP32 here on Github. It's simple and somewhat nieve, but we’ll step through it and see what it does. If you want to check out the whole repo, it’s here, but note: I changed to using platform.io rather than the Arduino IDE some time ago, and you should too.

You’ll recognise a bunch of this already if you’ve done any work with platform.io or ESP Arduino core. The first bunch of lines (1–6) are pulling in the libraries we are going to use. arduino.h is the core lib, wifi.hand HTTPClient.hare exactly what they sound like, all part of the Arduino core too. ArduinoJSON.h is the excellent library from Benoît Blanchon which makes a lot of this so much easier. WEMOS_SHT3X.h is the library for the temperature and humidity sensor that I use, and was discussed in part 1. Finally, secrets.h is all stuff that I don’t want to end up in the git repo, like my home SSID and pwd, and the URL of the MongoDB Realm web service we’ll be posting to. It’s not in the repo for obvs reasons, but it looks like this:

I really should split out the secrets from the config, so the config can go back into the repo, I’ll put that on the todo list. While we’re talking about config, the next section of the code, are a bunch of constants that really should go into the config too, but I can’t remember, but can probably guess why they aren't. Mostly pin definitions, of which pin I’ve plugged different sensors into. There is also the SAMPLES which describes how many samples of each sensor to take when averaging the readings.

The next two lines, which start RTC_DATA_ATTR define variables that live in the Real-time clock peripheral, which is useful for storing state when in deep sleep. I don’t think this is really necessary for this version of the code, but I have a version that I run on solar power outside, which goes into deep sleep. One of the variables is a boot counter, we’ll increment that every time the code runs through the setup() function so we can keep track of how many times it reboots. (for no particularly good reason)

Finally, we initialise a couple of objects, one is the SHT30 sensor, the other is space for us to build the JSON packet in.

You already know about setup() from playing around with Arduinos. It runs once when the code is executed, before moving onto the loop() function which then er.. loops.

In setup() I first light up the onboard LED to show that we’re doing something. Next (after some debug) we connect to the local Wifi. This is really flakey code for me, I need to work on some graceful retries here. It only connects once in about every 3 attempts, so you have to watch the LED and check that it eventually goes out, which means setup() has completed, if it stays on for more than 20sec, I hit the reset button on the ESP32. Assuming it did connect, the function moves on to tuning the onboard LED off, and transitions to loop()

loop() will just keep looping until the thing dies. It’s probably worth noting that if you are running this outside using solar power, and need to conserve energy (as you should anyway) you don’t use loop() , you put everything in setup() and then at the end of the function put the ESP into deep sleep until the watchdog timer wakes it up. It will wake it up by resetting it, so it just executes the setup() function over again. No need for loop()

As mentioned above, but obviously incorrectly, it appears I’m not using the bootCount variable for counting the actual boots of the device, it’s the number of loops around the main function. As mentioned, I’m not sure why, it probably felt like a good idea at the time.

We light up the onboard LED, so that we know activity is happening, and step through some functions to gather data.

Firstly, we get some onboard data from the ESP it’s self:

I look for things like the wakeup reason (again, a left over from the solar powered version), the ChipID which is unique to the device, and you can see, commented out, I (used to) get the voltage of the connected battery.

I also get the IP address that the device has on the local network and the RSSI (signal strength to the WAP).

All of this data is then pushed into a JSON document nicely nested inside a device sub-document

Back in the loop() function, we next look at the on board sensors, moisture, light, temp and humidity:

They all follow pretty much the same idea, that commenting your code is for wimps. Moisture and Light read their respective sensors SAMPLES times, and then create an average. I can’t remember why I didn’t do that for the SHT-30 sensors (Temp/Humid) but there must have been a reason. Once each of these functions has finished getting it’s data, it pushes it into the JSON object in a sensors sub-document

The final function is responsible for POSTing the data to a web service endpoint that I created using the amazing, MongoDB Realm. We’ll cover this in Part 3. The code for the POST function looks like:

I check to see the WiFi is connected, create an HTTP object, give it some headers, turn the JSON document that we’ve been building (which is currently just an internal object) into an encoded JSON stream and push it into an http.POST()call. We return to loop() have a rest for about some time, and do it all over again.

In Part 3, I’ll talk about MongoDB Realm and the webservice I use to receive the document. In case you were wondering, the final JSON packet looks like this:

Meanwhile, stick around for Part 4, (assuming I get around to writing it) and I’ll talk about how I use this data, visualised in MongoDB Charts to see when I need to water the plant.

On to part three? or back to part one?

--

--

Jim Blackhurst

Obsessed by managing data at planetary scale. (and Music, multicopters, 3D printing and geeky stuff). Opinions are my own, not my Employer's