Introduction
Simple DIY module for monitoring air quality using the ESP32 Mini with ESPHome firmware to integrate with Home Assistant. We’ll use the AHT21 and CCS811 sensors to measure Humidity, Temperature, Total Volatile Organic Compound (TVOC), and CO2 levels, and display the data on a 1.3-inch SH1106 OLED display.
All components are connected using an I2C interface through a DIY I2C rail. Additionally, the module includes an AUX button that can be set to toggle the display screen or cycle through display pages.
This project is budget-friendly, costing around $12 for the essential components, and offers a hands-on experience that combines electronics, coding, and 3D printing.
I designed this module specifically to monitor fumes from my 3D printer, ensuring a safer and healthier workspace. Whether you’re a seasoned maker or a beginner, this project is an exciting addition to your smart home setup.
Material List
3D PRINT PARTS
- Thingiverse.com: https://www.thingiverse.com/thing:6684495
- Printable.com: https://www.printables.com/model/932420-simple-esp32-aqi-module-enclosure-with-display
COMPONENTS FOR THIS PROJECT
- ESP32 Mini Module: Aliexpress | Shopee Thailand
- AHT21 Sensor (for humidity and temperature): Aliexpress | Shopee Thailand
- CCS811 Sensor (for TVOC and CO2): Aliexpress | Shopee Thailand
- 1.3-inch SH1106 OLED Display: Aliexpress | Shopee Thailand
- 6.5×14.5cm Strip Prototype Board: Aliexpress | Shopee Thailand
- JST-XH 2.54 connectors: Aliexpress | Shopee Thailand
- Dupont connector (Optional): Aliexpress | Shopee Thailand
- 1/4W Resistor: Aliexpress | Shopee Thailand
- 6x6x6 Push Button: Aliexpress | Shopee Thailand
- M3x10 Button Head Screws: Aliexpress | Shopee Thailand
- M2 Self Tapping Screws: Aliexpress | Shopee Thailand
- M2 Plastic Washer: Aliexpress | Shopee Thailand
Key Features of AHT21 and CCS811 Sensors
AHT21 Sensor
The AHT21 is an inexpensive and highly accurate temperature and humidity sensor. It offers a relative humidity accuracy of ±2% and a temperature accuracy of ±0.3 °C.
These specifications make it a reliable choice for indoor air quality monitoring, ensuring that the readings you get for humidity and temperature are precise and consistent.
With its compact size and I2C interface, the AHT21 is easy to integrate into your DIY projects. The I2C interface greatly reduces wiring clutter by enabling multiple sensors on the same bus using just a pair of data wires from the MCU.
This making it an excellent option for various applications, including environmental monitoring and smart home setups.
AHT21 Technical Parameter
- Supply voltage: DC 2.2 – 5.5V
- Measuring range (humidity): 0 ~ 100% RH
- Measuring range (temperature): -40 ~ + 120 ℃
- Humidity accuracy: ± 2 % RH ( 25 ℃ )
- Temperature accuracy: ± 0.3 ℃
- Resolution: temperature: 0.01℃ Humidity: 0.024%RH
CCS811 Sensor
The CCS811 is a versatile air quality sensor capable of measuring equivalent calculated carbon dioxide (eCO2) and Total Volatile Organic Compound (TVOC) concentrations.
It can detect eCO2 concentrations within a range of 400 to 8192 parts per million (ppm) and TVOC concentrations within a range of 0 to 1187 parts per billion (ppb).
According to the datasheet, the CCS811 can detect a variety of organic compounds, including:
- Alcohols
- Aldehydes
- Ketones
- Organic Acids
- Amines
- Aliphatic Hydrocarbons
- Aromatic Hydrocarbons
These capabilities make the CCS811 an essential component for monitoring indoor air quality, as it can provide insights into the presence of harmful pollutants and help ensure a healthier living environment.
Please note, this sensor, like all VOC/gas sensors, has variability and to get precise measurements you will need to calibrate it against known sources. That said, for general environmental sensors, it will give you a good idea of trends and comparisons.
DIY I2C Hub
To simplify wiring and split the I2C interface. I made a DIY I2C hub using a single-side strip prototype board.
Although it’s slightly more fragile than the green prototype board I usually use, this board is easier to cut and significantly reduces the time required to bridge connections between each connector.
The prototype board was cut to dimensions of 5 rows by 14 columns. Then you can soldered components according to the following diagram.
The pull-up resistors (3.3-10KΩ) were place in parallel from both SDA and SCL line to VCC line. You also need 4 pins JST-XH connectors for the input from ESP32 and output to AHT21 and the OLED display. While CCS811 sensor will need 5 pin connector because it need extra WAK pin that need to connect to GND.
For more information on the I2C bus and the importance of pull-up resistors, you can refer to this useful resource: How Many Devices Can You Connect to the I2C Bus?
Momentary Button
I’ve also use strip prototype board for mounting the momentary switch for the AUX button. I’ve cut the strip board to 5 rows by 6 columns.
I use 2mm drill bit to enlarge the corner holes for mounting it to the enclosure with M2x6 self-tap screws.
I’ve connect ground wire to pin 1 of 6×6 push button and the wire from GPIO27 to pin 3 to trigger the binary sensor.
The 3D Print Enclosure
I print the enclosure with eSun PLA+, it’s easy to print and suitable for indoor use.
The enclosure consists of two parts: a cover and a bottom section. The display is mounted to the cover using M2x8 self-tapping screws.
You may also need plastic washer if the display’s mounting holes are too large for the M2 screw, as the mount hole size can vary depending on the supplier.
I’ve used M3x10 ball head screws to secure the cover to the bottom part. You may need to tap the hole with an M3 tap for easier to screwing in.
The ESP32, sensors, and I2C hub are mounted in the provided slots on the bottom part and secured in place using hot melt glue or 3M VHB tape.
ESPHome YAML
The YAML configuration for AHT21 and CCS811 sensor:
According to data sheet recoomendation, the CCS811 need to burn-in for 48 hours when you first receive it, and then 20 minutes in the desired mode every time the sensor is in use.
This process helps stabilize the sensor’s sensitivity levels, which can fluctuate during early use.
To calibrate the sensor and get baseline:
value to obtain more accurate readings, please refer to the “Calibrating Baseline” section in the ESPHome documentation.
i2c: sda: GPIO21 scl: GPIO22 sensor: - platform: aht10 variant: AHT20 temperature: name: Temperature id: aht_temp humidity: name: Humidity id: aht_humid update_interval: 60s - platform: ccs811 id: WS_css811 eco2: name: "eCO2" id: eco2 tvoc: name: "TVOC" id: tvoc address: 0x5A baseline: 0x90BB #Replace with value from your calibration temperature: aht_temp humidity: aht_humid update_interval: 60s - platform: internal_temperature name: "ESP32 Temperature" id: int_temp
The YAML configuration for AUX button:
I’ve use a binary_sensor
component for an AUX button that wire to GPIO27. Then use on_click:
to control both short and long presses of the button.
Depending on the press duration, it triggers actions through “template switch” or “template button”. These actions toggle the display on or off and enable page navigation within the module.
This AUX button can easily customise, you can edit its action or add additional template buttons or template switches to perform different functions.
Please note that all template buttons and switches are exposed to Home Assistant, allowing control directly from the dashboard.
Binary Sensor: Monitors GPIO27 as an input and handles two types of button presses:
- Short press (50ms to 350ms): Triggers
button.press
action forpage_button
. - Long press (3s to 10s): Toggles the
display_switch
.
binary_sensor: - platform: gpio name: "Switch 1" id: aqi_switch1 internal: True pin: number: GPIO27 inverted: true mode: input: true pullup: true on_click: - min_length: 50ms max_length: 350ms then: - button.press: page_button - min_length: 3s max_length: 10s then: - switch.toggle: display_switch
Template Button: Perform some action turn on the display and switch display to next page.
button: - platform: template name: "Page Button" id: page_button on_press: - lambda: id(aqi_display).turn_on(); - lambda: id(aqi_display).set_contrast(1.0); - display.page.show_next: aqi_display - component.update: aqi_display
Template Switch: Represents the state of the display and use
Checks the current contrast level of the display (lambda:
) to determine the switch state.aqi_display
switch: - platform: template name: "Display Switch" id: display_switch lambda: |- if (id(aqi_display).get_contrast() > 0) { return true; } else { return false; } turn_on_action: - lambda: id(aqi_display).turn_on(); - lambda: id(aqi_display).set_contrast(1.0); - delay: 30s - lambda: id(aqi_display).set_contrast(0.5); turn_off_action: - lambda: id(aqi_display).set_contrast(0.0); - lambda: id(aqi_display).turn_off();
The YAML configuration for SH1106 Display:
font: #Opensans - file: 'fonts/OpenSans-Regular.ttf' id: opensans_reg_12 size: 12 #Opensans - Bold - file: 'fonts/OpenSans-Bold.ttf' id: opensans_bold_10 size: 10 - file: 'fonts/OpenSans-Bold.ttf' id: opensans_bold_14 size: 14 #Opensans - Condense Bold - file: 'fonts/OpenSans-Bold.ttf' id: opensans_CondBold_24 size: 24 - file: 'fonts/OpenSans-CondBold.ttf' id: opensans_CondBold_36 size: 36 display: - platform: ssd1306_i2c model: "SH1106 128x64" id: aqi_display address: 0x3C contrast: 1.0 pages: - id: page1 lambda: |- // Print TVOC it.printf(32, 0, id(opensans_bold_14), TextAlign::TOP_CENTER, "TVoc:"); if (id(tvoc).has_state()) { it.printf(32, 18, id(opensans_CondBold_24), TextAlign::TOP_CENTER, "%.0f", id(tvoc).state); } else { it.printf(32, 18, id(opensans_CondBold_24), TextAlign::TOP_CENTER, "N/A"); } it.printf(32, 48, id(opensans_bold_10), TextAlign::TOP_CENTER, "ppb"); // Print eco2 it.printf(96, 0, id(opensans_bold_14), TextAlign::TOP_CENTER, "eCO2:"); if (id(eco2).has_state()) { it.printf(96, 18, id(opensans_CondBold_24), TextAlign::TOP_CENTER, "%.0f", id(eco2).state); } else { it.printf(96, 18, id(opensans_CondBold_24), TextAlign::TOP_CENTER, "N/A"); } it.printf(96, 48, id(opensans_bold_10), TextAlign::TOP_CENTER, "ppm"); - id: page2 lambda: |- // Print Temp it.printf(0, 0, id(opensans_bold_10), TextAlign::TOP_LEFT, "Temperature:"); if (id(aht_temp).has_state()) { it.printf(127, 0, id(opensans_bold_14), TextAlign::TOP_RIGHT, "%.1f°C", id(aht_temp).state); } // Print Humid it.printf(0, 24, id(opensans_bold_10), TextAlign::TOP_LEFT, "Humidity:"); if (id(aht_humid).has_state()) { it.printf(127, 24, id(opensans_bold_14), TextAlign::TOP_RIGHT, "%.1f%%", id(aht_humid).state); } // Print CPU Temp it.printf(0, 48, id(opensans_bold_10), TextAlign::TOP_LEFT, "CPU Temp:"); if (id(int_temp).has_state()) { it.printf(127, 48, id(opensans_bold_14), TextAlign::TOP_RIGHT, "%.1f°C", id(int_temp).state); }
Conclusion
With just a simple ESP32 development module, a couple of sensors, and a little time, we can make a compact AQI module to monitor real-time environmental data and personalised control in your smart home setup.
Unlike off-the-shelf solutions, this DIY module offers several advantages. Not only is it significantly cheaper than commercial equivalents, but it also provides greater control and customisation options. The ESPHome firmware is very powerful and easy to use. It allows you to tailor the module to your specific needs.
1 Response
[…] For more information about wiring diagram and component list please refer to main project page: Simple DIY AQI Module: Measure TVOC, CO2, Humidity, and Temperature […]