Sponsored By

Bake Smarter with Bake Mate

Need a little bit of help in the kitchen? Here's how to build a Bake Mate—a baking assistant that weighs your ingredients, tells you how much of each you will need, and performs several other handy functions.

Avner Fernandes

October 10, 2018

11 Min Read
Bake Smarter with Bake Mate

This project was one of the winners of the Pi Chef Design Challenge sponsored by Element14.com.

CLICK HERE to download a fully detailed PDF of the Bake Mate build instructions with more graphics, photos, and full coding examples.

Here are a few reasons baking can be a hassle:

  • You need to keep referring to the recipe on the cookbook or computer.

  • It's easy to make errors when converting different units of measure (grams, ounces, pounds, pints, quarts, gallons, etc.), leading to all sorts of errors, such as setting the wrong oven temperature or using the wrong ingredient proportions.

  • You let something cook for too long.

  • Forgetting to add an ingredient.

  • Scaling: Ever needed to bake 1.5x or 2x the quantity? Scaling the ingredients (e.g., by doubling) isn’t difficult, but it is a hassle.

My solution to these problems is Bake Mate:

It consists of a Raspberry Pi 3, a 2.4" touchscreen PiTFT HAT, a load cell, and a thermocouple.

The Pi runs an application (built using Python & Tkinter) that displays the recipe. The load cell connected to the Pi weighs the mixing bowl and automatically detects how much of an ingredient has been added and notifies the user how much more is needed.

The recipes are formatted as JSON files that allow the application to parse data like the ingredients, instructions, baking time, etc.

The device also does automatic unit conversion. If the original recipe uses pounds or cups of flour, the application will automatically convert it to the desired unit (e.g., grams or ounces). Because the weighing scale is connected to the Pi, it will automatically detect the weight of the mixing bowl in an easy step and inform the user how much more of the ingredient needs to be added.

A typical ‘Bake Mate’ session would look something like:

  1. The user selects the recipe and places the empty mixing bowl on the scale.

  2. The Pi parses the recipe file and displays the ingredient that needs to be added: let's go with flour. The original recipe called for 4 cups of flour, but the application automatically converts it and simply shows the user how much more needs to be added—as a percentage of the required amount (in text and as a bar graph).

  3. The recipe now needs 2 cups of oil. Instead of measuring 2 cups separately and transferring it to the bowl, all the user will need to do is pour it in directly. The program converts the volume of oil required to a weight, which can be measured using the load cell (using the known density value, which will be stored in a lookup table). The user can simply pour the oil directly into the mixing bowl (which is on the load cell).

  4. When it's time to bake, the Pi will set an alarm for the required time. At the end, it'll notify the user.

PARTS LIST

 

Qty. 

Part

Digi Key#

 

 

 

1

Raspberry Pi 3

1

2.4" Touchscreen HAT (320x240 - 4DPi-24-HAT)

 

1

Load Cell & HX711 Amplifier

1

HC-12 wireless communication module 

1

Type K thermocouple & MAX31855 ADC

1

Sense HAT

 

1

Pi GPIO Breakout (optional)5V Cap

 

BUILD INSTRUCTIONS

1.) Setting up the Raspberry Pi 3:

  • Insert the microSD card that's been preloaded with NOOBS.

  • Plug in a USB mouse and USB keyboard and connect the Pi to a display via HDMI.

  • Connect the power supply to the Raspberry Pi 3.

  • Enter WiFi credentials and install Raspbian using NOOBS.

  • To get the Display HAT to work, install the manufacturer-provided kernel patch (since the display uses SPI instead of the native display connector).

2.) How to Code Recipe Files

I decided to store recipes on json files so that they’re easy to parse. I’ve detailed the format I used below.

Recipe JSON format:

  • A name

  • A description

  • No. of servings

  • Cooking time

  • No. of steps (initially added to make parsing easier, but I think I could get away without using this)

  • An array containing all the steps, where each step contains:

    • Sequence number

    • Text instruction (description)

    • Instruction type—different types include:

      • A simple 'do:' This type won't contain ingredient amounts because it is a simple instruction like "Stir" or "Melt chocolate."

      • Add measureable ‘addm:’ This type will contain the ingredient, the amount, and the unit. An instruction with this type of tag will cause the program to enable the weighing scale.

      • Add immeasurable ’addnm:’ This is for ingredients that are too small to measure or have no unit in general: a pinch of salt, a drop of flavoring, etc. I've added this because they count as instructions and will need to be present in the final ingredient checklist.

      • ‘Bake:’ This type will trigger the method that measures oven temperature and sets the timer.

    • Ingredient (name)

    • Unit of the ingredient: This will make it easier to automatically convert units.

    • Value of the amount of the ingredient

Sample Python code for parsing json:

import glob
import json

def getFiles(): #get a list of files
   fileList = glob.glob('*.json')
   return fileList

def getJSONname(s): #given the filename,
   data = json.load(open(s)) #create a JSON object
   print ("The recipe is",data["name"])
   return (data["name"])

def getJSONservings(s): #given the filename,
   data = json.load(open(s)) #create a JSON object
   print ("Servings: ",data["Servings"])
   return (data["Servings"])

def showJSON(data):
   print (" ")
   print ("The recipe is",data["name"])
   print ("Something about it",data["Description"])
   print ("Servings:",data["Servings"])
   print ("Cooking time:",data["CookTime"])
   nsteps = int(data["nSteps"])
   print ("Number of steps:",nsteps)
   print (" ")
   for x in range (nsteps):
       print("Step index" + str(x))
       print ("Step ",data["Steps"][x]["num"])
       print (data["Steps"][x]["ingr"])
       print ("Instruction type",data["Steps"][x]["type"])
       print ("Ingredient:",data["Steps"][x]["ingr"])
       print ("Unit:",data["Steps"][x]["ingrUnit"])
       print ("Value:",data["Steps"][x]["ingrValue"])
       print ("BakeTime:",data["Steps"][x]["bakeTime"])
       print ("BakeTemp:",data["Steps"][x]["bakeTemp"])
       print ("BakeTempUnit:",data["Steps"][x]["bakeTempUnit"])
       print (data["Steps"][x]["txt"],data["Steps"][x]["ingrValue"],data["Steps"][x]["ingrUnit"],"of",data["Steps"][x]["ingr"])
       print (" ")

fileList = getFiles() #get list of files
for filename in fileList: #for each file...
   print

("------------------------------------------------------")
   print ("File is ",filename)
   data = json.load(open(filename)) #createJSON object
   showJSON(data)
   getJSONname(filename) #get the recipe name

Sample recipe code:

{    "name": "Easy Chocolate Fudge","Description": "Quick and easy chocolate Fudge","Servings": "48","CookTime": "3 hours","nSteps": "6","Steps": [        {            "num": "1","txt": "Line a 8x8 dish with aluminum foil ",            "type": "do","ingr": "NA","ingrUnit": "NA","ingrValue": "0","bakeTime": "0","bakeTemp": "23","bakeTempUnit": "C"         },        {            "num": "2","txt": "Add chopped chocolate",            "type": "addm","ingr": "Chocolate","ingrUnit": "gm","ingrValue": "450","bakeTime": "0","bakeTemp": "23","bakeTempUnit": "C"         },{            "num": "3","txt": "Add a tin of condensed milk",            "type": "addm","ingr": "Condensed Milk","ingrUnit": "oz","ingrValue": "14","bakeTime": "0","bakeTemp": "23","bakeTempUnit": "C"         },{            "num": "4","txt": "Heat till it melts",            "type": "do","ingr": "NA","ingrUnit": "NA","ingrValue": "0","bakeTime": "0","bakeTemp": "23","bakeTempUnit": "C"         },{            "num": "5","txt": "Add Vanilla",            "type": "addnm","ingr": "Vanilla Essence","ingrUnit": "NA","ingrValue": "0","bakeTime": "0","bakeTemp": "23","bakeTempUnit": "C"         },{            "num": "6","txt": "Add Walnuts",            "type": "addm","ingr": "Walnuts","ingrUnit": "gm","ingrValue": "180","bakeTime": "0","bakeTemp": "23","bakeTempUnit": "C"         }    ],    "masks": {        "id": "valore"    },    "om_points": "value",    "parameters": {        "id": "valore"    }}

3.) Monitoring Temperature with the Type K Thermocouple + MAX31856:

Because the Display HAT uses hardware SPI, I decided to use a software (bit-banged) SPI to connect the MAX31856. Another issue is that the Display HAT doesn’t break out any of the unused pins, so I’ll be using a breakout board for now:

The MAX31856 board on the right: SPI, power, and GND lines on the top go to the Pi and the thermocouple is connected at the bottom. 

I used Stephen Smith's Python based MAX31856 library.

I placed the tip of the thermocouple on the Pi’s SoC and ran a test script to measure temperature.

4.) Testing the weighing scale: load cell + HX711

I built a test scale to test out the load-cell and HX711.

The load cell consists of strain gauges attached to the metal block. When a weight is applied, the block bends, which changes the resistance of the strain gauges. Monitoring the voltage with an ADC will allow you to measure resistance.

I used tatobari's HX711 Python Library, which contains instructions on how to calibrate the load cell and everything else you need to know.

For this quickly put together scale, I used some components I found lying around the house. I plan on designing and building a nicer one sometime soon.

5.) Software Architecture:

The code for the application is based on Python 3 and uses Tkinter for the GUI.

Here’s a quick look at how I arranged everything in the GUI:

Start Page:

This one is the simplest of all, and just contains a splash screen.

Menu Page:

This one isn't really needed at this point because I haven't started working on the part that lets the user create a recipe, which I hope to do soon. For now, the user presses on the button to "View Recipes."

Select Recipe:

This page displays a list of all the recipes in the folder, along with a short description of the selected recipe.

Scale Page:

After selecting the recipe, the user gets an option to scale the quantity. The application will automatically handle the converted weights of every ingredient, which makes everything easier.

When this page is displayed, a Python module called "thisRecipe" gets initialized with a bunch of values that will be used to keep track of the recipe's ongoing progress. This module will be used as a global variable that can be accessed by all the classes (which contain data for each page of the GUI).

Now that the first few pages are done, we move on to the pages that will actually display each step of the recipe.

I've broken the steps down into the four more probable types: “add measurable ingredient,” “add non-measurable ingredient,” “do (basic instructions like mixing, etc.),” and “bake.” These should be sufficient for baking, and I could always add more types if I ever encounter a recipe that needs it.

Each page will have a unique GUI and backend. The page for "add measureable ingredient" will need to access the data from the weighing scale using the HX711 library, which I've already covered and tested. The page for "bake " will contain a timer and will need to access oven temperature using the MAX31856 library. Each of these pages will also need to parse the required data from the step in the JSON file: weight or temperature. Naturally, all four of these pages have been designed as templates so that they can be reused. At each step, the required data is parsed and displayed in the respective field.

To keep track of each step, I use global variables that are declared in another file (thisrecipe.py). Because the four 'step' pages might be required in any random order (since it depends entirely on the recipe), I decided to implement it in a manner that certain tasks are performed when the particular page is first called (the enter tasks) and when it is left (the exit tasks).

The first time a page is invoked, it'll look at the current instruction number (in thisrecipe.py) and parse the required data from that particular step (weight, text, etc.).

Once the task of the page is done, the user presses on the "Next" button, which executes the exit tasks. The exit tasks basically increment the value of cur_step, and parse the instruction type of the next (upcoming step) from the JSON file. If the next step is "addm," the next page that is called is of the "add measureable" type. If it is a "do," the next page will be a "do" type; and so on.

Have a look at the block diagram above. The order in which the pages are invoked depends entirely on the recipe. At the end of every step, the next page type is determined and automatically invoked. Throughout the process, thisRecipe.py is used to keep track of the current step. Another parameter that needs to be tracked is the weight detected on the weighing scale so that the weight at each step can be calculated.

Connecting everything together.

6.) A Visual Indicator Using Sense HAT's RGB LED Matrix

I wanted to use the LED matrix on the Sense HAT as an indicator of how much of the ingredient has been added by making the matrix fill up as the required amount is added.

Using the Sense HAT on the Pi simultaneously with the Display HAT is complicated because HATs aren’t stackable, due to the identification EEPROM that is present on every HAT.

In order to get around this restriction, I used this code to control the matrix using I2C instead of using the library that comes with the Sense HAT.

7.) Mobile Notifications Using IFTTT

I made use of IFTTT’s notification service to send alerts when the oven’s temperature deviates too far from the temperature required by the recipe, and when the baking timer elapses (with a couple of notifications at predefined intervals in the middle).

Here’s an example of invoking an IFTTT notification from Python:

import requests
IFTTT_WEBHOOKS_URL = 'https://maker.ifttt.com/trigger/{}/with/key/'
def post_ifttt_webhook(event, value):
   data = {'value1': value} # The payload
   ifttt_event_url = IFTTT_WEBHOOKS_URL.format(event) # Inserts event
   requests.post(ifttt_event_url, json=data) # Sends a HTTP POST request to the webhook URL

post_ifttt_webhook('BakeMateTimer',75)

[All images courtesy Avner Fernandes]

Sign up for the Design News Daily newsletter.

You May Also Like