DIY Connected Espresso Machine: Boiler (Part 3)

Danila Loginov
4 min readJun 5, 2021


Last time we implemented the first abstraction for relays to control the boiler and the pump.

At the same time, the boiler has two thermistors on its side to determine the boiler temperature range. This gives us an opportunity not only to switch the boiler either on or off, but also to command what temperature we need — the first smart feature! So that’s what we are going to achieve today.


In my espresso machine, there are two thermoresistors that are basic circuit breakers using a bi-metal strip to control the current based on temperature. When temperature achieves a certain value one of the strip sides expands so the strip bends and opens the contact commutating the current.

A bimetallic strip (image from Wikipedia)

In other words, this can be considered as a button pressed by certain temperature conditions. One of the thermistors opens at 115 degrees C representing the temperature needed for espresso. The second one opens at 125 degrees C representing the temperature needed to make steam. Let’s call them “boiling” and “steam” values respectively.


If it is a button, we can wire one of its pins to the ground and another one to the input pin of the microcontroller. Using internal pullup we power up the input pin with the HIGH voltage by default (when the “button” is released, or a certain temperature is achieved).

Using Internal pullup (image from Arduino Basic Connections by

Until a certain temperature is not achieved, the “button” is pressed, so the thermistor contact is closed and the input pin goes to the LOW state since it is commutated with the ground.

Temp is not high enough — the contact is closed, input pin goes to LOW.
Temp is high enough — the contact is opened, input pin goes to HIGH.

Having this in mind, let’s first double the relays to control the boiler and then make three connections to thermistors: first is shared ground, second is for the “isBoiling” input pin, and third — for the “isSteam” input pin.

With Arduino Uno, I used A0 and A1 pins:

Boiler controlled by Arduino

With NodeMCU it was D5 and D6 pins:

Boiler controlled by NodeMCU


Boiler class extends the Relay class because it basically operates the relay, however, the behavior is extended: since we have information about the temperature we can command the boiler to achieve a certain temperature based on the thermoresistors state.

First, we define BoilerTemp ranges as an enum: Cold, Boiling, Steam — this clearly represents possible temperature ranges and eases operation.

Relay class inherited as private not to expose its methods (restrict from switching the relay directly) and three parameters are passed to the constructor: the relay pin, “isBoiling” pin, and “isSteam” pin.

getState() will shadow the Relay getState() method, while getTemp() and getTargetTemp() are the new getters to understand the current and target boiler temperature.

setTargetTemp(BoilerTemp) exposes control of the desired temperature. And the last, but not least work() method will encapsulate the algorithm to switch the relay based on temperature.

getTemp() implementation checks for the voltage from the input pins connected with thermistors and based on that returns the current temperature range.

work() compares the current temperature with the desired and switches the relay using the underlying Relay methods — to have proper reaction time, this method should be called as much as possible.

I believe there are many ways to implement even such simple functionality, however, having the behavior encapsulated in a class and proper interface, it can be easily done at any time during the firmware evolution!

Also with the enum describing temperature ranges, we can extend it with additional thresholds like AlmostBoiling, KindaSteam without rewriting the dependent pieces of code.


Test sketch is a bit more complex than in the previous episode since we need to iterate over possible temperature ranges.

Attention! To have thermistors reacting to temperature we need to wire everything, including the pump as well because we can’t heat up an empty boiler!

First, we instantiate the pump and boiler passing actual pins. Then we create two flags to remember passed states and prevent looping. In the setup() phase we turn on the pump to catch up pressure in the boiler and then set the target temperature to Boiling.

In the working loop(), we switch the target temperature if it was raised and call the method every iteration so it can timely react to the thermistors changing state.

Test Boiler

Next Steps

In the next episode, we will focus on the toggle mechanism in order to return back its capability to control the espresso machine.

The project code is available here: — it’s not finished by the moment I write this article so I will work on this during the next episodes.

That’s all for today, see you next time!

Next part: