ESP32 CO₂ Monitoring with SCD30 & SCD41 (ESPHome & MQTT) – Complete 2025 Guide

Monitoring indoor CO₂ levels is one of the most effective ways to track air quality, improve ventilation, and maintain a healthy indoor environment.
The SCD30 and SCD41 are two of the best consumer-grade CO₂ sensors, offering accurate measurements and excellent long-term stability.

This guide explains how to connect an ESP32 to either the SCD30 or the SCD41, and integrate them into Home Assistant using ESPHome and MQTT.


1. Sensors Covered in This Guide

SCD30 – CO₂, Temperature, Humidity

  • Accuracy: ±30 ppm
  • Interface: I2C
  • Warm-up: ~30 seconds
  • Best for: affordable reliable CO₂ monitoring

SCD41 – Next-gen CO₂ sensor

  • Accuracy: ±20 ppm
  • Faster response
  • Lower power
  • Best for: high-precision indoor monitoring

2. Hardware Required

  • ESP32 DevKit
  • SCD30 or SCD41 sensor
  • Jumper wires
  • USB cable
  • (Optional) Enclosure with air vents

3. Wiring Diagrams

Shared I2C Wiring (both SCD30 and SCD41)

ESP32 → Sensor  
3.3V → VIN  
GND  → GND  
GPIO21 → SDA  
GPIO22 → SCL  

Both sensors operate at 3.3V and support I2C directly.


METHOD 1 — ESPHome Integration

ESPHome has native support for both SCD30 and SCD41, making integration extremely easy.


4. ESPHome YAML for SCD30

esphome:
  name: esp32-scd30
  platform: ESP32
  board: esp32dev

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

logger:
api:
ota:

i2c:
  sda: 21
  scl: 22
  scan: true

sensor:
  - platform: scd30
    co2:
      name: "SCD30 CO2"
    temperature:
      name: "SCD30 Temperature"
    humidity:
      name: "SCD30 Humidity"
    update_interval: 10s

5. ESPHome YAML for SCD41

esphome:
  name: esp32-scd41
  platform: ESP32
  board: esp32dev

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

logger:
api:
ota:

i2c:
  sda: 21
  scl: 22
  scan: true

sensor:
  - platform: scd4x
    co2:
      name: "SCD41 CO2"
    temperature:
      name: "SCD41 Temperature"
    humidity:
      name: "SCD41 Humidity"
    update_interval: 10s

Both devices appear automatically in Home Assistant.


METHOD 2 — MQTT Integration (Manual HA YAML)

This section matches the same style used for BME280, SHT45, DS18B20 and PZEM articles.

The ESP32 publishes JSON via MQTT, and Home Assistant reads individual values.


6. Home Assistant configuration.yaml – SCD30/SCD41 MQTT Sensors

Add under:

mqtt:
  sensor:

Correct MQTT YAML Block (Works for Both Sensors)

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

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

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

Same MQTT topic, three values inside JSON.


7. ESP32 Arduino Code for MQTT (Supports SCD30 and SCD41)

Choose your sensor:

#define USE_SCD30   // or comment out and use SCD41

Full Code

#include <WiFi.h>
#include <PubSubClient.h>

#define WIFI_SSID "YOUR_WIFI"
#define WIFI_PASS "YOUR_PASSWORD"
#define MQTT_SERVER "192.168.0.10"

WiFiClient espClient;
PubSubClient client(espClient);

// -------------------------------------
// Choose sensor
// -------------------------------------
#define USE_SCD30
// #define USE_SCD41

#ifdef USE_SCD30
  #include <Adafruit_SCD30.h>
  Adafruit_SCD30 scd30;
#endif

#ifdef USE_SCD41
  #include <SensirionI2CScd4x.h>
  SensirionI2CScd4x scd41;
#endif

void setup() {
  Serial.begin(115200);

#ifdef USE_SCD41
  Wire.begin(21, 22);
  scd41.begin(Wire);
  scd41.startPeriodicMeasurement();
#endif

#ifdef USE_SCD30
  scd30.begin();
  scd30.setMeasurementInterval(2);
#endif

  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(500);

  client.setServer(MQTT_SERVER, 1883);
}

void loop() {
  if (!client.connected()) {
    while (!client.connected()) client.connect("ESP32_CO2");
  }

#ifdef USE_SCD30
  if (scd30.dataReady()) {
    scd30.read();
    float co2 = scd30.CO2;
    float temp = scd30.temperature;
    float hum = scd30.relative_humidity;

    String json = "{";
    json += "\"co2\":" + String(co2) + ",";
    json += "\"temperature\":" + String(temp) + ",";
    json += "\"humidity\":" + String(hum);
    json += "}";

    client.publish("home/air/quality", json.c_str());
  }
#endif

#ifdef USE_SCD41
  uint16_t co2; float temp, hum;
  scd41.readMeasurement(co2, temp, hum);

  String json = "{";
  json += "\"co2\":" + String(co2) + ",";
  json += "\"temperature\":" + String(temp) + ",";
  json += "\"humidity\":" + String(hum);
  json += "}";

  client.publish("home/air/quality", json.c_str());
#endif

  client.loop();
  delay(3000);
}

8. Home Assistant Dashboard Example

type: vertical-stack
cards:
  - type: sensor
    entity: sensor.co2_level
  - type: sensor
    entity: sensor.air_temperature
  - type: sensor
    entity: sensor.air_humidity
  - type: history-graph
    entities:
      - sensor.co2_level
      - sensor.air_temperature
      - sensor.air_humidity


9. Recommended Automations

Ventilate the room when CO₂ exceeds 1,000 ppm

automation:
  - alias: "Ventilation Alert"
    trigger:
      - platform: numeric_state
        entity_id: sensor.co2_level
        above: 1000
    action:
      - service: notify.mobile_app
        data:
          message: "CO₂ levels are high — open a window."

Automatically turn on HVAC ventilation

  - alias: "HVAC Ventilate on High CO2"
    trigger:
      - platform: numeric_state
        entity_id: sensor.co2_level
        above: 1200
    action:
      - service: climate.set_fan_mode
        target:
          entity_id: climate.living_room
        data:
          fan_mode: "high"

10. Accuracy Comparison: SCD30 vs SCD41

FeatureSCD30SCD41
CO₂ Accuracy±30 ppm±20 ppm
Warm-up30–45 sec15 sec
Power UseMediumLow
I2CYesYes
Recommended ForGeneral IAQHigh-precision environments

11. Troubleshooting

CO₂ values stuck or too slow

  • Sensors require 1–3 minutes of warm-up
  • Avoid enclosed boxes — airflow needed

Temperature seems too high

  • CO₂ sensors generate self-heat
  • Increase update interval in ESPHome to reduce drift

Sensor not detected

  • Enable i2c: scan: true
  • Check pin order (SCD41 pins can be inverted on cheap clones)
  • Use short wires (<30 cm)

Keywords

esp32 scd30
esp32 scd41
home assistant co2 monitor
esphome scd30
esphome scd41
mqtt co2 sensor esp32
esp32 air quality sensor
scd30 vs scd41
indoor co2 monitoring home assistant

Newsletter Updates

Enter your email address below and subscribe to our newsletter

Leave a Reply

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