Here's the finished prototype in use
Why did I make this?My wife and I make cheese and yogurt at home. Both require multiple steps, a thermometer, and a bit of waiting. I had made a thermometer with an Adafruit Trinket Pro. It had an LCD, a button, and would beep when we reached specific temperatures to remind us when next steps were needed. We'd set it, forget it, wait for the beep, press the button, shut off the heat, stir, or add cultures, and ignore again til the next beeps. Very handy.
Then the LCD just stopped working. Instead of fixing it, I created this next iteration to take advantage of multiple Alexa devices we have throughout our home, new capabilities available via Alexa Skills Kit and AWS IoT, Lambda, DynamoDB, and more-capable Arduino software and hardware.
We now have flexible ways for Alexa to help us monitor and control what's cooking in the kitchen, outside in our smoker/grill, and from any room and device in the house.
How does it work?The Alexa skill handles the user interaction - ask it to make yogurt, and it fetches the matching recipe and sends the request to start that recipe on step 1 to the device, by way of a device shadow and MQTT, a communication protocol for the Internet of Things.
The device watches for changes to the shadow, and uses recipe data onboard to set the set point and show its status on the display.
The device takes temperature readings, updates the display and device shadow, and when a set point is reached, alerts the user. The Alexa skill can tell the user what the temperature is at any time, even if the device is asleep, by accessing the shadow data in the AWS cloud.
When the user hears or sees the alarm they can ask Alexa to go to the next step, and then Alexa will send the desired state (same recipe, step 2) to the shadow, and the device will switch to that state using the recipe data it has on flash memory.
There's a bit more to it, which I'll go over after we get the project built.
Setting UpYou'll need a Mac, PC or Linux system to run the Arduino IDE from, program the devices, and upload files to AWS.
Start by either cloning or downloading the project from GitHub. If you downloaded the ZIP, extract that somewhere.
Install the Arduino IDE.
The Build (Hardware)First, we'll wire up the Arduino Uno and the peripheral components.
If you have access to a laser cutter, make an acrylic or wood template for the display and button. Otherwise, you can just connect these to the breadboard.
Attach the Arduino Uno and a full size breadboard to the base.
Solder hookup wires to the audio jack, neopixel ring, and the button. This allows you to just plug the ends into the breadboard. If your DS18B20 probe doesn't have a 3.5mm plug, solder one to it. Make sure the jack wire colors match the plug wires. (red, black & yellow typically)
Attach M3 standoffs, the button, and a 3.5mm audio jack to the small panel. Then attach the neopixel ring (the one I bough from the Arduino store fit perfectly) with scotch tape. Attach the panel to the base with M3 nuts.
Connect jumper wires from 5V and GND on the Arduino Uno to the left power bus on the breadboard. You may want to label this side - you do not want to connect the ESP8266 to 5V.
Connect the 5V and GND wires of the neopixel ring to the bus, and the Data In wire to Arduino Uno's digital pin 6. Pin 6 will control the neopixels digitally.
Connect one leg of the pushbutton to the 5V bus. Connect the other leg to a strip on the breadboard, and connect that strip to digital pin 12 with a jumper, and to the GND bus through a 10K ohm resistor. Pin 12 will normally read LOW. When the pushbutton is closed, it will pull pin 12 HIGH.
Connect 5V and GND from the DS18B20 jack to the bus, and connect the yellow data wire to a strip on the breadboard. Then connect that strip to 5V through a 4.7K ohm resistor, and connect the strip through a jumper wire to digital pin 10. Pin 10 will serve as the OneWire bus to communicate with the probe.
Plug a 5V piezo buzzer into the breadboard. One leg should be labelled (+) - connect a 100 ohm resistor to that and connect a jumper from the other end of the resistor to digital pin 9. Connect the other leg of the buzzer to GND. When the alarm sounds, a pulse is sent to the positive lead causing the buzzer to emit sound. The pulse width can be controlled in the code to make the best (loudest) sound.
Now we have the basic Arduino Uno circuit.
Open the Arduino IDE. Before we program the Arduino Uno, open the library tab and install the DallasTemperature library. (Sketch | Include Library | Manage Libraries, then filter "Dallas").
Select the latest version, then click Install.
Then search for and install FastLED the same way.
Close the library manager.
The only other library this Arduino Uno sketch uses is pre-installed, Wire.
Now open the ThermometerUI sketch, connect the USB cable from your computer to the Arduino, and then select the board and port in the IDE. (Tools | Board). You want Arduino/Genuino Uno.
Click upload
Once the sketch is uploaded, the device will reset and start running the code.
The neopixel ring should pulse / breathe an amber color. The piezo buzzer should beep three times, quickly.
Open the serial monitor. Set the baud rate to 9600. You should see the button state and a number. Each dot is a loop, the number is the temperature reading in degrees C roughly every 10 seconds.
Press and release the button and the state should display.
Congratulations, you just built half of the thermometer!
Build Step 2 - Adafruit Feather HUZZAH ESP8266If your ESP8266 device doesn't have headers, go ahead and solder them on. Adafruit's guide has instructions. Then place it in the breadboard at the top.
We are going to verify the ESP8266 can control the Arduino Uno first, and then add the display.
Refer to this guide & diagram if using an Adafruit Feather, it shows the pin names.
Connect a jumper between the Feather's GND pin and the GND bus. This ensures both the Uno and the ESP8266 have the same ground reference. (DO NOT CONNECT the ESP8266 to 5V)
Using two jumpers, connect SDA (GPIO4) and SCK (GPIO5) of the ESP8266 to the corresponding pins on the Arduino Uno (SDA is A4, SCK is A5).
Follow Adafruit's guide to setting up the Arduino IDE for the HUZZAH, including the blink test and the WiFi test, then come back... It's a great, thorough guide, and you'll need to confirm your IDE has everything needed for the ESP8266 before we move on.
OK, we're going to test the I2C communications between the ESP8266 and the Arduino Uno. We have a special test sketch just for that, Load the TestThermometerUI sketch into the IDE, ensure the right board (ESP8266) and port are selected, and then upload to the board.
Open the serial monitor at 9600 baud. You should see a repeating output - a line of 0xFF and a line with 65535 on it. That's fine, the Uno is not powered on.
Connect a USB battery to the Uno to power it.
Alternatively, you may carefully connect the VBUS (not any other pin, be careful!) from the ESP8266 directly to the VIN pin on the Arduino to power it. Since the HUZZAH runs on 3.3V, we must be careful not to connect any other pins to the Uno, or we might damage the ESP8266.
You should now see a much lower number, which is the temperature in Celsius, in the serial monitor. This is the integer value of the first two bytes. The ESP8266 is requesting 6 bytes from the Uno every loop, and the remaining bytes are filled with spaces. If you connected more thermometer probes to the Uno, you'd send them in these other slots.
Now at the top of the serial window, type "sg" and hit return (or click Send).
The color of the neopixels should turn to green.
You can type "sr" (red), "sb", (blue), or "sa" (amber). By adding to the Uno's code you can support many other shades. Any other letter after s will turn the color to black (off).
Type "a1", the piezo buzzer should sound. Press the button, or send "a0", and it will stop.
This shows that the ESP8266 can control the Arduino. Now we'll add the display and start connecting the device to the AWS IoT cloud.
Build Step 3 - e-paper displayAttach the e-paper display the panel, if you're using one, and connect the 8-pin connector supplied, then carefully route the bundle of wires over to the ESP8266. I used an elastic and wire ties, and extended the female ends with color coded male-to-male jumpers.
Connect the red 3.3V wire to the 3.3V output of the ESP8266, and GND (black) to GND.
Connect each colored wire to the ESP8266 as follows (they are all on the same side, so from left to right)
- GPIO2 - RST (white)
- (skip GPIO16)
- GPIO0 - DC (green)
- GPIO15 - CS (orange)
- GPIO13 MOSI - DIN (blue)
- GPIO12 - BUSY (purple)
- GPIO14 SCK - CLK (yellow)
Stick some rubber feet under the acrylic base, and the device is ready!
If you'd like to, you can test just the display right now, by installing the E-paper library and demo code. But that's not necessary.
Next, we'll set up the AWS IoT and configure the device and program the ESP8266.
Build Step 4 - AWS IoT and ProgrammingFirst, open the ThermometerAWSIoT sketch in the Arduino IDE.
You'll notice several tabs. Copy config-sample.h to a new config.h tab or just create a new tab called config.h, then paste this in:
// KEYS
// Keep this in config.h and keep it private!
// Even so, keep your devices secure, do not use this method
// for a production device
/*********WiFi settings*******/
#define WLAN_SSID "your-wifi-ssid"
#define WLAN_PASS "your-wifi-passphrase"
/*****************************/
/*********AWS IoT Thing settings*******/
#define AWS_REGION "us-east-1"
#define AWS_SERVERPORT 443
#define AWS_ENDPOINT "your-endpoint.iot.us-east-1.amazonaws.com"
#define THING_ID "your-thing-id"
#define AWS_KEY_ID "your-aws-key-id"
#define AWS_KEY_SECRET "your-aws-key-secret"
/*********S3 Settings********/
#define THING_DATA "http://s3.amazonaws.com/my-bucket"
#define RECIPES_URL "http://s3.amazonaws.com/my-bucket/recipes.json"
This is your unique configuration, and you shouldn't share this with anyone.
We'll come back to this several times to fill in the items. You can replace the placeholders in quotes with your wifi WLAN_SSID and WLAN_PASS now.
You'll need an AWS account and an Amazon Developer account. You can sign up for one if you don't already have one. If you use a region other than us-east-1, replace AWS_REGION in config.h.
AWS_SERVERPORT will not need to change, it's SSL port 443 for the code we are using.
4.1 - AWS IAM SetupFirst, we need a Thing User and policy.
Go to the AWS IAM console.
Click Create Policy, then click the JSON tab, then replace the contents with this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Connect",
"iot:Publish",
"iot:Subscribe",
"iot:Receive",
"iot:GetThingShadow",
"iot:UpdateThingShadow",
"iot:DeleteThingShadow"
],
"Resource": "*"
}
]
}
Then click Review Policy, and supply the Policy Name (IoTAccess) and click Create Policy. This is the access policy for accessing IoT.
Now go to Users and click Add User, then give the new user a name IoTDevice_user.
Click Create group, name the group IoTDevice, then attach the policy you just created. (Filter the long list like below)
Then click the Review button bottom-right, review this page:
Click create user. You should see a confirmation page. Download the credentials.csv to a safe place. You can also click Show before closing the window to get the secret.
You just created a user in your account with limited access, only to IoT APIs. These credentials you saved will be used to give the Arduino code access to the AWS IoT. We'd use certificates, but the libraries for ESP8266 are a bit limited and take up memory on the device. More on device security later.
In the Arduino IDE, update config.h with the Access Key ID and the Secret:
#define AWS_KEY_ID "your-aws-key-id"
#define AWS_KEY_SECRET "your-aws-key-secret"
While we are in the IAM console... if you do not have AWS CLI set up, we'll need to do that.
Under users, find your username. If the only user shown is "adminuser", then create a new user with your name in the Administrators group. This is optional but safer should your keys become compromised, and the best way to access your AWS resources instead of through a root account.
Click the user with Administrators group access, click security credentials, then click Create access key.
You'll again be prompted to grab or download the credentials. These are admin credentials for administering your account, keep them safe...
Follow the AWS CLI user guide to install aws cli, then configure in Terminal with "aws configure
". Then use the credentials you just downloaded. This gives you the ability to access AWS and configure things from your PC or Mac's command line. We'll use this when you upload the Alexa skill code.
Next, we need a Thing
4.2 - AWS IoT Thing SetupGo to the AWS IoT Console. You'll want to choose a region that supports Alexa Skills and IoT. I'm using us-east-1.
We'll create a Thing Policy first.
Click Manage, then click the Secure menu item, then Policies. Click Create. Call this ThingGeneral.
Under Advanced, paste this to replace the JSON, then create the policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:*"
],
"Resource": "*"
}
]
}
This is very permissive, but if you have no other things, it shouldn't matter. The IAM guide explains how to restrict policies to specific resources.
Now finally we can create a Thing
Click Manage, then click the Create button. Then click Create Single Thing.
Type in the name for your thing Thermometer1 then Next
On Step 2/3 click Create Certificate next to One-click certificate creation.
Create a directory on your computer (name it for your thing) and download each item and the Root CA. We don't need them right now.
Click Attach a Policy. Choose the policy you just created, then Register Thing.
You'll return to the Manage page and your Thing should be on a card. If you have lots of things you can search by name.
Click the card to open the Thing management page:
That long string is the AWS ARN (resource id) for your thing, which you can use in IAM to restrict resource access. We don't need to do that now.
Click Interact in the menu on the left, then copy the REST API Endpoint to the Arduino IDE into your config.h
And insert the Thing name too:
#define AWS_ENDPOINT "your-endpoint.iot.us-east-1.amazonaws.com"
#define THING_ID "Thermometer1"
4.3 - AWS S3 Bucket setupThe ESP8266 is downloading the recipe data from S3, which is Amazon's data store, via http to its flash filesystem. That way we can add recipes without reprogramming the device.
Go to the AWS S3 console. Click Create bucket, then give the bucket a name. my-bucket probably won't work, they must be unique. Create the bucket, clicking Next through the settings but on step 3, give your bucket public read access.
Replace the my-bucket part of the config.h with your bucket name (in two places).
Now upload your first file - open the bucket, choose Upload, then navigate to the project's assets/thing-data folder and select recipes.json
Upload it and choose "make public"
Then verify you can access the bucket in a browser with the second URL in the config.h.
/*********S3 Settings********/
#define THING_DATA "http://s3.amazonaws.com/my-bucket"
#define RECIPES_URL "http://s3.amazonaws.com/my-bucket/recipes.json"
4.4 - Program the ESP8266You've got everything in place, so now, in the Arduino IDE, in the ThermometerAWSIoT sketch, confirm you've updated all the settings in config.h and save it.
Make sure you've installed the ESP8266 hardware libraries which are noted in the Adafruit docs above.
Install these additional remaining libraries. Some hosted on Github will require that you download a zip, extract that, and place the folder in your Arduino/libraries folder, others can be downloaded with Library Manager:
- AWS Websocket Client and dependencies (from github)
- (The MQTTPaho client you need is here)
- ArduinoJSON via the Library Manager
- GxEPD (from github)
- Adafruit GFX library via the Library Manager
And then build and upload the sketch.
Then open the serial monitor at 115200 baud.
You'll see a lot of debugging and the neopixel ring will cycle through amber, blue (wifi connected), and green (IoT connected).
The e-paper display should show the current air temperature. If you place the probe in a glass of hot water, the temperature should increase.
sending I2C command sa
connecting to wifi
scandone
......................................................scandone
.scandone
state: 0 -> 2 (b0)
state: 2 -> 0 (2)
reconnect
scandone
state: 0 -> 2 (b0)
state: 2 -> 3 (0)
state: 3 -> 5 (10)
add 0
aid 3
cnt
connected with WIFI_SSID, channel 9
dhcp client start...
ip:10.0.5.33,mask:255.255.255.0,gw:10.0.0.1
connected
sending I2C command sb
setup recipe data
Requesting URL: http://s3.amazonaws.com/my-bucket/recipes.json
ETAG at URL is "a3ccd988020041ad49f184ccd0699795"
Last-Modified: Thu, 25 Jan 2018 22:17:47 GMT
ETAG of file is "a3ccd988020041ad49f184ccd0699795"
/recipes.json has not changed!
13684 - conn: 1 - (27872)
pm open,type:2 0
updating device shadow
$aws/things/Thermometer1/shadow/update
29
{"state":{"reported":{"temperature":29,"mode":"measure","alarm_high":0,"alarm_low":0,"step":0}}}
DISPLAY heap size: 26976
_PowerOn : 72770
_Update_Full : 1101198
_PowerOff : 68691
Requesting register from I2C slave
0x0 0x1D 0x20 0x20 0x20 0x20
New temp received: 29
Temperature is nominal
The device should be connected to AWS IoT - to confirm, open the IoT page for your Thing, then click Shadow.
You should see the Shadow State with a reported section. This is the data your device is sending, each time the information changes. Warm or cool the thermometer and the temperature entry should change.
You can test the complete connectivity by updating the desired state of the device shadow. Click Edit, then insert the desired portion below, paying careful attention to commas. You can't save until you have valid JSON.
{
"desired": {
"mode": "yogurt",
"step": 1
},
"reported": {
"temperature": 28,
"mode": "measure",
"alarm_high": 0,
"alarm_low": 0,
"step": 0
}
}
Watch the serial monitor, the new state should appear almost immediately and the display should update.
The serial monitor reflects the update:
Message 2 arrived: qos 0, retained 0, dup 0, packetid 0
Payload {"version":14,"timestamp":1519072074,"state":{"mode":"yogurt","step":1},"metadata":{"mode":{"timestamp":1519072074},"step":{"timestamp":1519072074}}}
parsed Json
Response:
0
0
Desired step: 1
parsed Json
yogurt
Yogurt
step1
82
0
0
Heating milk
updating device shadow
$aws/things/Thermometer1/shadow/update
28
{"state":{"reported":{"temperature":28,"mode":"yogurt","alarm_high":82,"alarm_low":0,"step":1}}}
Your display should say "Heating milk"
Congratulations, have a working, connected device.
You are now ready to add the last and most useful part - the Alexa Skill.
Build Step 5 - Alexa SkillThe Skill involves both AWS and Amazon Alexa resources. First, we'll create the AWS resources including some Lambda code to handle the Skill logic, create a few DynamoDB tables for the user state, then set up the Alexa Skill configuration, and then update the Lambda function's attributes.
5.1 - AWS IAM (again!)We need policies for Lambda.
Open IAM Console and click Create Policy
Name this alexaLambdaThermometerRole
Under Policies, we need to attach three policies:
For the first two, click Attach Policy and just filter until you locate the matching policy (shown above) and attach it. For the third, create it with Add inline policy and paste this JSON and save:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "dynamodb:*",
"Resource": "arn:aws:dynamodb:us-east-1:*:table/*"
}
]
}
The policy user is your Lambda function. Its access can be further restricted in the future by specifying a list of resources or actions.
5.2 - AWS LambdaThis is the heart and soul of the "smart" part of the project. We'll upload from the command line and then visit the console to test and monitor.
Open a Terminal or Command Line window and navigate to the project.
First we'll set up Node.js (if you don't have it installed).
$
cd skill
$
npm install
If that fails, then visit https://nodejs.org/en/ and either download for your system or use a package manager (such as HomeBrew on MacOs "brew install node") to install node.js.
Once node is installed, repeat "npm install" in the skill directory. You should see a confirmation that node_modules were created. These are library files that need to accompany the index.js file when it's uploaded to AWS.
You should have already set up AWS CLI, if not, please do so now.
If you didn't set up an environment variable yet for your AWS account id, do that now:
$ export AWS_ACCOUNTID=899999999
(You can get this from the AWS console in My Account | Account Settings)
In the main thing-thermometer directory, type
$ ./scripts/createlambda.sh skill ThermometerSkill
This runs a bash script that zips up the lambda code and uploads and creates a new lambda function called ThermometerSkill
At the end of all the output you'll see a bunch of JSON. This confirms the creation and supplies some parameters and an id for your Lambda function.
If you make changes to index.js, you can upload those with updatelambda.sh, the same way.
Now open the AWS Lambda console in your browser.
You should see a list of functions. Click the ThermometerSkill name to open that up. It should look like this:
There's no trigger, but we'll take care of that in a later step.
Scroll down, there's a section called Environment variables. These can be entered via the AWS CLI as well, but we'll add some here now. The lambda code uses these so you don't need to hardcode the values. Insert each of the following, for example:
DYNAMODB_STATE_TABLE ThermometerSkillState
DYNAMODB_THING_TABLE Thermometers
THING_API api-endpoint.iot.us-east-1.amazonaws.com
CDN https://s3.amazonaws.com/my-bucket
APP_ID (blank for now)
Once they are all entered, click Save at the top of the window.
5.3 - DynamoDBAlexa will automatically create the State Table we referenced in the last step, but we need to set up the Devices table.
Go to the DynamoDB Console.
Click Create Table, name the table Thermometers, and supply userId as primary key.
Click create.
This table is used to look up users and find out which thermometers they can control. In a production system, you'd need a way for users to set up their device and automatically add them to this table. But today, we can manually create that link.
Once the table state says Created, click the Items tab. The table will be empty. Set up the device here by clicking Create Item
For now, type "me" in the value space, then click the (+) then Append | String and type the key thingId and value Thermometer1 and click Save
The thingId key must match a Thing Id. The userId should be an Alexa user id.
The Alexa skill code looks up users here, we'll change "me" to your Alexa account ID later.
5.4 - Alexa SkillGo to https://developer.amazon.com/ - create an account if you don't have one yet. You can sign in with the same email and password you use on Amazon.com if you like.
Open the Alexa Skills console and click Create Skill. Name it ThermometerSkill
Then select Custom Model and click Create Skill
You should see a new style console that looks like this:
For a quick start, click the JSON Editor link on the left, then find the interactions.json file in the project's assets directory (thing-thermometer/assets/interactions.json), and upload it. Then click Save Model.
This will set up several intents and slots, enough to work with the rest of the code.
The main intent here is cookSomethingIntent and the main slot is foodToCook.
The invocation name is kitchen gadget, but you can change it to Fred or Crazy Eddie, or something else, if you prefer. And you can edit the interaction model to customize as needed.
To just change the Invocation Name, click Invocation on the left.
Click Build Model. It will take a minute or two. Once built, go to Interfaces, click the toggle to turn on Display Interface.
In the URL of the console. look for amzn1.ask.skill.bunch-of-digits and copy this to your clipboard. It's your APP ID.
Open your Lambda function in the AWS console and click Alexa Skills Kit under Triggers, then down the page, paste that APP ID into the Configure Triggers space:
Then click Add, then Save.
Add that same app id to the Environment variables section we set up earlier, then click Save again.
Then copy the function ARN displayed on the top right side of the page to your clipboard. It should look like arn:aws:lambda:us-east-1:899999889:function:ThermometerSkill
In the Alexa Skills console click Endpoint, check the radio button for AWS Lambda, then paste the ARN of the function into the first slot Default Region.
Then click Save Endpoints.
A bit tricky, it seems it must be done in that order, but the Lambda and Skill should be connected now.
Go to Invocation again and click Build Model.
Now click Test at the top and toggle testing on.
Click and hold the microphone icon on the page, and say "Alexa, open kitchen gadget", then release the microphone icon. You should get a response. JSON is displayed on the right for debugging and testing.
Note: the first time you invoke the skill, it will create the state table in DynamoDB. If you get an error, it may be because the table was not ready. Wait a minute and try again. Verify the new table has been created in the DynamoDB console.
Look for the entry "user" in the "session" section. The userId there is what you'll need to insert in the DynamoDB Thermometers table, to replace the "me" placeholder.
Do that now, confirm you want to override the unique key, and save the change to the DynamoDB item.
Now return to the test console and try the invocation again.
Respond with "yogurt" and your device (if it's on) should change to cooking mode.
Have a peek in the IoT console to confirm the shadow is updated.
Congratulations! You now have a fully functioning skill with a connected thermometer.
Next, I'll summarize what we accomplished and what some next steps might be.
Wrap UpThe complete solution involves a number of pieces interacting, but each component is relatively simple.
The full code for the project is on github. The README there will discuss some of the components of the code in more detail that I can here, and will be updated as further features are developed. The code is fairly well commented.
We created a device with a user interface. The prototype is not pretty, but it allows further experimentation.
It measures liquid temperatures on the stovetop, and has a visual and audible alert.
It has a WiFi connection so it can send its state to the AWS IoT Device Shadow, and receive desired changes, as well as get any updates to the list of recipes.
And it has Arduino code so that it can manage all of this.
We created an Alexa skill, with Lambda code and supporting resources, to handle the voice interaction, so you can get the current status of the thermometer from anywhere in or outside of your home.
Next StepsThe Alexa skill could be augmented by adding additional fields to the recipes data - links to websites and cooking instructions, maybe even videos for users with Alexa devices that support video.
The recipes list is easy to update, to add roast chicken, roast turkey, brisket.
If we want to measure the temperature of meats, or the chamber temperature of a smoker, we can add some common thermistor probes. A resistor divider circuit and some simple code will allow us to add those to the buffer data shared between the Arduino and the ESP8266.
Indicating which probe should be used for each measurement can be done in the Arduino code and the Alexa app.
We might want to add a "maintain" mode for the smoker, with a target temp and warn the user if the temperature went more than 10-20 degrees above or below that. And perhaps a timer, so we can let the brisket smoke for several hours.
Notifications are possible with Alexa, but for boiling milk, not the right choice. However, for a long smoke, Alexa could remind us that it's time to take the meat off.
More complex, but the on-boarding of a new user and device would require a web page, changes to the device code to capture the WiFi credentials, and preferably, use the device certificates and a communication protocol called TLS 1.2 to secure access to the IoT shadow, along with more restrictive IAM policies and maybe a hardware encryption module.
We'd want to allow multiple devices per account.
Some users might want to create their own recipes, or customize the target temperatures.
Users in the US would likely want the temperature in Fahrenheit. We could ask them their preference or allow them to set one.
I used an e-paper display because it uses very little power. It's also easy to read in bright sun. I'd like to utilize the deep sleep and wakeup capabilities of the hardware (both the ESP8266 and the Atmel micro-controllers in Arduino boards support sleep modes) and a rechargeable battery so the device is more portable and could last all day or all weekend next to the smoker.
And finally, the components we used in the device could be optimized, a PCB and enclosure designed, and manufactured and sold.
But we've got a decent start here, with a workable dialog and usable device.
Read on for some of the deeper content I've glossed over in the build.
Deeper Dive - AWS IoT Device ShadowUsing AWS IoT enables both Alexa and the device to work together without needing to communicate directly.
The device shadow represents both the current state of the device, and the desired state supplied by Alexa. So it's simple for Alexa to update the state with the desired recipe step, and to obtain the current reported temperature. It also enables the device to communicate intermittently - only reporting the state when it's changed - and any number of systems could monitor and obtain that state independently.
Deeper Dive - The Voice User InterfaceFor the Alexa skill, I looked at the available Alexa Home API capabilities. That lets you set up a device that can be controlled by Alexa - we could use "thermometer sensor" or "thermostat". You can change the setpoints.
But I wanted Alexa to be smarter... and set the temperature based on the recipe - if you're making yogurt, you actually have two steps: first, heating to 82C (or 170F), then a human has to shut off the heat, then you wait til the temperature is down to 42C (120F). It seems better to have a custom Alexa Skill take care of that.
We'd just say:
"Alexa, ask kitchen helper to make yogurt"
Wait, that's it? Not quite.
I white boarded several scenarios and over time added some extra use cases, to cover things that might happen during the process.
So the basic conversation is like this:
But during a couple of test runs testing, my wife - reading her Kindle on the sofa while the milk came to a near boil - would ask me "how much longer". While that's hard to predict, we can get Alexa to handle "What's the status?" or "What's the temperature now?"
Also, considering that if I make and sell these, people might start their first conversation with Alexa using any of these phrases, after
"Alexa Open Kitchen Helper"
"What can I make?"
"How do I make yogurt?"
"I'd like to make brisket!"
While we're mid-recipe making yogurt: "Make ricotta"
So the Voice UI needs to handle a lot of edge cases and at least two states: making a recipe and not making a recipe. And perhaps cancelling the current recipe.
To handle the different modes, I used the State Management provided by the Alexa SDK. The user's state is saved across sessions in a DynamoDB table, and fetched for the next interaction. The intent is channeled to a handler that matches the current state, so that when we're in a recipe, "go to the next step" is properly handled, but if we are not in a recipe, Alexa knows and lets the user know that command can't be fulfilled.
"Alexa, ask Kitchen Helper to go to the next step"
"You're not in a recipe right now. What would you like to cook?"
The initial version of my code had yogurt and ricotta hard-coded. It was helpful to test, but then, refactoring, I moved all the recipe-specific information to a separate data file. This file is in JSON format, which is easy for the code to read and convert to a data structure (or object). It consists of just a list of recipes, some intro text, and a list of steps. The version for the device is just a subset, to keep the size small - and creation of that can be automated.
I also created some utility functions that obtain or set the device shadow data. Here's a function from the Lambda code:
/*
* request a device shadow change and then respond to the user
*
* update contains these attributes:
* * desired - the desired state to send
* * responseText - text for Alexa to speak (optional)
* * prompt - an optional question to ask (optional)
* * displayText - text to put on a device, if supported (optional)
*/
function updateDevice(update) {
console.log("controlling a device");
console.log(JSON.stringify(update));
// the user's thing id to update
let thingId = this.attributes["thingId"];
// save "this" so the callback can reference it
let sess = this;
if ( !thingId ) {
console.log('Update: no device id');
return;
}
// IotData is the IoT API
var thing = new AWS.IotData({endpoint: process.env.THING_API});
// set up the data to send to the API
var payload = {
state: {
desired: update.desired
}
};
var params = {
thingName: thingId,
payload: JSON.stringify(payload)
};
// Perform the update. The callback receives the result
thing.updateThingShadow(params, function (err, data) {
console.log("returned from shadow update");
console.log(update);
// err is an error, log it until we see some real ones
if (err) {
// do something
console.log('updateThingShadow error:');
console.log(err);
sess.emit(':tell', "Oh gosh, something went wrong!");
} else {
console.log('updateThingShadow success:');
console.log(data);
// if the responseText is set, then call showTemplate
// to respond to the user with voice and text on supported devices
if ( update.responseText ) {
showTemplate.call(sess, update);
}
}
});
}
The code above handles the asynchronous device update and then responds to the user if necessary (by calling showTemplate),
The showTemplate code is below:
/**
* display a template and end
*
* all params are optional
* responseText - will be spoken
* displayText - will appear in template
* prompt - add a listen
*/
function showTemplate(params) {
if ( params.responseText ) {
console.log("speaking: " + params.responseText)
this.response.speak(params.responseText);
}
if ( params.prompt ) {
console.log("listening: " + params.prompt)
this.response.listen(params.prompt);
}
if (params.displayText && supportsDisplay.call(this)) {
// utility methods for creating Image and TextField objects
const makePlainText = Alexa.utils.TextUtils.makePlainText;
const makeImage = Alexa.utils.ImageUtils.makeImage;
const builder = new Alexa.templateBuilders.BodyTemplate1Builder();
const template = builder.setTitle(this.t('SKILL_NAME'))
.setBackgroundImage(makeImage(CDN + '/gas-stove-bg.jpg'))
.setTextContent(makePlainText(params.displayText))
.build();
console.log("template: " + params.displayText)
this.response.renderTemplate(template);
}
console.log('saving state');
this.emit(':saveState', true)
this.emit(':responseReady');
}
Deeper Dive - The Device & CodeWiFi and IoT connectivity was the driving force behind the selection of the ESP8266. It'd easy to obtain and affordable. Unfortunately, it still has some memory limitations and some Arduino libraries do not support it. But I was able to put the right pieces together to get it working. I tried using an ESP32 (more memory, more pins) but had enough stability issues (sudden hardware resets) that I put it aside. I might try it again, possibly using the Espressif compiler or Mongoose OS.
The other issue with ESP8266 is the number of pins and GPIO ports. It only has one analog input, and SPI for the e-paper display used up most of the other pins. Hence, the Arduino Uno. It can act as an I2C slave and handle the button polling and neopixel animation without impacting the WiFi and e-paper updates. Using multiple microprocessors is not uncommon.
I started to use an Arduino MKR ZERO for that purpose, it's small and low power. However, the Arduino I2C library for the SAMD hardware has not implemented the I2C slave mode I needed. I need to research other options.
With respect to the code for both devices, they went through several iterations and optimizations. I created simple wrapper functions that supported fetching the recipes file from the S3 bucket only if the etag (the version metadata sent by S3) had changed. I also created some code that would fetch a binary image and display it on the e-paper, but haven't used it yet because it brings the available heap down to almost zero. It would be great to display an image and put the device to sleep when it's not in a recipe mode, and have the user wake the unit up by pressing the button.
Here's calling the bitmap code during the display update:
uint16_t bmW, bmH;
uint8_t *myBitmap = getBitmapFromFS("/yogurt.bin", bmW, bmH);
display.drawBitmap(myBitmap, 0, 30, bmW, bmH, DISPLAY_FGCOLOR);
getBitmapFromFS() uses the HTTP (web request to fetch a file from S3) and SPIFFS (local filesystem in flash memory feature of ESP) features in the helper copyUrlToFile(fileurl, filename).
That helper function requests the file from the web server, but if the file's etag stored in SPIFFS is the same, it does nothing else. If it differs, it saves the file and the etag in separate files. It only needs to do this once for each file when the device boots up. While the program runs, it accesses the file directly.
I created these because I felt that while you can program the SPIFFs from the Arduino IDE, it's really hard to update the data (such as the recipes, or bitmaps to display) once the device is in a user's hands. Now, each device should automatically get and store the latest copy.
These helper functions are all in thingdata.h
The certificate vs key and secret is definitely a blocker to commercial products, certificate is more secure and manageable, but the available libraries for ESP8266 seem to skip some parts to save memory and still used up quite a bit. So the current solution, which is using a key and secret and websockets over SSL, is sub-optimal but ok for prototyping. The PubSubClient on ESP32 worked - after hacking the max data size - but something kept resetting the device.
My first prototype had things dangling from the breadboard, so the next used a wooden box with holes drilled. The current solution - an acrylic base with breadboard and a panel mount - helps keep things a bit more organized. The main issue with a prototype for a device like this is portability. Having the wires come loose when the probe tugs at the breadboard is more than annoying.. so, with my laser cutter, I created a panel with standoffs and a breadboard.
FinallyI hope you enjoyed the build, and it's generated some ideas for your own next project.
Comments