Community project
Je Veux Cr Er Un Objet Qui Absorbe

This CO2 monitoring and fan control system uses an ESP32 to read air quality data from an MH-Z19B sensor and automatically activate a ventilation fan when carbon dioxide levels exceed safe thresholds. The system displays real-time CO2 measurements on an SSD1306 OLED screen and uses an RGB LED to provide visual feedback about air quality status—green for good air, yellow for warning, and red for poor conditions.
The guide includes a complete wiring diagram, parts list, and step-by-step assembly instructions for connecting the CO2 sensor, OLED display, RGB LED with current-limiting resistors, and relay-controlled fan to the ESP32. Firmware is provided to handle sensor readings, threshold-based fan automation, display updates, and color-coded LED indicators, making this an ideal project for monitoring indoor air quality in offices, classrooms, or living spaces.
Wiring diagram
Interactive · read-only
Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.
Parts list
Bill of materials| Component | Qty | Notes |
|---|---|---|
| MH-Z19B CO2 Sensor | 1 | NDIR CO2 sensor, 400–5000 ppm range, UART interface at 9600 baud, 5V powered, 3.3V-compatible TX/RX logic |
| SSD1306 OLED | 1 | 0.96 inch 128x64 OLED display with I2C interface |
| Relay Module | 1 | Single channel relay module |
| Rgb Led | 1 | Discrete four-pin common-cathode RGB LED with separate red, green, and blue anodes. Each colour channel needs its own current-limit resistor and can be PWM-dimmed from separate MCU GPIO pins. |
| Resistor 100Ω100 Ω | 1 | 100 ohm current-limiting resistor for RGB LED Red channel |
| Resistor 100Ω100 Ω | 1 | 100 ohm current-limiting resistor for RGB LED Green channel |
| Resistor 100Ω100 Ω | 1 | 100 ohm current-limiting resistor for RGB LED Blue channel |
| 5V DC Fan | 1 | Small 5V DC brushless fan (40mm or 50mm). Switched via relay. Pulls CO2-rich air through the soda lime capture cartridge. |
Assembly
5 stepsPower off everything
Make sure the ESP32 is unplugged from USB before wiring anything.
- ⚠ Never wire components while the board is powered.
Wire the MH-Z19B CO₂ sensor
Connect MH-Z19B VIN → ESP32 5V, GND → GND, TXD → GPIO16, RXD → GPIO17. The sensor UART logic is 3.3V — no level shifter needed for RX/TX.
- Tip: Allow 3 minutes warm-up after first power-on before readings are valid.
Wire the SSD1306 OLED display
Connect OLED VCC → ESP32 3.3V, GND → GND, SDA → GPIO21, SCL → GPIO22.
Wire the RGB LED with resistors
Insert three 100Ω resistors in series on each color channel: GPIO25 → R1 → LED-R, GPIO27 → R2 → LED-G, GPIO14 → R3 → LED-B. Connect LED GND (common cathode) to GND.
- Tip: The long leg of each color pin is the anode; the single short leg is the common cathode.
Wire the relay and fan
Connect relay VCC → 5V, GND → GND, IN → GPIO26. Then wire the 5V fan's positive lead to the relay's COM terminal and the relay's NO (Normally Open) terminal to 5V. Fan negative → GND. When GPIO26 goes LOW, the relay closes and the fan runs.
- ⚠ Keep relay wiring away from the breadboard signal area.
- ⚠ The fan must be 5V rated — do not use a 12V fan here.
Pin assignments
Board wiring reference| Pin | Connection | Type |
|---|---|---|
| 5V | co2_sensor VIN | power |
| GND | co2_sensor GND | ground |
| GPIO 16 | co2_sensor TX | uart |
| GPIO 17 | co2_sensor RX | uart |
| 3V3 | oled VCC | power |
| GND | oled GND | ground |
| GPIO 21 | oled SDA | i2c |
| GPIO 22 | oled SCL | i2c |
| 5V | relay VCC | power |
| GND | relay GND | ground |
| GPIO 26 | relay IN | digital |
| GPIO 25 | res_r A | data |
| EXT | res_r B → Rgb Led R | data |
| GPIO 27 | res_g A | data |
| EXT | res_g B → Rgb Led G | data |
| GPIO 14 | res_b A | data |
| EXT | res_b B → Rgb Led B | data |
| GND | rgb_led GND | ground |
| EXT | fan VCC → Relay Module VCC | power |
| GND | fan GND | ground |
Firmware
ESP32#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <MHZ19.h>
#include <HardwareSerial.h>
// Pin definitions
#define CO2_RX_PIN 16 // ESP32 RX2 ← MH-Z19B TX
#define CO2_TX_PIN 17 // ESP32 TX2 → MH-Z19B RX
#define RELAY_PIN 26 // Relay IN (LOW = fan ON)
#define LED_R_PIN 25
#define LED_G_PIN 27
#define LED_B_PIN 14
// Thresholds (ppm)
#define CO2_GOOD 800
#define CO2_WARN 1200
#define CO2_BAD 1600
// OLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
// Forward declarations
void setLED(uint8_t r, uint8_t g, uint8_t b);
void updateDisplay(int co2ppm, bool fan);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// CO2 sensor
HardwareSerial co2Serial(2);
MHZ19 mhz19;
// State
int lastCO2 = -1;
bool fanOn = false;
unsigned long lastRead = 0;
const unsigned long READ_INTERVAL = 5000; // ms
void setLED(uint8_t r, uint8_t g, uint8_t b) {
analogWrite(LED_R_PIN, r);
analogWrite(LED_G_PIN, g);
analogWrite(LED_B_PIN, b);
}
void updateDisplay(int co2ppm, bool fan) {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// Title
display.setTextSize(1);
display.setCursor(0, 0);
display.print("CO2 Capture System");
display.drawLine(0, 10, 127, 10, SSD1306_WHITE);
// CO2 value
display.setTextSize(2);
display.setCursor(0, 16);
if (co2ppm > 0) {
display.print(co2ppm);
display.setTextSize(1);
display.print(" ppm");
} else {
display.print("Warmup...");
}
// Status
display.setTextSize(1);
display.setCursor(0, 40);
if (co2ppm <= 0) display.print("Status: WARMING UP");
else if (co2ppm < CO2_GOOD) display.print("Status: GOOD ");
else if (co2ppm < CO2_WARN) display.print("Status: MODERATE");
else if (co2ppm < CO2_BAD) display.print("Status: HIGH ");
else display.print("Status: DANGER !");
// Fan
display.setCursor(0, 52);
display.print("Fan: ");
display.print(fan ? "ON (capturing)" : "OFF");
display.display();
}
void setup() {
Serial.begin(115200);
// LED pins
pinMode(LED_R_PIN, OUTPUT);
pinMode(LED_G_PIN, OUTPUT);
pinMode(LED_B_PIN, OUTPUT);
// Relay
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, HIGH); // relay inactive (active LOW module)
// OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 not found");
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 20);
display.print("CO2 Capture System");
display.setCursor(20, 36);
display.print("Warming up 3 min");
display.display();
// CO2 sensor on UART2
co2Serial.begin(9600, SERIAL_8N1, CO2_RX_PIN, CO2_TX_PIN);
mhz19.begin(co2Serial);
mhz19.autoCalibration(false);
setLED(0, 0, 30); // dim blue during warmup
Serial.println("MH-Z19B warming up (3 minutes needed)...");
}
void loop() {
unsigned long now = millis();
if (now - lastRead >= READ_INTERVAL) {
lastRead = now;
int co2 = mhz19.getCO2();
if (co2 > 0) {
// Fan logic
bool shouldFanOn = (co2 >= CO2_WARN);
if (shouldFanOn != fanOn) {
fanOn = shouldFanOn;
digitalWrite(RELAY_PIN, fanOn ? LOW : HIGH);
}
// LED color
if (co2 < CO2_GOOD) setLED(0, 255, 0); // green
else if (co2 < CO2_WARN) setLED(255, 165, 0); // orange
else if (co2 < CO2_BAD) setLED(255, 50, 0); // red-orange
else setLED(255, 0, 0); // red
if (co2 != lastCO2) {
lastCO2 = co2;
updateDisplay(co2, fanOn);
Serial.printf("CO2: %d ppm Fan: %s\n", co2, fanOn ? "ON" : "OFF");
}
} else {
// Still warming up
updateDisplay(-1, false);
}
}
}“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.
Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.