A self-contained, Home-Assistant-integrated indoor air quality monitor. The unit watches CO₂, temperature, humidity, VOC index, NOx index, PM1.0 / PM2.5 / PM10, and ambient light, displays everything on a local OLED, exhausts air through a built-in 30 mm fan to keep the sensors fed with fresh air, and reports to Home Assistant over WiFi.
I currently have nine of these running in my home and they've been working well — the design is solid and repeatable. This project documents how to build one.
Five of the nine units I've built using this design.
The included Home Assistant dashboard — live tiles, gauges, and 24-hour history graphs for every sensor.
- Hardware: ESP32-S3 + 5 I²C sensors (CO₂, temp/hum, VOC/NOx, light, OLED) + UART-attached Plantower particulate sensor + transistor-driven 30 mm fan, all in a sealed IP65 ABS enclosure with a USB-C power inlet.
- Firmware (ESPHome): Multi-page OLED rotation (clock, environment, gases, particulates), reverse-video FAIR / POOR alerts, an onboard RGB status LED, and
delta:filters that reduce HA traffic ~70% without losing dashboard fidelity. - Home Assistant integration: Ready-to-paste single-page Lovelace dashboard with all metrics organized in sections, automatic detection and recovery for the SCD41's known firmware-hang behavior, and offline-device notifications.
| Doc | What's in it |
|---|---|
| docs/HARDWARE.md | Full Bill of Materials with links and specs — exact parts I used, why each was chosen, sensor-quality discussion, GPIO assignments |
| docs/ASSEMBLY.md | Physical build: drilling the enclosure, mounting components, PCB layout, wire-wrapping technique, hot-glue light filter for the BH1750 |
| docs/FIRMWARE.md | Firmware design decisions: multi-page display rotation, FAIR/POOR alerts, status LED color scheme, data-traffic delta filters with reasoning per sensor, calibration explanations |
| docs/wiring/ | Two wiring diagrams: the 5V power domain (USB-C, ESP, PMS, fan) and the 3.3V / I²C domain (the 5 sensors) |
| docs/photos/ | Build progression and finished-unit photos |
AQ Monitor Project/
├── README.md ← this file
├── LICENSE
├── docs/
│ ├── HARDWARE.md ← parts list + sourcing notes
│ ├── ASSEMBLY.md ← physical build, photos, wire-wrap notes
│ ├── photos/ ← 7 build/finished-unit photos
│ └── wiring/
│ ├── 01-5v-power-domain.png
│ └── 02-i2c-domain.png
├── esphome/
│ └── aq-monitor.yaml ← full ESPHome firmware
├── automations/ ← copy-paste into HA's UI YAML editor
│ ├── co2-freeze-auto-restart.yaml
│ └── offline-notification.yaml
├── dashboards/
│ └── aq_monitor_dashboard.yaml ← ready-to-use Lovelace dashboard YAML
├── packages/
│ └── aq_monitor.yaml ← (alternative) same automations as a
│ single HA package file for users who
│ already use packages
└── scripts/
└── generate_dashboard.py ← regenerate the dashboard YAML
(only needed if you want to customize
or scale to multiple devices)
-
Build the hardware — see docs/HARDWARE.md for the BOM and docs/ASSEMBLY.md for the build process.
-
Flash the ESP32-S3 with esphome/aq-monitor.yaml. Copy esphome/secrets.example.yaml to
esphome/secrets.yamland fill in your WiFi + API keys. Before flashing, changealtitude_compensation: 670mto your local elevation in meters — the SCD41 uses altitude for atmospheric-pressure compensation, and using the default 670 m at sea level (or vice versa) introduces meaningful CO₂ errors. After flash + boot + HA connection, you'll seesensor.aq_monitor_co2, etc. -
Add the automations — for each file in automations/, in HA go to Settings → Automations & Scenes → "+ Create Automation" → "Start with an empty automation" → ⋮ → "Edit in YAML" → paste the contents of the file (everything from
alias:down). Save. Two automations total — one for SCD41 freeze auto-restart, one for offline-device notification. -
Install the dashboard — in HA, Settings → Dashboards → "+ Add Dashboard" → "New dashboard from scratch". Title: "AQ Monitor", icon
mdi:air-filter. Open the new (empty) dashboard, top-right ⋮ → Edit Dashboard → ⋮ → Raw Configuration Editor, delete what's there, paste the contents of dashboards/aq_monitor_dashboard.yaml. Save.
Working and stable. Tested with ESPHome 2026.4.x and Home Assistant Core 2026.4.x. The firmware has been running continuously without issue.
Known limitations — the SCD41 in low_power_periodic mode occasionally hangs (the included automation auto-recovers it). Some AYWHP ESP32-S3 boards may exhibit minor batch-to-batch variance in their on-board LDOs that can cause WiFi auth issues; the YAML header documents the workaround if you hit it.
MIT — see LICENSE.