Skip to main content

How to build your own smart coffee machine

Raspberry Pi Barrista

Outputting from the Raspberry Pi

The buttons now need to actually control mechanical or solid state relays or set points within your coffee machine. To do this you need to tell your Raspberry Pi to drive outputs high or low or just change set point values. 

Luckily this is super easy. First of all, set which GPIOs are going to be outputs. Grab that Raspberry Pi Python module by adding the following to the top of your script:

import RPi.GPIO as GPIO

In this case you will be referring to the IO pins on Raspberry Pi as per the Broadcom SOC channel, so therefore add the following directly below the previous line:


Now to set the following as outputs: coffeeButton, which will switch a Double Pole Single Throw relay GPIO 20 by adding the following below the previously explained code:

GPIO.setup(20, GPIO.OUT)

One pole will turn the pump on or off. The other pole will direct heated water through the 2-by-3 way solenoid valve’s vent or through the group head. 

Most coffee machines will have a 2-by-3 way solenoid valve to vent pressure when deactivating the coffee switch. This is a safety mechanism to ensure no trapped pressure is exerted over the user when removing the group head.

No need to use a GPIO for steam because you don’t need to output to a relay. You will only need to increase the set point. Coffee’s set point will be around 105°C and steam will be around 140°C. The set point will drive the PID controller to heat the boiler to 140°C. 

The steam pressure will be the driving force, therefore you won’t need to  activate the pump.

However you will need to control the boiler, so set the boiler to

GPIO.setup(23, GPIO.OUT)

The last output is water, which will only turn the pump on. You only need a Single Pole Single Throw relay for this output:

GPIO.setup(19, GPIO.OUT)

You could use another DPST for this to keep parts standard on the printed circuit board (as per the ingredients list).

Now that your outputs are set, time to hook them up into your button callback methods. Do this simply by calling the output() method of the GPIO class. When you press ‘coffee’ you want the pump to kick in and the 2-by-3 way solenoid valve to output to the group head. 

Therefore drive the output high under the coffeePress_callback() method by adding GPIO.output(20, 1)

When you depress the coffee button you want the pump to stop and the 2-by-3 way solenoid valve to vent, therefore drive GPIO 20 low by adding GPIO.output(20,0) under the coffeeRelease_callback() method.

For steam you are only changing the set point, therefore create and define an attribute within the CoffeeApp. Add SP = NumericProperty(105) below to define the coffee app class. 

Now add self.SP = 140 and self.SP = 105 under steam press and release methods respectively.

You want the pump to kick in/out when you press/release the water button, so therefore add GPIO.output(19, 1) and GPIO.output(19, 0) under waterPress and waterRelease respectively.

Even though the GUI looks super basic at the moment, it doesn’t take much to turn it into a visual masterpiece.

Even though the GUI looks super basic at the moment, it doesn’t take much to turn it into a visual masterpiece.

Adding a graph widget

Finally, time to get some plots plotting. Every IoT gadget needs a dashboard and every good dashboard needs a plot. 

Import that Graph and LinePlot module by adding to the top of your script:

from import Graph, LinePlot

Now create a widget class for your graph using class PlotterWidget(Graph) and of course initialise specific attributes within your plotter widget by calling the def __init__(self, **kwargs) method. 

There are a lot of attributes to define, so check out GitHub user de-man for the code. Such attributes include grid sizing, border colours, y/x max/min, etc – it’s all very customisable.

Now when you run that script you can see the coffee GUI taking form. The graph is on the left and the buttons should be arrayed vertically on the right.

Animating your graph 

Incorporate animated line plots to your graph so you can get a visual look at the temperature control and the temperature history of your coffee machine. 

You will need to add two plots to the graph. One will be called SPplot . This is the set point which will tell the PID controller what value to drive to. 

The other will be called PVplot . This is the process value that will be read by the temperature probe from your machine. You will need to add in a couple of time methods and attributes in order to animate the graph. The main code snippet to note is a Kivy clock event:

Clock.schedule_interval(plotter.update, SAMPLE_RATE)

This will call plotter.update at a defined interval set by


Binding control to your graph

Now the animated line plots are scrolling along autonomously on your graph, but they aren’t actually linked to any of the controls. 

The SPplot needs to be hooked up to the steam button which controls the actual set point. Do this by adding one simple line of code after all your previous binding calls:

code self.bind(SP = plotter.setter('currentSignal')

Go ahead and run that code. When you press the steam button now and keep it pressed the SPplot goes from 105 to 140. 

Now release the button, and you will see SPplot drop back down to 105. Success. Time for a coffee break.

Relay driver circuits

The Raspberry Pi’s IOs generate 0V on a low and 3.3V on a high. The specified DPST relays need a DC coil voltage of 5.0V in order to switch, meaning that a driver circuit will be needed to boost the voltage of the Raspberry Pi’s output up to 5.0V and drive the coil to switch the relay.

Building a driver circuit is straightforward. First, grab a general purpose transistor such as a 2N3904. Then, create a circuit for the transistor that will allow it to run in the saturation region of operation during an ON (GPIO high). 

To do this, calculate the correct resistor value to obtain the saturation current needed and hook it up in series with the GPIO of the Raspberry Pi’s output to the transistor’s base. 

Hook up the transistor’s emitter leg to ground. The collector leg will be hooked up to the coil’s low rail on the relay. The coil’s input of the relay will be hooked up to a supply of 5V. This is a generic transistor switch setup. 

When the Raspberry Pi outputs 3.3V to the base of the transistor, the current will be enough (thanks to the calculated resistor value) to drive the transistor into saturation. 

This will then allow full current to flow from the 5V rail through the relay’s coil and then to ground. The coil in the relay will then actuate the mechanical switch controlling a higher load. Remember to incorporate a diode across the relay’s coil in order to eliminate back-EMF. This is a must as back-EMF has the potential to hurt the Raspberry Pi.

Getting temperature via OneWire

Temperature control is next, and is a bit more involved. This is because you need to set up reading the temperature input from the k-type thermocouple via the amplifier – a MAX31850K. 

This thermocouple amplifier is actually an integrated circuit that combines everything needed in order to read the thermocouple accurately and in the digital form using OneWire. 

Luckily, the Raspberry Pi was built to utilise the OneWire capability. You will now need to look at setting up OneWire within the Raspberry Pi. First enable OneWire with:

$ sudo rasp-config

Go to Advanced Settings, then Enable OneWire. You can also enable OneWire via:

$ sudo nano /boot/config.txt

and adding dtoverlay=w1-gpio to the file. After either option you must then $ sudo nano reboot . Once the Raspberry Pi is back up and running, hook up the MAX31850K (as per the MAX31850K OneWire instructions) to the Raspberry Pi’s OneWire pin, which is GPIO 4. 

Now add the appropriate modules into the Linux kernel – in this case w1-gpio and w1-therm: $ sudo modprobe w1-gpio then $ sudo modprobe w1-therm . Now go to the device directory where the OneWire temperature folder and file is stored: $ cd /sys/bus/w1/devices now $ ls .

Take note of the folder name as this will be specific to each OneWire device (an example would be 3b-000000191766). Navigate to that folder now and there will be a /w1_slave file. This file is where your read-in temperature value is stored.

Now read from that file by $ cat w1_slave – the output should be some hexadecimal values and if the data is being read successfully you will see for example crc=[hex_value] YES with t=23401. This is the temperature value read in by the probe, which actually means 23.401°C. Success!

Now incorporate what you have just learnt into your script. Add to your script the os module so that you can issue terminal commands within the script. Use import os , then use 

os.system('modprobe w1-gpio') 


os.system('modprobe w1-therm')

Remember when running this script, you may need to execute with a sudo. With your script having direct access to the temperature you can now write the appropriate code to validate and process the temperature values. 

Don’t hesitate to borrow the kettle from the kitchen.

Building the PID controller

Lastly you need to incorporate the PID controller. Make a specific class for this named PIDcontroller 

Within the class create attributes and methods that are applicable to the controller, such as error, lastError, SP, PV and updatePID. You need to call the controller at set intervals. 

Do this by binding another clock schedule interval to the controller. Also you need to make sure the temperature plot is hooked up to the actual temperature readings. In this case you read the temperature through the PID class. Do this again by using the bind() and setter() methods to link the attributes between the classes.

So that is that. You now have a basic Kivy app that can control a coffee machine to a better standard than most mid-range machines on the market.