Skip to content

20. Project development

This week I worked on defining my final project idea and started to getting used to the documentation process.

The idea

I am thinking of making a controlled anaerobic fermentation chamber for coffee beans, as well as other food. Essentially it allows coffee beans to ferment in the absence of oxygen, while allowing CO2 released in the process to get out.

Monitoring the following environmental parameters, it will help food makers control their fermentation process in a more precise manner, and produce consistent results every time.

  1. CO2 gas pressure
  2. O2 gas concentration
  3. pH
  4. temperature

In addition, I would like to animate this process with some graphics on the display and make fermentation a fun thing to watch instead of something boring and old-fashioned.

Shapewise, it will be like an enclosed vessel with a control panel on top, possibly similar to this commercial product.

In addition, it is important to have an internet-connected controller.

Areas of concerns

  • cost of gas sensors, $50 or more is rather expensive
  • cooling options necessary? fan or something else?
  • what CO2 pumps to use?

Task break-down

  • mechanical design and fabrication
  • circuit design and power management
  • component purchase
  • user interface design
  • mechanical assembly
  • electronics assembly
  • programming
  • testing
  • make an accompanying app

Inspirational projects

  1. Open Ecotrons
  2. It has a pH and temperature sensor

  1. Oxygen purge system for wine storage tank
  2. It purges oxygen in the wine fermentation tank by pumping in food grade nitrogen, with the help of a $99 oxygen sensor - the LuminOx LOX-O2.
  3. Beer fermentation chamber


Qty Description Price Link Notes
1 CO2 gas sensor 28.00 $ someone recommends Adafruit SGP30 TVOC/eCO2 Gas Sensor but it’s eCO2, not actualy CO2
1 O2 gas sensor 49.90 $ Grove Oxygen Sensor
1 temperature sensor 1.50 $
1 pH sensor 14.00 $
1 Fan 22.00 $
1 ESP32 7.00 $
1 Neopixel 22.00 $
1 Valve 22.00 $
1 LED Display 22.00 $
1 Pump for CO2 / N2 3.00 $
1 Power supply 5.00 $
TOTAL ?.00 $


  • Make a 3d CAD model of the device in Fusion 360 by 30 April
  • Purchase components sometimes in April

April 11 - Input devices

For my final project, I would need inputs from the following

  • a temperature sensor
  • a pressure sensor to measure the pressure inside the fermentation chamber,
  • an O2 gas sensor and
  • a CO2 gas sensor
  • a button for activating carbon dioxide pump

Temperature sensor

I am thinking of using an existing analog temperature sensor with steel head (SEN118A2B) from a past project. It was bought from SeeedStudio, and the cool thing is that it is waterproof. Datasheet Sample code

An alternative would be a common DHT11 digital temperature sensor.

Pressure sensor

I obtained a MPS20N0040D-D pressure sensor. Link to datasheet However, upon receiving it I realized that MPS20N0040D-D is a through-hole type and I should have gotten MPS20N0040D-S which is the SMD version. It can measure up to 40kPa.

CO2 gas sensor

As for the CO2 gas sensor, my local stores offer 2 options - MG811 CO2 Sensor Module at a price of $28 a piece. It can measure 350-10000ppm, and since it is quite pricy so I decided not to get it this time, until I am sure of the choice of sensor to use. - MH-Z19 at $27 each. It can measure 0-5000ppm.

Useful files and code

O2 gas sensor

MIX8410 is an analog O2 gas sensor sold by Seeed Studio at $49.90 a piece. Link As this is quite expensive, I was thinking whether I really need this. In the end, I think I can do away without it.

April 15 - Output devices

For my final project, I would need the following outputs:

  • Neopixel LEDs
  • carbon dioxide pump
  • maybe a mini display but I am thinking whether I can use the individually addressable Neopixels to display the temperature and carbon dioxide concentration

This week I designed a PCB with Attiny44 as the main chip. It can be used to

May 11 - Interface

I studied how Blynk app can be used with ESP32 to control Neopixels, and came across this tutorial

Sample code

#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <Adafruit_NeoPixel.h>
#define PIN 15
#define NUM 25
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUM,PIN, NEO_GRB + NEO_KHZ800);
char auth[] = "HoLYSq-Sxxxx";
char ssid[] = "admin";
char pass[] = "12345678";
int r,g,b,data;
void setup()
  Blynk.begin(auth, ssid, pass);
void loop()
r = param[0].asInt();
g = param[1].asInt();
b = param[2].asInt();
data = param.asInt();
else if(data==1)
void static1(int r, int g, int b)
  for(int i=0;i<=NUM;i++)
  pixels.setPixelColor(i, pixels.Color(r,g,b));;
void animation1()
  for(int i=0;i<NUM;i++)
    pixels.setPixelColor(i, pixels.Color(255,0,0));;
  for(int i=NUM;i>=0;i--)
    pixels.setPixelColor(i, pixels.Color(0,255,0));;
  for(int i=0;i<NUM;i++)
    pixels.setPixelColor(i, pixels.Color(0,255,255));;
  for(int i=NUM;i>=0;i--)
    pixels.setPixelColor(i, pixels.Color(255,255,0));;

May 22 - More sensors purchased and deciding a container shape

More sensors

This weekend I finally had time to find an affordable CO2 sensor. I also ordered an Arduino-compatible pH sensor namely PH4502C, but the latter is extra, and I am unsure if I have time to integrate it before the end of Fab Academy.

Container shape

For quite some time now, I have not decided on an overall shape for my device. This weekend I decided that it would be more or less ovoid or egg-shaped.

To model the ovoid shape, I scounted the Internet for a mathematical formular. This makes for an interesting read.

Below is what is known as a classical ovoid. Essentially it is made by plotting two circles and a couple of tangential paths to connect them. The challenge is to program those tangential paths.

Source: Alex Matsyura et al

Shortly after I came across a mathematical formular for ovals in a research paper titled A universal formula for avian egg shape

where B is the egg maximum breadth, L is the egg length, and w is the parameter that shows the distance between two vertical lines corresponding to the maximum breadth and the half length of the egg.

The parameters are illustrated below for ease of understanding. Source:Narushin et al

I experimented with OpenSCAD to make an egg-shaped container

May 27

Jun 27

Having come back from an unexpected 3-week quarantine period, I finally got back to testing the Barduino board made for Networking and Communications week. I will program it with the Neopixels that arrived while I was in the quarantine camp.

Its pinout is as follows (courtesy of FabLab Barcelona)

To make an LED matrix, I plan to daisy-chain 10 strips of Neopixels, each 19 pixels, in a manner similar to the one shown below.


After more than 3 months in lock-down, finally I could get back in the lab to 3d-print the container casing which had been designed in Fusion360.

Backing for LEDs


As advised by many tutorials, I soldered a capacitor across the VCC and GND connections


Testing the air pump


const int RELAY_PIN = 7;  // the Arduino pin, which connects to the IN pin of relay

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin A5 as an output.

// the loop function runs over and over again forever
void loop() {
  digitalWrite(RELAY_PIN, HIGH); // turn on pump 5 seconds
  digitalWrite(RELAY_PIN, LOW);  // turn off pump 5 seconds

Positioning electronics

Programming Neopixel matrix

In order to program the Neopixel matrix to display animation and maybe texts, I needed the following 3 libraries Adafruit_Neopixel Adafruit_NeoMatrix Adafruit_GFX

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>

The Adafruit_Neopixel library had already been installed previously for other projects, so I just installed the other two by dropping the unzipped files into Arduino Libraries folder.

The matrix layout is defined in the following line of code

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(5, 8, 27, NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS + NEO_MATRIX_ZIGZAG, NEO_GRB + NEO_KHZ800);

The first two arguments — 19 and 10 — reflect the width and height of the matrix, in pixels. My matrix has 19 rows, each comprising of 10 pixels. The third argument — 27 — is the microcontroller’s pin number to which the NeoPixels are connected. The next two arguments, either NEO_MATRIX_TOP or NEO_MATRIX_BOTTOM , indicates the first pixel’s position. My matrix starts at the top left corner. The next arguments, NEO_MATRIX_ROWS + NEO_MATRIX_PROGRESSIVE, mean the pixels are arranged in row by row fashion, and the rows proceed in a zigzag manner. If each row proceeds in the same direction i.e. progressive, the argument would be NEO_MATRIX_ROWS + NEO_MATRIX_PROGRESSIVE.

Adafruit has provided an excellent guide to Neomatrix library where you can read up in details. …

Initially I was concerned that the ESP32 may not have enough RAM to support all 190 Neopixels. It is said to require 3 bytes of RAM per pixel. A 19x10 matrix requires 19103 = 5700 bytes. Thankfully, the ESP32 handles it all quite well and I did not have to worry about this issue at all. Video

Although I had originally hoped that the egg’s body could display some text therefore removing the need to add an LCD display, as shown in the video, the text is diffused through the egg’s body and it is hard to distinguish the individual characters.

Map temperature and Neopixels

Many people have tried to create a visual gauge for temperature with LED pixels. Take this for example


Arduino: 1.8.18 (Windows Store (Windows 10), Board: "ESP32 Dev Module, Disabled, Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS), 240MHz (WiFi/BT), QIO, 80MHz, 4MB (32Mb), 921600, None"

Sketch uses 216322 bytes (16%) of program storage space. Maximum is 1310720 bytes.

Global variables use 13376 bytes (4%) of dynamic memory, leaving 314304 bytes for local variables. Maximum is 327680 bytes.

C:\Users\Mai\Documents\ArduinoData\packages\esp32\tools\esptool_py\3.0.0/esptool.exe --chip esp32 --port COM11 --baud 115200 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xe000 C:\Users\Mai\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.6/tools/partitions/boot_app0.bin 0x1000 C:\Users\Mai\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.6/tools/sdk/bin/bootloader_qio_80m.bin 0x10000 C:\Users\Mai\AppData\Local\Temp\arduino_build_346688/sketch_20211222_barduinoneopixtest.ino.bin 0x8000 C:\Users\Mai\AppData\Local\Temp\arduino_build_346688/sketch_20211222_barduinoneopixtest.ino.partitions.bin v3.0-dev

Serial port COM11

Traceback (most recent call last):

  File "", line 3682, in <module>

  File "", line 3675, in _main

  File "", line 3329, in main

  File "", line 263, in __init__

  File "site-packages\serial\", line 88, in serial_for_url

  File "site-packages\serial\", line 62, in open

serial.serialutil.SerialException: could not open port 'COM11': WindowsError(433, 'A device which does not exist was specified.')

Failed to execute script esptool

the selected serial port Failed to execute script esptool

 does not exist or your board is not connected

Error : A fatal error occurred: Failed to connect to ESP32: Invalid head of packet (0x00)

Programming ESP32

Basic OTA setup

Admittedly, I am a little lazy to connect the FTDI cable every time I want to upload a sketch, and knowing that the ESP32 supports Over-The-Air (OTA) programming, I set out to try the OTA function with the goal of programming the ESP32 wirelessly and conveniently.

First, I opened a BasicOTA sketch example found in Files > Examples > ArduinoOTA, and proceeded to update the SSID and password to my home network. The next step is uploading the sketch.

To create a .bin file, you can refer to steps by Randomnerdtutorials.

In order to see whether the Barduino is on the network, I installed Advanced IP Scanner. Some folks suggested using Bonjour Browser which is quite antiquated piece of software but maybe you are ok with that.


python -i -p 3232 –auth= -f sketch.bin python -i -p 3232 –auth= -f sketch.bin

Python doesn’t actually say anything when uploading worked, but it will tell you Error uploading when something is wrong. The serial monitor on the other hand shows the uploading progress from 0% to 100%. Check that to ensure that uploading is completed.

Webserver with relay control

My idea is to control the CO2 pump with a phone or web app interface, and I am going to create a webserver for my pump relay.

Typing the ESP32 IP address in my browser, I pulled up a webpage that allows me to toggle the relay.

Note that as the relay is turned on or off, the serial monitor also outputs NO 11 or 10 correspondingly.


Webserver with temperature display

Having connected the DHT11 sensor to ESP32, I encountered the following error “Failed to read from DHT11”. At first, I thought it was a sensor problem. However, I later found out that the very same sensor worked when connected to an Arduino Uno.

Feeling puzzled, I asked a friend who is an electronics expert. He told me that it is important to have a pull-up resistor on the data input to the ESP32. Unlike Arduino Uno, the ESP does not come with pre-installed pull-up resistors on its I/O pins.

As a result of this, I removed the existing 680ohm pull-up resistor on DHT11 and soldered a 10k Ohm one in its place. ![](../images/project/final-dht11-pullup.jpg

Finally, the temperature and humidity values appeared sensible.

After verifying that the ESP32 could read the DHT11 properly, I proceeded to create a webserver displaying the sensor’s outputs, with the help of Randomnerdtutorials.


  Rui Santos
  Complete project details at

// Import required libraries
#include "WiFi.h"
//#include "AsyncTCP.h"
#include "ESPAsyncWebServer.h"
#include "Adafruit_Sensor.h"
#include "DHT.h"

// Replace with your network credentials
//const char* ssid = "REPLACE_WITH_YOUR_SSID";
//const char* password = "REPLACE_WITH_YOUR_PASSWORD";

#define DHTPIN 27     // Digital pin connected to the DHT sensor

// Uncomment the type of sensor in use:
#define DHTTYPE    DHT11     // DHT 11, DHT 22 (AM2302) or DHT 21 (AM2301)


// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

String readDHTTemperature() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  // Check if any reads failed and exit early (to try again).
  if (isnan(t)) {    
    Serial.println("Failed to read from DHT sensor!");
    return "--";
  else {
    return String(t);

String readDHTHumidity() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  if (isnan(h)) {
    Serial.println("Failed to read from DHT sensor!");
    return "--";
  else {
    return String(h);

const char index_html[] PROGMEM = R"rawliteral(
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    html {
     font-family: Arial;
     display: inline-block;
     margin: 0px auto;
     text-align: center;
    h2 { font-size: 3.0rem; }
    p { font-size: 3.0rem; }
    .units { font-size: 1.2rem; }
      font-size: 1.5rem;
      padding-bottom: 15px;
  <h2>ESP32 DHT Server</h2>
    <i class="fas fa-thermometer-half" style="color:#059e8a;"></i> 
    <span class="dht-labels">Temperature</span> 
    <span id="temperature">%TEMPERATURE%</span>
    <sup class="units">&deg;C</sup>
    <i class="fas fa-tint" style="color:#00add6;"></i> 
    <span class="dht-labels">Humidity</span>
    <span id="humidity">%HUMIDITY%</span>
    <sup class="units">&percnt;</sup>
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("temperature").innerHTML = this.responseText;
  };"GET", "/temperature", true);
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("humidity").innerHTML = this.responseText;
  };"GET", "/humidity", true);
}, 10000 ) ;

// Replaces placeholder with DHT values
String processor(const String& var){
  if(var == "TEMPERATURE"){
    return readDHTTemperature();
  else if(var == "HUMIDITY"){
    return readDHTHumidity();
  return String();

void setup(){
  // Serial port for debugging purposes


  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("Connecting to WiFi..");

  // Print ESP32 Local IP Address

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readDHTTemperature().c_str());
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readDHTHumidity().c_str());

  // Start server

void loop(){


Webserver with CO2 level display

Next I would like to add an icon for CO2 level, and found something appropriate on the website

Finally, I integrated all the sensor parameters and relay control on the same page.

It is obvious that the CO2 ppm value is ridiculously high. I later realized that this was because the CO2 sensor was outputting a 5V signal, whereas the ESP32 is on a 3.3V logic. Once the CO2 sensor was supplied with a 3.3V incoming voltage, its output ppm value immediately looked a lot more credible.


Last update: July 27, 2022