AWS IoT Coffee Monitor – Part 1

This is part 1 of 2 on building an AWS IoT Coffee Monitor. Part 2 can be found here

If you’re a serial coffee drinker like me, 5 to 10 cups of coffee a day is normal and if your also like me, you get times that your busy/in the zone/distracted and before you know it, that last sip was ice cold, eww.

Getting up to rewarm your coffee in the microwave is super annoying, let’s say I drink best case (worst for some) 10 cups a day, and that 50% go cold while I am working. Then let’s say that I have at least started to sip on the coffee and that half is drank. To heat half a cup takes about 30 seconds, so 5 cups takes about 2.5 minutes a day. That is 12.5 minutes a week, 50 minutes a month and 10 hours a year, give or take. This does not even factor in the trip to the microwave and back (+-20 seconds) or all of the distractions and coworkers along the way.

So I don’t know about you, but wasting +-10 hours a year on reheating coffee is not acceptable.

The engineer in me had to solve this problem. I want to get a push notification to my phone indicating the different states in which my coffee is in, ex: Coffee is getting cold.

Requirements:

  • Don’t want to spend too much time on creating the solution
  • Cheap, initial cost and ongoing
  • Little to no maintenance after deployment
  • User friendly notifications
  • Serverless

Solution:

  • IaC (Infrastructure as Code) must be used, I prefer SAM over Serverless as it is AWS native and less tedious than Cloud Formation.
  • Use AWS IOT, as the resources that will be used are so small they will fall within the free tier.
  • Use hardware to monitor the coffees temperature. I settled on the ESP32 as it is the cheapest device that connects to AWS IOT and it is beefy enough to handle the TLS encryption need.
  • I basically dug around in my old/dead electronics projects box and managed to scavenge a cheap and reasonably accurate LM35 temperature sensor. This will be added to the bottom of my coffee coaster to measure the temperature.
  • Then on the server side, or rather serverless, we use AWS Lambda functions. A Lambda function is subscribed to listen to the MQTT Topic of which the ESP32 publishes the events like temperature.
  • The Lambda function stores the current data in DynamoDB, it uses historic data to make an informed decision on the current state the cup of coffee is in. States like: The coffee is now cold.
  • To visualize data we need to download data from DynamoDB manually and then open it in Excel. This is a bit of a pain, but is/was only used when initially establishing the events for state change temperature points and patterns.
  • A separate Lambda function that listens to events from the IOT Lambda, whose sole purpose is to send Telegram notifications to me, informing me of the state my coffee is in.

Prerequisites:

  • Familiar with AWS
  • Have the AWS SDK installed and configured
  • AWS SAM installed (brief explanation provide further down)
  • Mongoose OS installed (brief explanation provide further down)

Hardware

As mentioned before we will use an ESP32, specifically the SparkFun ESP32 Thing, it works of 3,3V and has many cool features. Most noteworthy is the built in LiPo battery charger and then the fact that it can handle TLS encryption (with reasonable ease) unlike its predecessor the ESP8266.

Then for the temperature sensor we will use the commonly available LM35, it can be powered from 3.3V and then outputs a linear voltage proportional to the temperature. So that every 10mV is 1°C, we can then measure the temperature by using the ADC (Analog to Digital Conversion) of the ESP32. By just reading the voltage on the Analog Out pin of the LM35, we will get a digital number ranging from 0 – 4095 in code, which we can then map to a temperature value.

I connected the sensor as follows:

ESP32 LM35
PIN 36 (ADC1_0) Analog Out
3.3V Vcc
GND GND

If your following along, you can connect the VCC and GND pins on any of the pins available, just be careful on what pin you use to do the ADC.

1 Cool Coffee Coaster [click to enlarge]
1 IoT Device [click to enlarge]
1 IoT Coffee Monitor [click to enlarge]

Not the best quality, I did mention this is a weekend project right?

ESP32 Config and AWS IoT

We have already established that the ESP32 is the hardware that we will be using, there are quite a few options for the software that we can use on the ESP32, I have used and just prefer Mongoose OS (MOS). You can find out more here. Mongoose OS can be programmed in a C/C++ or JavaScript language/environment, we will be using JavaScript. Download the executable, then follow this guide: https://mongoose-os.com/docs/mongoose-os/quickstart/setup.md to Step 3 (not including 4), we will use our own code from here on out.

You can get the code for this project here: https://github.com/rehanvdm/CoffeeMonitor the code for the ESP32 is under /mongoose. After you downloaded the code, change to that directory inside the MOS console:

# cd C:/Users/Rehan/Projects/Node/Lambdas/CoffeeMonitor-V2/mongoose

Then we first need to build the code, to transform it into something the ESP32 understands (assembly)

# mos build

Now we can upload our code to the board with:

# mos flash

This command might fail (immediately) a few times, depending on whether the serial bus is busy, if it fails, just run it a few times until it succeeds.  Next we need to connect our ESP32 to the internet, get your Wi-Fi access point credentials ready and substitute your values before running the command below:

# mos wifi YOUR_WIFI_ACCESS_POINT_NAME YOUR_WIFI_PASSWORD

Lastly we need to connect our device to AWS, since the SDK is installed we can use the handy MongooseOS command to provision a Certificate and create the Thing in AWS IOT Core (substitute your region, I am using us-east-1).

# mos aws-iot-setup --aws-region us-east-1 --aws-iot-policy mos-default

The device should reboot, the LED will toggle every 5 seconds and you will start seeing console output like below, verifying that the device is publishing data to AWS IoT.

This will create certificates in the directory /mongoose (.crt.pem and .key.pem) and if we navigate to our AWS IOT Core console, we will see the Thing and the Certificates.

Your device should then be sending the temperature every 1 minute now, we will send this message over the topic: coffee-monitor/home/temp. In our SAM Template we will do the rest by creating a Rule that specifies all data from the MQTT Topic should go to our CoffeMonitor Lambda. Then we can verify that our device has connected by going to our Shadow, image below.

To see the events coming in, go to the test page and subscribe to the topic coffee-monitor/#, this will listen to all messages send from the Coffee Monitor ESP32s. You can manually test/generate an event by pressing the PIN 0 button.

ESP32 Code

MQTT and Device Shadow

First a word on MQTT and the AWS IoT Thing Shadow.

MQTT is a pub sub protocol designed specifically to limit bandwidth and foot print unlike https, perfect for IoT. MQTT Topics are just endpoints to which we send data to the server. Any client connected to the MQTT Server can then specify to which Topics they want to listen to. In the context of SQL, imagine that the FROM clause is all our sensors and then the Topic is used in the WHERE clause, used to select only certain data that we want. More on this in Part 2, when we connect our Lambda to the Topic using an SQL like expression.

Our Topics for this project are structured in the following way: Application/Location/EventType. This gives us a large amount of combinations to listen to. For example, to listen to all Coffee Monitors temperature messages independent of location we would listen to coffee-monitor/#/temp.

The topics that our ESP32 publishes are:

  • coffee-monitor/home/temp
  • coffee-monitor/home/button_click

In our code I also just show how to update our Thing Device Shadow. A Device Shadow is just a JSON document that represents the state of the device. If the device updates the Shadow then it sends the updated JSON document to the server, if the server updates the Shadow, then it sends it to the device. Thus we can communicate this way by always keeping the Shadow in sync on both server and device. The device can then act on this and change its state. We will be sending the Shadow (but not make use of it) and Topic messages to the server in this project.

Code

The full source code for the ESP32 can be viewed here: https://github.com/rehanvdm/CoffeeMonitor/mongoose/fs/init.js

The important bits are below:

To get the temperature we read the ADC value of the temperature pin and convert it to a temperature value with the formula:

let adcVal = ADC.read(tempPin);
let tempVal = ((adcVal / 4095.0) * 3300)/10;

Where 4095 is the resolution of the ADC and then 3300 is the 3.3V maximum value, we divide by 10 as each mv is 1 degree. Thus we get the percentage the temperature pins voltage is at, then multiply that by the maximum voltage to get the current voltage and then divide by 10 to get the temperature. The calculation in the code is slightly different as I measured an offset and compensated for that, using a different value for the resolution.

Since this temperature sensor is not extremely accurate or precise, we use a Simple Moving Average (SMA more info here) filter in the code to smooth the values returned from the sensor. A size 10 point SMA is used that records the temperature every 5 seconds. Then another SMA of size 3 runs every 1 minute and calculates the temperature before we send it to the server.

The temperature is sent to the Topic coffee-monitor/home/temp:

let currentSMATemp =  SmaArrayValue(lastThreeMinutesTemps);
PublishEvent("temp", { "event_type": "temp", "value": currentSMATemp });

Then another piece of interesting code is the button press. Button presses gets counted and then after a button press, we de-bounced with some code and then finally sends the event with the amount of presses recorded so far:

shadowState.btnCount++;
PublishEvent("button_click", { "event_type": "button_click", "click_no": shadowState.btnCount });

We also just report (don’t make use of it in this project) the shadow to the Server with:

let shadowState = {led: false, btnCount: 0, uptime: 0}; 
Shadow.update(0, shadowState);

This send the JSON Object which we define as the Shadow back to the server and then we receive any changes to the shadow here:

Shadow.addHandler(function(event, obj)
{
    if (event === 'UPDATE_DELTA')
    {
        print('GOT DELTA:', JSON.stringify(obj));
        Shadow.update(0, shadowState); // Report our new state, hopefully clearing delta
    }
});

That’s it for Part 1. Part 2 will consists of the SAM Template that we use to do IaC and then also the code for the Lambdas, DynamoDB, SNS and the Telegram bot.

FYI, you can use S3 as a storage instead of DynamoDB, the access patterns can be very easily changed to read from Athena. It mostly writes, reads can easily be cached in memory of the lambda. This will make reporting much easier as you can directly connect Quicksights to Athena, this is great if you already have a Quicksight subscription ($9 basic). This method seems a bit over kill, for the amount of data that we will be storing, but here is the design diagram anyways: