Air quality is a critical yet often overlooked aspect of our daily lives. To address this, I designed and built a portable air quality monitor. The device is compact and reliable, leveraging the Beetle ESP32-C3 to connect to a suite of precision sensors.
I housed the electronics in a custom 3D-printed enclosure from JUSTWAY. The monitor measures temperature, humidity, volatile organic compounds (VOCs), carbon dioxide equivalents (CO₂eq), and particulate matter (PM). For remote monitoring, it sends real-time data to ThingSpeak Cloud.
Packed within this sleek, compact unit is a powerful combination of precision sensors, working in unison to paint a complete picture of the air around you:
- Temperature & Humidity: The SHT4x sensor serves as the foundation, providing accurate readings that set the context for all other measurements.
- Chemical Pollutants: The SGP30 acts as a nose for the device, detecting and estimating levels of Volatile Organic Compounds (tVOCs) and CO₂ Equivalents (CO₂eq).
- Particulate Matter: The DFRobot PM sensor then quantifies the unseen, counting airborne particles ranging from a microscopic 0.3µm up to 10µm.
Together, these sensors provide a comprehensive, real-time snapshot of air quality, making the device a powerful tool whether it's used in a home, a lab, or out in the field.
The foundation of this portable monitor is a selection of high-quality components, each chosen for its specific role in creating a reliable and effective device.
The brain of the operation is the Beetle ESP32-C3 from Seeed Studio, an ultra-small and power-efficient microcontroller that handles all data processing and wireless communication. This enables the device to seamlessly connect to the cloud via its built-in Wi-Fi.
For environmental sensing, I integrated a trio of precision sensors: the SHT4x for accurate temperature and humidity readings;
the SGP30, which functions as a digital nose to detect harmful indoor air pollutants like VOCs and CO₂eq;
and the DFRobot Air Quality Sensor, a powerful module that provides a detailed breakdown of particulate matter by counting particles across six size bins.
To protect this sensitive hardware, I designed a custom case that was professionally 3D printed by JUSTWAY.
The enclosure is not only durable and lightweight but also meticulously crafted to ensure the sensors perform optimally while making the entire unit easy to carry. Finally, a rechargeable battery was integrated to ensure the monitor could operate for extended periods, freeing it from the constraints of a power cord.
🧱 Enclosure DesignThe enclosure was designed and printed by JUSTWAY, tailored to fit the Beetle ESP32-C3 and all sensors snugly. It includes:
- Ventilation slots for accurate air sampling
- Mounting points for the battery
- USB access for charging and firmware updates
The result is a sleek, durable, and field-ready device that looks as good as it performs.
🚀JUSTWAY – Custom 3D Printing with PrecisionTo bring this project into the physical world, I partnered with JUSTWAY for a custom 3D-printed enclosure. Their service delivered a sleek, durable case tailored to the Beetle ESP32-C3 and sensor layout, with thoughtful features like ventilation slots, battery mounts, and USB access. JUSTWAY’s print quality and turnaround time made the difference between a prototype and a polished, field-ready device.
After the hardware assembly, create a new channel on the ThinkSpeak.
Finish up by adding the fields and save the channel.
Noted down the API key and later will use it in the Arduino sketch.
🤩JUSTWAY’s Summer Cashback Event💰 JUSTWAY is offering 15% cashback on all orders placed between June 1st – August 31st, 2025 (UTC). If you’re 3D printing your enclosure or need CNC machining, this is the best time to save!
Follow this link to get a summer cashback from JUSTWAY
🔥 Why You Should Use This Deal
✔ Get 15% cashback to reduce production costs
✔ Works for CNC, 3D Printing, and Sheet Metal fabrication
✔ Cashback credited within 3 business days
✔ Combine cashback with coupons and loyalty points for extra savings!
📌 How to Apply1️⃣ Place an order with JUSTWAY between June 1st – August 31st, 2025.
2️⃣ Once your order is completed, email marketing03@justway.com with your order number and JUSTWAY account details.
3️⃣ JUSTWAY will review your order, and upon approval, 15% of your order value will be credited to your account.
🧩 Software HighlightsThe firmware is written in Arduino and includes:
- I²C sensor integration
- Absolute humidity compensation for SGP30
- Wi-Fi connectivity and HTTP GET requests
- LED activity indicator
- Formatted serial output for debugging
The code is modular and optimized for reliability, with clear separation between sensor reads and cloud transmission.
#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include "sensirion_common.h"
#include "sgp30.h"
#include <SensirionI2cSht4x.h>
#include "DFRobot_AirQualitySensor.h"
// --- Wi-Fi Credentials ---
const char* ssid = "";
const char* password = "";
// --- ThingSpeak ---
const char* server = "http://api.thingspeak.com/update";
const char* apiKey = ""; // Write API Key
// --- Constants ---
#define LED_PIN 10
#define NO_ERROR 0
#define PM_I2C_ADDRESS 0x19
// --- Sensor Objects ---
SensirionI2cSht4x sht4x;
DFRobot_AirQualitySensor particle(&Wire, PM_I2C_ADDRESS);
// --- Globals ---
s16 sgpError;
u16 scaled_ethanol_signal, scaled_h2_signal;
void setup() {
Serial.begin(115200);
Wire.begin();
pinMode(LED_PIN, OUTPUT);
// --- Wi-Fi Init ---
Serial.print("Connecting to Wi-Fi");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi connected!");
// --- PM Sensor Init ---
while (!particle.begin()) {
Serial.println("[PM] Sensor not found. Retrying...");
delay(1000);
}
Serial.println("[PM] Sensor initialized.");
Serial.print("[PM] Version: ");
Serial.println(particle.gainVersion());
// --- SHT4x Init ---
sht4x.begin(Wire, SHT40_I2C_ADDR_44);
sht4x.softReset();
delay(10);
// --- SGP30 Init ---
while (sgp_probe() != STATUS_OK) {
Serial.println("[SGP30] Probe failed. Retrying...");
delay(1000);
}
sgpError = sgp_measure_signals_blocking_read(&scaled_ethanol_signal, &scaled_h2_signal);
if (sgpError == STATUS_OK) {
Serial.println("[SGP30] RAM signal read successful.");
}
// --- Set Absolute Humidity ---
float temperature = 0.0, humidity = 0.0;
if (sht4x.measureLowestPrecision(temperature, humidity) == NO_ERROR) {
float absHumidity = 216.7f * ((humidity / 100.0f) * 6.112f *
exp((17.62f * temperature) / (243.12f + temperature)) /
(273.15f + temperature));
sgp_set_absolute_humidity((u32)(absHumidity * 1000)); // mg/m³
Serial.print("[SGP30] Absolute Humidity set to: ");
Serial.print(absHumidity, 2);
Serial.println(" g/m³");
}
sgp_iaq_init();
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500);
// --- SHT4x Read ---
float temperature = 0.0, humidity = 0.0;
sht4x.measureLowestPrecision(temperature, humidity);
// --- SGP30 IAQ Read ---
u16 tvoc_ppb = 0, co2_eq_ppm = 0;
sgp_measure_iaq_blocking_read(&tvoc_ppb, &co2_eq_ppm);
// --- PM Sensor Read ---
uint16_t pm_0_3 = particle.gainParticleNum_Every0_1L(PARTICLENUM_0_3_UM_EVERY0_1L_AIR);
uint16_t pm_0_5 = particle.gainParticleNum_Every0_1L(PARTICLENUM_0_5_UM_EVERY0_1L_AIR);
uint16_t pm_1_0 = particle.gainParticleNum_Every0_1L(PARTICLENUM_1_0_UM_EVERY0_1L_AIR);
uint16_t pm_2_5 = particle.gainParticleNum_Every0_1L(PARTICLENUM_2_5_UM_EVERY0_1L_AIR);
// --- Send to ThingSpeak ---
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
String url = String(server) + "?api_key=" + apiKey +
"&field1=" + String(temperature, 2) +
"&field2=" + String(humidity, 2) +
"&field3=" + String(tvoc_ppb) +
"&field4=" + String(co2_eq_ppm) +
"&field5=" + String(pm_0_3) +
"&field6=" + String(pm_0_5) +
"&field7=" + String(pm_1_0) +
"&field7=" + String(pm_2_5);
http.begin(url);
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[ThingSpeak] Data sent. Response code: %d\n", httpCode);
} else {
Serial.printf("[ThingSpeak] Failed to send data. Error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
} else {
Serial.println("[Wi-Fi] Not connected.");
}
digitalWrite(LED_PIN, LOW);
delay(10000); // ThingSpeak allows 15s minimum interval
}
☁️ Cloud IntegrationUsing the onboard Wi-Fi, the monitor pushes data to ThingSpeak every 15 seconds. This enables:
- Real-time visualization of air quality trends
- Remote access from any device
- Historical data logging for analysis
Each sensor value is mapped to a ThingSpeak field:
You can view the live dashboard on ThingSpeak Channel 3044704.
Indoor air quality monitoring
- Indoor air quality monitoring
- Classroom or lab experiments
- Portable environmental sensing
- Smart home integration
This project wouldn’t have come together without the support and quality components from some incredible platforms:
DFRobot – for their high-resolution PM sensor that delivers reliable, granular air quality data. Their sensors are a maker’s dream: accurate, compact, and easy to integrate.
Seeed Studio – for the Beetle ESP32-C3 and Grove sensors that made this build modular, scalable, and cloud-ready. Their ecosystem continues to empower rapid prototyping and professional-grade IoT development.
JUSTWAY – for the precision 3D printing service that transformed my design into a sleek, durable enclosure. Their attention to detail and fast turnaround made the hardware feel truly polished.
💬 Final ThoughtsThis project combines precision sensing, wireless connectivity, and thoughtful design into a compact, battery-powered air quality monitor. Whether you're a maker, student, or environmental enthusiast, this build offers a powerful platform for exploring the invisible world around us.
Follow this link to get a Summer cashback from JUSTWAY
🛒 Order your 3D prints today via JUSTWAY and claim your 15% cashback before August 31st, 2025! 📩 After ordering, email marketing03@justway.com to apply for cashback!
Comments