Home Assistant Air Quality Monitor: CO₂/VOC/PM2.5

Build an ESP32 air quality station for Home Assistant using SCD41 or SCD30 for CO₂, SGP40 for VOCs, and SDS011 for PM2.5, with clear wiring, ESPHome setup, MQTT alternatives, dashboard examples, and practical automations for healthier indoor air.

If you want one “single source of truth” for indoor air quality, the most useful combo is:

  • CO₂ (ventilation / human presence proxy) → SCD41 or SCD30
  • VOC index (chemicals, cooking, cleaners, off-gassing) → SGP40
  • PM2.5 / particulate (smoke, dust, outdoor pollution) → SDS011 or similar PM sensor

With an ESP32 you can build a compact air quality station that reports to Home Assistant and gives you:

  • A clear indoor air quality dashboard
  • Automations: fan boost / ventilation alerts / window reminders
  • Long-term trends and “what caused that spike?” insights

This guide shows wiring and configuration using ESPHome (recommended) plus an MQTT-style alternative approach.

1. Sensor Selection (Quick)

CO₂: SCD41 vs SCD30

  • SCD41: smaller, typically lower power, great modern choice
  • SCD30: older but excellent accuracy and stability

Both provide:

  • CO₂ (ppm)
  • Temperature (°C)
  • Humidity (%)

VOC: SGP40

  • Outputs VOC Index (not ppm)
  • Best interpreted as a trend/scale:
    • Lower is better, spikes indicate pollution events

PM2.5: SDS011 (UART)

  • Measures PM2.5 and PM10 (µg/m³)
  • Needs airflow and decent placement
  • Draws more current than the I²C sensors

2. Hardware Required

Optional but recommended:

  • Small 5 V fan for airflow (quiet, slow) for more stable PM readings
  • Display (OLED) if you want local readout (optional)

3. Wiring Overview

3.1 I²C Bus (SCD41/SCD30 + SGP40)

I²C is shared:

ESP32        SCD41/SCD30     SGP40
3.3V  -----> VCC             VCC
GND   -----> GND             GND
GPIO21 ----> SDA             SDA
GPIO22 ----> SCL             SCL

Note: Many breakouts already include pull-up resistors. If your setup behaves oddly (I²C instability), reduce cable length and avoid “too many pull-ups” on the same bus.

3.2 SDS011 (UART)

SDS011 typically uses 5 V power and UART TTL.

ESP32        SDS011
5V    -----> 5V
GND   -----> GND
GPIO16 (RX) <----- TX
GPIO17 (TX) -----> RX

(ESP32 TX/RX pins can be swapped; just match them in config.)

METHOD 1 — ESPHome Air Quality Station (Recommended)

ESPHome makes multi-sensor nodes clean and maintainable.

4. ESPHome Base Configuration

esphome:
  name: esp32-air-quality
  platform: ESP32
  board: esp32dev

wifi:
  ssid: "YOUR_WIFI"
  password: "YOUR_PASSWORD"

logger:
api:
ota:

5. I²C Configuration

i2c:
  sda: 21
  scl: 22
  scan: true

6. CO₂ Sensor (Choose One)

Option A: SCD41 (scd4x)

sensor:
  - platform: scd4x
    co2:
      name: "Air CO2"
      id: co2_ppm
    temperature:
      name: "Air Temperature"
      id: air_temp
    humidity:
      name: "Air Humidity"
      id: air_hum
    update_interval: 30s

Option B: SCD30

sensor:
  - platform: scd30
    co2:
      name: "Air CO2"
      id: co2_ppm
    temperature:
      name: "Air Temperature"
      id: air_temp
    humidity:
      name: "Air Humidity"
      id: air_hum
    update_interval: 30s

Use one, not both.

7. VOC Sensor (SGP40)

ESPHome commonly uses the sgp4x platform (VOC index output).

sensor:
  - platform: sgp4x
    voc:
      name: "VOC Index"
      id: voc_index
    update_interval: 30s

Better VOC accuracy: SGP sensors often benefit from temperature/humidity compensation. If your ESPHome setup supports it, feed temp/humidity into the VOC algorithm.

8. PM Sensor (SDS011)

SDS011 uses UART:

uart:
  rx_pin: 16
  tx_pin: 17
  baud_rate: 9600

Now define the sensor:

sensor:
  - platform: sds011
    pm_2_5:
      name: "PM2.5"
      id: pm25
    pm_10_0:
      name: "PM10"
      id: pm10
    update_interval: 60s

9. Optional: Derived “Air Quality Score” Sensors

Once you have the raw values, you can create a simple combined score to show as a single gauge.

Example: “poor if any is bad” logic as a text sensor:

text_sensor:
  - platform: template
    name: "Air Quality Status"
    lambda: |-
      if (id(co2_ppm).state > 1200 || id(pm25).state > 35 || id(voc_index).state > 200) {
        return {"Poor"};
      } else if (id(co2_ppm).state > 900 || id(pm25).state > 15 || id(voc_index).state > 120) {
        return {"OK"};
      } else {
        return {"Good"};
      }

This is deliberately simple and practical: it gives you a single “Good/OK/Poor” status that tracks what you actually care about.

10. Home Assistant Dashboard (Indoor Air Quality)

Once the ESPHome node is added, Home Assistant will have entities like:

  • sensor.air_co2
  • sensor.pm2_5
  • sensor.voc_index
  • sensor.air_temperature
  • sensor.air_humidity
  • text_sensor.air_quality_status (if added)

10.1 Dashboard Card Example

type: vertical-stack
cards:
  - type: gauge
    entity: sensor.air_co2
    name: CO₂ (ppm)
    min: 400
    max: 2000

  - type: gauge
    entity: sensor.pm2_5
    name: PM2.5 (µg/m³)
    min: 0
    max: 150

  - type: gauge
    entity: sensor.voc_index
    name: VOC Index
    min: 0
    max: 500

  - type: entities
    entities:
      - sensor.air_temperature
      - sensor.air_humidity
      - text_sensor.air_quality_status

11. Useful Home Assistant Automations

11.1 Ventilation Reminder (CO₂)

automation:
  - alias: "Air Quality – CO2 High"
    trigger:
      - platform: numeric_state
        entity_id: sensor.air_co2
        above: 1000
        for: "00:10:00"
    action:
      - service: notify.mobile_app
        data:
          title: "Air Quality"
          message: "CO₂ is high (>1000 ppm). Consider opening a window."

11.2 PM2.5 Alert (Smoke / Dust)

  - alias: "Air Quality – PM2.5 Spike"
    trigger:
      - platform: numeric_state
        entity_id: sensor.pm2_5
        above: 35
        for: "00:05:00"
    action:
      - service: notify.mobile_app
        data:
          title: "Air Quality"
          message: "PM2.5 is elevated. Check for smoke/cooking or outdoor pollution."

11.3 VOC Spike (Cooking / Cleaning)

  - alias: "Air Quality – VOC Spike"
    trigger:
      - platform: numeric_state
        entity_id: sensor.voc_index
        above: 200
        for: "00:05:00"
    action:
      - service: notify.mobile_app
        data:
          title: "Air Quality"
          message: "VOC index is high. Ventilation recommended."

METHOD 2 — MQTT Approach (If You Prefer Your MQTT Style)

ESP32 publishes one JSON payload:

Topic: home/air/quality

Payload:

{"co2": 842, "pm25": 6.2, "pm10": 11.4, "voc": 92, "t": 22.1, "h": 45.3}

Home Assistant:

mqtt:
  sensor:
    - name: "Air CO2"
      state_topic: "home/air/quality"
      value_template: "{{ value_json.co2 }}"
      unit_of_measurement: "ppm"

    - name: "PM2.5"
      state_topic: "home/air/quality"
      value_template: "{{ value_json.pm25 }}"
      unit_of_measurement: "µg/m³"

    - name: "PM10"
      state_topic: "home/air/quality"
      value_template: "{{ value_json.pm10 }}"
      unit_of_measurement: "µg/m³"

    - name: "VOC Index"
      state_topic: "home/air/quality"
      value_template: "{{ value_json.voc }}"

    - name: "Air Temperature"
      state_topic: "home/air/quality"
      value_template: "{{ value_json.t }}"
      unit_of_measurement: "°C"

    - name: "Air Humidity"
      state_topic: "home/air/quality"
      value_template: "{{ value_json.h }}"
      unit_of_measurement: "%"

This matches the working MQTT syntax you shared earlier.

12. Placement & Accuracy Tips (Matters More Than People Think)

  • Don’t place it directly next to a window or AC vent
  • Keep it away from kitchen hob if you want “whole room” readings
  • PM sensors need airflow:
    • Don’t suffocate the inlet/outlet
    • Consider a slow fan for stability
  • CO₂ sensors want stable air mixing:
    • Avoid corners and enclosed shelves
  • Give CO₂ sensors time to warm up and settle after power-up

Summary

A combined ESP32 air quality station is one of the most useful “always-on” Home Assistant nodes:

  • CO₂ tells you when ventilation is needed
  • VOC catches chemicals, cooking, cleaners and off-gassing
  • PM2.5 catches smoke, dust and outdoor pollution events
  • Everything appears as normal HA sensors with a clean dashboard and automations

Once installed, you’ll quickly learn the patterns of your home: what cooking does, what cleaning sprays do, and how quickly CO₂ rises overnight—then you can automate ventilation in a smart, targeted way.

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *