Commercial air purifiers run their fans at a single noisy speed regardless of whether the air actually needs cleaning. Smart variants exist but cost $400. The hardware to build your own — a particle sensor and a PWM-controllable fan — totals $50. Speed scales smoothly with measured PM2.5; clean room means whisper-quiet, smoky room means full power.
How it works
UART] -->|PM1.0 / PM2.5 / PM10| ESP32 ESP32[[ESP32]] --> PWM[PWM signal
25 kHz] PWM --> MOSFET[IRLZ44N MOSFET
or 4-pin PWM input] MOSFET --> Fan[12V 120mm fan] Fan --> Filter[HEPA filter] --> Air ESP32 -->|MQTT readings| HA[Home Assistant]
Closed-loop control: sensor reads air, fan adjusts, filter cleans, sensor reads again. The PWM frequency must be above 20 kHz to stay above audible.
Bill of materials
- ESP32 dev board — $7
- PMS5003 / PMS7003 laser PM sensor — $20
- 120 mm 12 V PWM fan (4-pin, server-grade Noctua iPPC works great) — $15
- HEPA filter cartridge (Panasonic or generic 200 × 200 mm) — $8
- 12 V / 2 A power supply — $6
- Plywood, screws, sheet metal for enclosure — $10
Total around $66 (a bit over our nominal $50 because of the filter; if you can salvage one from a dead vacuum cleaner, you save $8).
Wiring
PMS5003 ESP32
VCC --- 5V (Vin)
GND --- GND
TX --- GPIO 16 (RX2)
RX --- GPIO 17 (TX2, optional)
SET --- 3.3V (always on; pull LOW to sleep)
RESET --- 3.3V
PWM fan ESP32 / 12V
+12V --- 12V supply
GND --- 12V GND (and ESP32 GND, common)
PWM (blue)-- GPIO 25 (25 kHz PWM)
TACH (yellow) - GPIO 26 (optional, for RPM)4-pin PWM fans accept the PWM signal directly on the blue wire, no MOSFET needed. The PWM signal is 5V tolerant in the spec but ESP32 outputs 3.3V which works fine on every fan I've tried. If your fan is 3-pin (no PWM input), you need a MOSFET to PWM the 12V supply line.
Firmware design
The PMS5003 sends a 32-byte frame over UART every second. The PMS library parses it into PM1.0, PM2.5, PM10 (µg/m³). The control loop reads the latest value, applies a smoothing filter (5-sample running average), and maps it to fan duty cycle:
- PM2.5 < 12 µg (good): 20% fan
- PM2.5 12-35 (moderate): 30-50%
- PM2.5 35-100 (unhealthy): 50-90%
- PM2.5 > 100 (very unhealthy): 100%
The mapping is configurable; the firmware exposes the breakpoints in config.h.
Going further
- Run two of these in different rooms; compare readings on a Grafana dashboard to find the worst air spots in the house.
- Add an SCD30 to also track CO2 — opening a window when CO2 is high can paradoxically increase PM2.5, so the dashboard is informative.
- Schedule the fan to ramp up before you wake (allergies improve when the bedroom is pre-cleaned).
Key code: main loop
This is the heart of the firmware, taken from the working sketch. The complete file (with config template, library list, and the rest of the helpers) is around 74 lines and is included in the downloadable project package — request it via the form below.
void loop() {
pms.requestRead();
if (pms.readUntil(pmsData, 2000)) {
// Running average over last 5 reads (rough smoothing)
pm25Avg = pm25Avg * 0.8f + pmsData.PM_AE_UG_2_5 * 0.2f;
uint8_t duty = pm25ToDuty(pm25Avg);
ledcWrite(PWM_CHANNEL, duty);
Serial.printf("[pm] 1.0=%d 2.5=%d 10=%d avg25=%.1f duty=%d\n",
pmsData.PM_AE_UG_1_0, pmsData.PM_AE_UG_2_5,
pmsData.PM_AE_UG_10_0, pm25Avg, duty);
if (millis() - lastReportMs > 30000) {
lastReportMs = millis();
mqttPublish(pmsData.PM_AE_UG_1_0, pmsData.PM_AE_UG_2_5,
pmsData.PM_AE_UG_10_0, duty);
}
}
mqtt.loop();
delay(2000);
}Get the complete project package
The article above shows the core firmware and the principles behind it. The complete project package — assembled, tested, and ready to flash — is available by email request. We send it manually, and we read every request.
- Complete Arduino sketch (.ino) with full error handling
- List of required libraries with version numbers
- Printable wiring diagram (PDF)
- Bill of materials with current part numbers
- Build guide and troubleshooting tips
- Configuration template (WiFi, MQTT, etc.)
Share your thoughts
Worked with this in production and have a story to share, or disagree with a tradeoff? Email us at support@mybytenest.com — we read everything.