JFBrew is my project of DIY fermentation chamber for my own beer. It consists in a chamber (a hacked fridge, for example) where the temperature is regulated (using sensors, the compressor of the fridge and a heat cable) so that the beer ferments in the best possible conditions.
About the name? My name is Jean-François, and I want to brew my own beer. It's that simple !
How to brew your own beer?As many of you, I love to taste good beers. Pils, lager, IPA, stout, double, triple, Trappist, saison, hefeweizen, belgian golden strong ale, I love all kind of beers. And since I've learned about BrewPi, I've wanted to do it by myself. Oh! my goal is certainly not to make it better than BrewPi, just to make it by myself.
Ok, note that I'm a total beginner in beer brewery, so do not trust everything I write about the science of beer :)
Nowadays, it's very easy to find a beer brew kit for beginner, with tools (buckets, hydrometer, brush, caps) and ingredients (malt extract, yeast and hops). These kits are designed to be easy to use by anyone. It's indeed possible to make your own beer at home.
As these kits use malt extract, you just need to add water, sugar and yeast to the malt, and let it ferment for 2 to 4 weeks in order to make your own beer.
More advanced brewer choose to start from scratch, from the cereal grain, fresh hops and specific yeast, but that's another story.
One constraint for the yeast to make a good tasting beer, is that it wants to sit in in confortable and stable temperature during the fermentation. Some recipes require that the beer first ferments at 22°C for 2 weeks, then at 18°C for another 2 weeks. The more stable the temperature, the better the beer will taste!
That's where JFBrew comes into action: let's build a chamber where we will be able to regulate the temperature that the yeast will enjoy.
How does it work?The fermentation chamber is a box that
- heats, in order to increase the temperature
- cools, in order to lower the temperature
- measure the temperature inside the box and inside the fermenting beer
- tightly regulate the temperature inside the box so that the beer stays at the perfect temperature for the yeast to eat the sugar and produce alcohol and aromas.
A fridge is a good starting point, as it's a box that cools to keep food fresh. It's relatively easy to add a heat cable and sensor the fridge, and to modify it's wiring so that you can enable/disable its compressor at your own will (well... at your software will).
Once the fridge is ready, you'll need to connect the compressor and the heat cable to relays that you'll control via GPIO of your favourite development boad : the M5Stack board!
The software that runs in the M5Stack will measure the temperature of the beer, decide if it's too cold or too warm, and activate the heater or the cooler as needed, using a closed-loop regulation and a PID algorithms.
Let's hack the fridgeThe first thing is to understand how the fridge works, how the electrical wires are connected from the mains plug to the compressor and the thermostat. This step took me quite a long time, as I couldn't find any technical data about the fridge. I followed all the wire, check them with a continuity tester, and drew this kind of diagram:
Once you know how it works, you need to cut some cables in order to connect the compressor to a relay you'll drive from the M5Stack board.
I won't explain this operation in details, as I'm not a qualified electrician and as every fridge is different. You can however find much more detailed information in the fridge hacking guide from BrewPi.
The following picture shows my fridge after the modifications. You see the new wires, the 2 relays on the right, and the temperature sensors going into the fridge via a hole above the compressor.
Now, let's talk about a subject I know a little more : writing code! I decided to use the following tools and libraries:
- PlatformIO as the build system;
- Jetbrain's CLion as the IDE (Atom and the plugin from PlatformIO is pretty good, but I prefer CLion);
- C++ as the programming language;
- Arduino framework as the base framework;
- The M5Stack library to control the M5Stack board and peripherals;
- cfGUI as the UI library (warning : this is a self promotion, as I'm the author of cfGUI !).
- MiniPID as the PID library (no, I didn't write my own PID algo...)
- MQTT as communication bus
Ok, here is the main subject of this story : the software I wrote to make this project.
Here are the main features of the current version of the software:
- Initialize the M5Stack board, the screen and the Wifi connection;
- Connect to my MQTT broker;
- Poll temperature from 3 temperature sensors (ambient, fridge and beer);
- Drive 2 SSR relays when heating/cooling is needed;
- Regulate the temperature of the beer by heating or cooling the air inside the fridge;
- Provide a simple UI on the LCD screen : display current temperature and state, allows the enable/disable regulation and specify a temperature set point;
- Publishe monitoring data (temperature, states,...) on MQTT topics;
- Subscribe to MQTT topics to receive commands.
The following diagram shows how the M5Stack board is connected to the others devices:
And here is an overview of the project in real life:
The most interesting part of this software is the regulation part. Let me explain how it works.
I wanted to be able to maintain the temperature of the fermenting beer as stable as possible (even if the temperature outside fluctuates and even if the fermentation release some heat).
With the fridge, you cannot act directly on the beer (there is no heater/cooler directly into the wort), you can only heat or cool the air inside the fridge. That's why you need 2 temperature sensors : one that measures the temperature of the beer and one that measures the temperature of the fridge.
Now, let's say you want the beer to be at 24°C for the best fermentation, but for now, it sits at 19°C. You need to heat it in order to reach 24°C.
As I said before, you cannot heat the beer directly, you only heats the temperature of the air inside the fridge. So, what temperature should you try to reach in the fridge so that the beer reaches 24°C? Mhm...Perhaps 29°C for some time. As it's now hotter in the fridge than in the beer, the temperature of the beer slowly increase.
But hey! The temperature of the beer must not increase too far from 24°C, as it could kill the yeast. So, when you see that the temperature of the beer increases, you'll need to heat a little less so that the temperature of the beer does not overshoot. With less heat power, the temperature of the fridge slowly decreases down to 24°C, as the temperature of the beer continues to increases up to 24°C.
Here is basically what the temperature regulation of JFBrew does multiple times per second : read the current temperature of the beer and of the fridge, decide what would be the optimal temperature inside the fridge to reach the beer set point, and apply a certain amount of heating/cooling power to reach this temperature.
This is done using 2 PID regulators.
- The first one processes the difference between the current temperature of the beer and the desired temperature. The output of this PID is the temperature needed inside the fridge.
- The second one processes the difference between the output of the first one and the current temperature of the fridge. It outputs the heating/cooling power that must be applied to the cooler/heater to reach the correct temperature inside the fridge.
The output of the second ("fridge") PID is then converted in pulses of variable width that drive the heater/cooler relays. The higher the output, the larger the pulse.
A PID algorithm is quite simple to write (just 3 functions : progressive, integral and derivative) but it took me a lot of time, tries and errors and energy to tune! The key points in a PID processing are the settings of the PID : what gains should I apply to the 3 terms? How to ensure that the setpoint will be reached without overshooting and oscillating too much?
It was at that moment I knew I needed to develop monitoring and debug tools to help me "see" what happens inside the software and inside the fridge and decide if my settings were correct.
Wanna see the codeEverything start in main.cpp. It contains the 2 classic methods for every Arduino project : setup and loop. setup() initializes everything (WiFi connection, connection to MQTT broker, UI, application).
void setup() {
const bool initLCD = true;
const bool initSD = false;
M5.begin(initLCD, initSD);
M5.Speaker.mute();
app = new Application(config);
Serial.println("Init HW... ");
app->Init();
Serial.println("OK!");
Serial.print("Init UI... ");
InitUI();
Serial.println("OK!");
Serial.print("Connecting to Wifi... ");
ConnectWifi();
Serial.println("OK!");
}
Loop() is the main loop of the application. The software is based on a 'supermainloop' meaning everyting (UI update, MQTT communication, sensor measurement, state machine management, PID processing, output to GPIO) is done at each loop iteration. I use this pattern to keep the application simple : there's no need of lock, thread, dynamic allocation and thus, less risk of memory leaks and race conditions. I might change this pattern later, when the application evolves, but it fits my needs for now.
The main function that is called in loop() is Application::Update():
void loop() {
...
app->Update();
...
}
The class Application is the main business class of JFBrew. It manages the state machine of the application, and processes the temperature regulation.
Here is the content of Application::Update():
void Application::Update() {
if(!configuration.IsTemperatureSensorStubbed()) {
tempSensorBus->Update();
}
bool areTempSensorsOk = TemperatureSensorsSanityCheck();
bool areTempSensorsReady = AreTemperatureSensorsReady();
if(!areTempSensorsOk) {
state = States::Error;
heaterPwmRelay->Consign(0);
coolerPwmRelay->Consign(0);
} else if(!areTempSensorsReady) {
state = States::Pending;
heaterPwmRelay->Consign(0);
coolerPwmRelay->Consign(0);
} else {
if(state == States::Pending) {
state = States::Idle;
idleTime = minIdleTime+1;
}
ProcessRegulation();
}
heaterPwmRelay->Update();
coolerPwmRelay->Update();
cpt++;
}
It
starts by reading the temperature from the temperature sensors. It checks that the temperatures are valid (they could be invalid when a sensor is disconnected, for example). Then, the temperature regulation is processed with the new temperature, and the heater/cooler PWM are updated.
Here is a stripped down version of Application::ProcessRegulation():
void Application::ProcessRegulation() {
double heaterConsign = 0.0f;
double coolerConsign = 0.0f;
double fridgeConsign = 0.0f;
auto currentBeerTemp = beerTempSensor->Value();
auto currentFridgeTemp = fridgeTempSensor->Value();
auto currentDeltaTemp = std::abs(currentBeerTemp - currentFridgeTemp);
// Maximum temperature delta allowed between the T° of the fridge and the T° of the beer
auto maxDeltaTemp = std::abs(5.0 - currentDeltaTemp);
// Calculate the temperature needed in the fridge in function of current temperature of the beer
beerToFridgePid->setOutputLimits(-maxDeltaTemp, +maxDeltaTemp);
fridgeConsign = beerToFridgePid->getOutput(currentBeerTemp, beerSetPoint);
beerToFridgePidOutput = currentFridgeTemp + fridgeConsign;
// Calculate the heating / cooling power needed in order to reach the correct temperature inside the fridge
heaterConsign = heaterPid->getOutput(currentFridgeTemp, beerToFridgePidOutput);
heaterPidOutput = heaterConsign;
coolerConsign = coolerPid->getOutput(currentFridgeTemp, beerToFridgePidOutput);
coolerPidOutput = coolerConsign;
// coolerPidOut and heaterPidOutput will later be converted in pulse width to the cooler/heater relay
}
The monitoring toolsI developed these tools for 2 reasons:
- I need to be able to debug and tune my software
- I would like to know what's going on inside the fridge while the beer is fermenting
The first tool was developed in Python and based on QT (PySide) and PyQtGraph. It displays in "real-time" the data published by JFBrew on MQTT topics. The data are the temperatures, the value of both PIDs and the state of the heater/cooler. I mainly used this tool to debug and tune the code.
The other tools are mainly monitoring tools:
- A recorder that write all data received on MQTT to a file;
- Another recorder that store all data received on MQTT to an influxDB database. They are then display in a Grafana instance;
- A third one that toots messages to my Mastodon account to tell me what happens to my beer.
And I can think of a lot more tools to write : send notification to a Matrix account, display data in Home-assistant (home automation software), send an email with a summary of the day,...
That's the power of MQTT : JFBrew sends the data once, and many simple tools process these data and do whatever you program them!
These monitoring tools run on my homeserver based on an Odroid XU4 board, but they could run on any PC or a RaspberryPi, for example.
Let me introduce you to La Jean-FrançoiseI've already brewed my first beer with JFBrew. It's name "La Jean-Françoise" (remeber, my name is... Jean-François) and it's currently doing the last fermentation in the bottles.
Now that my first beer is nearly finished, I'm currently writing a detailed documentation about JFBrew.
I'll continue to work on the project. Here are some ideas :
- add a fan inside the fridge
- monitor the fermentation in realtime with a wireless hydrometer
- improve the look and feel of the fridge, add some LEDs, and place it in a dedicated space (for now, it sits in my office)
- improve MQTT messaging in order to send less messages when not necessary
- auto-detect temperature sensors (their IDs are hard-coded for now)
- many many more things to come.
If you liked this story and my project, do not hesitate to contact me, tell me what you think, share your ideas, use it, fork it, help me improve it!
Comments