PID Controlled Thermostat Using ESP32 (Applied to a Rancilio Silvia Coffee Machine)
Intro: PID Controlled Thermostat Using ESP32 (Applied to a Rancilio Silvia Coffee Machine)
Warning
The Rancilio Silvia machine runs on high voltage. Any modification to it might be fatal to you or your machine, you act at your own risk and responsible for your modification. Any modification to the machine might void the manufacturer warranty. Make sure to disconnect the machine from the power line before working on it.
What is a PID controller?
A proportional-integral-derivative controller (or just PID controller) is used to regulate speed, temperature, pressure or other process variable with high accuracy and stable results. I am going to use the Arduino PID library to control the temperature of my Rancilio Silvia coffee machine. More mathematical information can be found on Wikipedia - https://en.wikipedia.org/wiki/PID_controller
Why would I modify my working espresso machine?
I bought my new Silvia E v5 in 2019 and it is still working as new, so why would I modify it? The Silvia has 3 thermostats in it:
- Brew thermostat 100°C (marked with red dot)
- Steam thermostat 140°C
- Safety thermostat (cuts the power if the boiler reaches 165°C)
Silvia’s original brew thermostat turns the boiler on at around 90°C and turns it off around 100°C. Once turned off, the temperature will continue to climb up to ~114°C and only then it starts to cool down. This means that every time you pour an espresso shot, the temperature will be different. These temperature variations make it very hard to get a good espresso shot at the right temperature, which has a huge effect on the taste. You can do temperature surfing or install a PID controller :-)
Wait, but there are ready-to-use PID controllers online!
That’s right, but all the solutions I found online (well, except meCoffee.nl which are not available) require an ugly controller box outside the machine or cutting holes in the machine’s box. I wanted to keep my machine fully restorable to its original state.
So my controller will use a web UI to control the target temperature.
Also, learning new things and DIY is fun! This was my first microcontroller project.
STEP 1: Connecting the Sensor to the Board
- Prepare the MAX31865 board based on the instructions found here - https://learn.adafruit.com/adafruit-max31865-rtd-pt100-amplifier
Each PT100 sensor might have a different configuration using this board. Connect the PT100 sensor to the MAX31865 board and the MAX31865 to the esp32 board.
I used the following pins on the esp32:
MAX31865 pin > Esp32 pin
CS > 13
DI > 14
DO > 27
CLK > 26
VIN > 3.3V
GND > GND
STEP 2: The Software
I have used PlatformIO on Visual Studio Code so it would be very simple to install all the required libraries.
I have also used some code parts from https://github.com/Schm1tz1/ESPressIoT which was a great source.
You can download or clone the code from GitHub - https://github.com/bnayalivne/silvia-pid
Make sure to edit the src/web.cpp file and change the ssid and password for the network connection.
To upload the software you have to do 2 things.
- Upload the data/ folder which contains the HTML/JS files - In VS Code click on the PlatformIO icon on the left find the “Upload File System image” task.
- Upload the software using “Upload” or “Upload and monitor” tasks.
Once the software is installed on the esp32 board you should get the IP address from the board on the IDE monitor.
If you open the IP address on your browser (for example http://192.168.0.123 ) you should see the UI of the controller showing the current temperature of the room.
STEP 3: Preparing the Wires
Other than controlling the temperature using PID, I also wanted to have the following features:
- The controller has to turn on when Silvia is on, and turn off with the machine.
- No external power cables.
- It should be possible to undo any change easily (that’s why it was important to use the spade-connectors).
So I used the power going out from Silvia’s CPU unit.
I have soldered female spade connectors to the USB charger,
And 2xCable splitters to take power from the original machine CPU unit.
I have also prepared red+black 14AWG cables with male spade connectors on one side to go from the SSR to replace the current thermostat.
STEP 4: Connect As Read Only
On this step we are going to connect the temperature sensor as "read only" mode, to make sure we are getting correct temperature reading from the sensor before letting the new PID to control the boiler status.
- Connect the controller to the power supply - On the Silvia’s CPU unit I have disconnected cables 10 and 2 and instead connected my splitters, one leg of the splitter will connect to the original cable and the second will connect to the USB charger
Connect the microUSB cable to the charger and pass it through the cables hole to the boiler area.
I have used a small candy box (Altoids) to cover the esp32 and the MAX31865 boards.
Unscrew one of the brew thermostats (the one with the red dot on it, see image). screws and place the PT100 ring on it with some thermal paste. This will capture the boiler’s temperature and will be used in the PID software.
It's time to make sure we are getting readings from the temperature sensor to the controller and that you are able to connect the UI from your phone/pc using it’s IP address. Make sure the controller turns on/off with the machine and that the temperatures match the boiler status (during espresso brew, during steam mode, etc…).
Do not proceed to the next step if you can't get correct readings at this step.
Be careful not to turn on the machine while the covers are open, keep in mind that this is an high-voltage machine!
STEP 5: Move the Control Over to the PID Controller
- Remove the 2 connectors from the brew thermostat and connect them with the red and black cables that will go to the SSR.
- Position the SSR unit behind the front cover. Make sure it's away from the drip tray.
There is a single screw there that I used to fix it, but you mght want to drill another screw. - Connect the cables that go from the boiler (the cables that are now attached to the old thermostat cables) to the SSR AC side.
- Connect the DC side of the SSR to ESP32 board pins 19 and GND. This will make the SSR turn on/off on the AC side.
STEP 6: Make a Great Espresso
At this point, your ESP32 should be able to read the boiler’s temperature and control it’s operation using PID.
You can configure the desired temperature using the web UI.
What’s next?
- Make the temperature recover better after every shot.
- Nicer UI.
- Easier to switch WiFi networks.
- Steam temperature control.
- Turn on/off remotely or based on preset hours.
Now, let's make a great espresso!
38 Comments
CapitaineB 1 year ago
Thanx
bnayalivne 1 year ago
I believe it's called NodeMCU esp32s, I probably can get any other esp32 board just make sure the pin numbers are not different.
Also, make sure to use the newer branch (which is not perfect either but works for me, if you want to contribute you are welcome).
https://github.com/bnayalivne/silvia-pid/tree/brew-detection
grahampowell 2 years ago
[env:nodemcu-32s]
board = nodemcu-32s
framework = arduino
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git
lib_deps =
ESP Async WebServer
adafruit/Adafruit MAX31865 library @ 1.1.0
https://github.com/br3ttb/Arduino-PID-Library
bblanchon/ArduinoJson @ ^6.16.1
https://github.com/tzapu/WiFiManager.git
monitor_speed = 115200
bnayalivne 2 years ago
Try to update the environment target board,
see here -
https://docs.platformio.org/en/stable/boards/espre...
Should be something like this
[env:featheresp32]
platform=espressif32
board=featheresp32
and without the platform_packages part and might be without framework part.
grahampowell 2 years ago
Compiling .pio/build/nodemcu-32s/lib11f/AsyncTCP/AsyncTCP.cpp.o
src/main.cpp: In function 'void loop()':
src/main.cpp:67:28: error: call of overloaded 'abs(long unsigned int)' is ambiguous
if(abs(time_now-time_last)>=PID_INTERVAL or time_last > time_now) {
^
In file included from /Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/8.4.0/cstdlib:75,
from /Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/8.4.0/stdlib.h:36,
from /Users/grahampowell/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include/assert.h:21,
from /Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/sys-include/sys/reent.h:503,
from /Users/grahampowell/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include/sys/reent.h:17,
from /Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/sys-include/stdio.h:60,
from /Users/grahampowell/.platformio/packages/framework-arduinoespressif32/cores/esp32/Arduino.h:27,
from src/main.cpp:1:
/Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/sys-include/stdlib.h:74:5: note: candidate: 'int abs(int)'
int abs (int);
^~~
In file included from /Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/8.4.0/cstdlib:77,
from /Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/8.4.0/stdlib.h:36,
from /Users/grahampowell/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include/assert.h:21,
from /Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/sys-include/sys/reent.h:503,
from /Users/grahampowell/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include/sys/reent.h:17,
from /Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/sys-include/stdio.h:60,
from /Users/grahampowell/.platformio/packages/framework-arduinoespressif32/cores/esp32/Arduino.h:27,
from src/main.cpp:1:
/Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/8.4.0/bits/std_abs.h:78:3: note: candidate: 'constexpr long double std::abs(long double)'
abs(long double __x)
^~~
/Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/8.4.0/bits/std_abs.h:74:3: note: candidate: 'constexpr float std::abs(float)'
abs(float __x)
^~~
/Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/8.4.0/bits/std_abs.h:70:3: note: candidate: 'constexpr double std::abs(double)'
abs(double __x)
^~~
/Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/8.4.0/bits/std_abs.h:61:3: note: candidate: 'long long int std::abs(long long int)'
abs(long long __x) { return __builtin_llabs (__x); }
^~~
/Users/grahampowell/.platformio/packages/toolchain-xtensa-esp32/xtensa-esp32-elf/include/c++/8.4.0/bits/std_abs.h:56:3: note: candidate: 'long int std::abs(long int)'
abs(long __i) { return __builtin_labs(__i); }
^~~
Compiling .pio/build/nodemcu-32s/libab3/FS/FS.cpp.o
*** [.pio/build/nodemcu-32s/src/main.cpp.o] Error 1
============================================================================== [FAILED] Took 5.42 seconds ==============================================================================
* The terminal process "platformio 'run', '--target', 'upload', '--target', 'monitor', '--environment', 'nodemcu-32s'" terminated with exit code: 1.
* Terminal will be reused by tasks, press any key to close it.
Kolkman 2 years ago
This is a fork of the code from Schmidt with improved eye candy..
The hardware mods to my Silvia were based on the instructions above, highly recommended.
bnayalivne 2 years ago
Instead of hard-coding the WiFi secrets I recommend to use WiFiManager -
https://github.com/tzapu/WiFiManager
It's a really straight forward code change from what I remember.
Kolkman 2 years ago
(I have the async webserver as a dependency because of the websocket that feeds the graph on the config page)
AshrafU1 2 years ago
bnayalivne 2 years ago
Basically everything is in Github
https://github.com/bnayalivne/silvia-pid/blob/mast...
You should check the platformio.ini file with the dependencies.
Do you have any error while trying to compile it?
AshrafU1 2 years ago
mymaor89 2 years ago
Had issues with the code (one of the dependencies has a code change)
The PT100 has 3 wires so had to solder and cut the MAX31865 (read the docs!)
Also had to put wifi extender close to the Silvia.
But it works and works great! I'll be glad for explaination about PID arguments.
Thank you for great tutorial!
bnayalivne 2 years ago
1. I hope that you saw the second branch "brew-detection" which has better recovery between shots and also allows over the air updates (so you won't need to open the machine when you want to update the code).
The issue with the branch is that the PID settings are mostly hard-coded (but works great for me), except for the target temperature of course.
2. If you want to open a PR to fix the dependency it would be great.
Thanks!
Bnaya
kresyzig 3 years ago
So my question is regarding the PID control when the heat transfer conditions for the machine change (waming up vs idle and warm vs pulling a shot vs frothing vs recovering, etc). Initially I was getting a very stable temperature when the machine was idle, but a poor temperature stability
when pulling a shot or when recovering from steaming. I have since implemented algorithms to shift the integral term around (the integral value, not Ki or Ti) based on regime changes and it works much better in terms of recovery speed and less oscillations/overshooting. Before I had either integral windup issues or the integral term was unable to track the drifting power draw that was required between two regime changes, although Ki was appropriate for idling. Have you folks attempted similar things and have you came across better yet practical techniques to achieve this? Thanks!
kresyzig 3 years ago
fddownunder 3 years ago
One way of getting a slightly more stable temperature when pulling a shot, is by turning on the boiler while pulling the shot. This will also have a positive effect on the temperature recovery. See my reply above, how the status of the brew switch could be read. Johan.
kresyzig 3 years ago
I am quite satisfied with this solution. The temperature is very stable when idle and the algorithm is very responsive when using any water or steam. Under and over shooting of the temperature is kept to a minimum and the setpoint is reached very quickly. There is a file with a PID autotune algorithm, but I did not have much success using this, I think due to the relatively long deadtime of the system (3.5s for my machine). It was much better to tune the parameters by looking at the response of the system when using the simple control algorithm.
bnayalivne 3 years ago
Thanks for sharing your experience :-)
I have worked on a better version for the software that detects brewing based on temperature changes, taken from https://github.com/rancilio-pid/ranciliopid
It's not perfect yet because the UI won't update the PID values, but the temperature recovery between shots is much more stable (takes ~90 seconds to recover between shots without over-shooting, and I believe I can improve it as well).
You can see it on this branch until it's ready -
https://github.com/bnayalivne/silvia-pid/tree/brew...
It also supports OTA updates which is very nice for updating the firmware without opening the machine.
kresyzig 3 years ago
Edit: I just looked at your code. So it uses a second set of PID parameters upon brew detection, correct? This is something I had attempted in the past, but I was not very successful a reaching a very stable temperature during brewing this method. When idling, my machine's temperature is basically within +/-0.02 C of the setpoint (127.75 C on my HX machine) for long stretches of time, with some short periods within +/-0.05 C when power requirement shifts (I am not sure why, possibly due to changes in the water flow through the E61 head or due to slight changes in the line voltage). When I start brewing, I obviously expect an initial drop of temperature, but I was hoping to be able to get back within +/-0.1 C of the setpoint fairly quickly during the brew. With the ramp algorithm I have the temperature reaches back the setpoint quickly (7.5 seconds). Typically what happens is that the temperature drops another 0.25 C due to deadtime after the ramp algorithm is triggered (it might be challenging to improve this), than it overshoots the setpoint by 0.25 C before the transition to PID. I am still not quite satisfied about the transition. I think the issue is partly related to the temperature derivative that is not constant. I think that computing the second derivative to calculate the second order trend will help. What kind of temperature accuracy are you getting with your algorithms in that kind of situation?
kresyzig 3 years ago
I just added a second order temperature derivative for the ramp algorithm to my code and it seems to significantly improve the results. It reduces the temperature overshoot by turning off the ramp earlier and it also reduces significantly the following drop of the temperature below the setpoint by turning on the PID algorithm earlier. I will collect data and share a plot of how the result looks like when I have the chance. I pushed the modifications to my code to my repository