SCD40 vs SCD41 with ESP32 and ESPHome: Real CO₂ Monitoring for Home Assistant

Connect Sensirion SCD40 or SCD41 to ESP32 and ESPHome for real CO₂ monitoring in Home Assistant. Compare accuracy, wiring, low-power modes, self-calibration, offsets and ventilation alerts.

If you want actual CO₂ readings in Home Assistant, use an optical CO₂ sensor such as Sensirion SCD40 or SCD41—not an estimated “eCO₂” value from a VOC sensor. Both SCD4x variants measure carbon dioxide using photoacoustic optical sensing and provide temperature and humidity outputs over I²C. An ESP32 running ESPHome can publish all three measurements without custom firmware.

The SCD40 covers ordinary room ventilation monitoring; the SCD41 extends the specified CO₂ range and adds an on-demand single-shot mode for projects with a constrained power budget. This guide explains what the specification differences mean, how to wire either sensor safely, and how to avoid calibration and enclosure mistakes that can make a perfectly good sensor look wrong.

SCD40 vs SCD41: The practical differences

FeatureSCD40SCD41
Sensing principlePhotoacoustic optical CO₂Photoacoustic optical CO₂
Specified CO₂ measurement range400–2,000 ppm400–5,000 ppm
Published CO₂ output range0–40,000 ppm; not the accuracy range0–40,000 ppm; not the accuracy range
Accuracy in specified range±(50 ppm + 5% of reading), 400–2,000 ppmImproved and range-dependent; see Sensirion’s latest specification
Normal periodic measurementEvery 5 sEvery 5 s
Low-power periodic modeEvery 30 sEvery 30 s
Single-shot CO₂ measurementNot availableAvailable; approximately 5 s per measurement
Temperature and humidityIncludedIncluded
I²C address0x620x62
ESPHome componentscd4xscd4x

Sensirion’s current product pages list the SCD40 accuracy as ±(50 ppm + 5% of reading) from 400–2,000 ppm. The SCD41 provides an extended specified range to 5,000 ppm and improved accuracy specifications across subranges. Do not confuse the 0–40,000 ppm output range with a promise of accuracy up to 40,000 ppm.

For a normal bedroom, office or living-room monitor, either device provides useful measured CO₂ data. The SCD41 is particularly relevant if you expect higher concentrations, need its tighter specified accuracy, or want single-shot operation.

What “real CO₂” means here

The SCD4x series uses Sensirion’s photoacoustic optical technology. Its measurement is based on CO₂ absorption of infrared light, rather than inferring CO₂ from a different gas. That distinction matters when comparing it with a VOC sensor: cooking, perfume or cleaning products can cause a VOC-derived eCO₂ estimate to change even when actual CO₂ does not change proportionally.

A VOC sensor and an optical CO₂ sensor are complementary, not interchangeable. Keep VOC/IAQ trends for odour and chemical-source detection; use an SCD40/SCD41 when ventilation decisions depend on measured CO₂ concentration. The SCD4x is not a carbon-monoxide (CO) detector or a substitute for certified life-safety equipment.

Parts and wiring for a classic ESP32 DevKit

Use an SCD40/SCD41 breakout with an explicit 3.3 V logic specification, a classic ESP32 board, suitable cables and a ventilated enclosure. Many breakout boards add pull-up resistors, regulators and level shifting; the bare sensor’s supply specifications do not tell you what every third-party module does.

Sensor breakoutClassic ESP32 DevKitNotes
VCC / VIN3V3Use 3.3 V if the breakout supports it; check seller schematic
GNDGNDCommon ground required
SDAGPIO21Default I²C data example
SCLGPIO22Default I²C clock example

The bare SCD4x sensor supports a broad supply range, but ESP32 GPIOs are not 5 V tolerant. A breakout powered at 5 V can pull the I²C lines to 5 V unless it provides appropriate level translation. If the board labels are unclear, check the pull-up rail with a meter or use a documented 3.3 V-compatible module. Also account for the sensor’s brief measurement-current peaks when selecting the regulator; a weak 3.3 V rail can cause communication errors.

The I²C address is fixed at 0x62

SCD40 / SCD41 I²C address: 0x62

Unlike a BME280, the normal SCD4x interface does not provide a jumper for choosing a second address. Two SCD40/SCD41 devices on the same bus will respond simultaneously unless you put them on separate buses or isolate them with an I²C multiplexer.

For two identical-address devices, a TCA9548A can create separate logical I²C channels. An ESP32 with spare I²C-capable pins can alternatively run multiple configured buses. Always identify which physical board is being read before calibrating it.

Minimal ESPHome configuration

ESPHome’s current scd4x platform supports both devices; you do not select an SCD40 or SCD41 model in the basic YAML. The component detects the connected sensor. The following is a complete starting node for a standard ESP32 DevKit:

esphome:
  name: bedroom-co2
  friendly_name: Bedroom CO2

esp32:
  board: esp32dev
  framework:
    type: esp-idf

logger:

api:
  encryption:
    key: !secret api_encryption_key

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true

sensor:
  - platform: scd4x
    address: 0x62
    measurement_mode: periodic
    automatic_self_calibration: true
    update_interval: 60s

    co2:
      name: "Bedroom CO2"
      id: bedroom_co2

    temperature:
      name: "Bedroom SCD4x Temperature"

    humidity:
      name: "Bedroom SCD4x Humidity"

On startup, ESPHome’s I²C scan should find device 0x62. Home Assistant then receives separate CO₂, temperature and humidity entities through the Native API. Leave the measurement and update settings conservative until you have a stable baseline and a good mounting position.

Why 60-second update_interval is not a low-power mode

The two timing settings control different things. In measurement_mode: periodic, the SCD4x performs a measurement about every five seconds. ESPHome’s update_interval: 60s controls how often the component checks and publishes data; it does not tell the sensor to sleep for the other 55 seconds.

If power matters, set the actual sensor measurement mode rather than merely reducing the number of Home Assistant updates.

Low-power periodic mode: works on SCD40 and SCD41

sensor:
  - platform: scd4x
    measurement_mode: low_power_periodic
    update_interval: 60s
    co2:
      name: "Room CO2"
    temperature:
      name: "Room Temperature"
    humidity:
      name: "Room Humidity"

Here the sensor measures about every 30 seconds instead of every five seconds. ESPHome requires update_interval to be at least 30 seconds for this mode. Both SCD40 and SCD41 support it. For a mains-powered indoor monitor, normal periodic mode is usually a straightforward choice; low-power periodic trades measurement frequency for reduced sensor energy use.

SCD41 single-shot mode for on-demand measurements

SCD41—not SCD40—supports single_shot CO₂ measurement. Each measurement takes approximately five seconds. This makes sense for a device that deliberately measures less frequently; it does not automatically make the entire ESP32 node low power, because Wi-Fi and the development board’s regulator can still dominate total consumption.

sensor:
  - platform: scd4x
    measurement_mode: single_shot
    update_interval: 5min
    co2:
      name: "Battery Node CO2"
    temperature:
      name: "Battery Node Temperature"
    humidity:
      name: "Battery Node Humidity"

The manufacturer’s automatic self-calibration behaviour is optimised for a particular sampling history; ESPHome notes that SCD41 single-shot ASC is optimised around a five-minute measurement interval. Power-cycling the sensor between every reading may prevent the necessary calibration history from accumulating. Deep sleep must also be designed around the sensor’s conversion time, network startup and actual storage behaviour; do not assume this YAML alone makes a reliable sleeping battery node.

Never use single_shot_rht_only for CO₂ monitoring

ESPHome also exposes the SCD41 mode single_shot_rht_only. This completes temperature/humidity acquisition quickly, but the CO₂ value is reported as 0 ppm. It is only appropriate when CO₂ is not part of the requested measurement. Do not build a ventilation automation around the 0 ppm result.

Automatic self-calibration: when it works

Automatic self-calibration (ASC) is enabled by default in ESPHome. It uses measurement history and an assumption about the lowest background concentration that the sensor regularly encounters. Sensirion’s standard ASC configuration assumes periodic exposure to outdoor-fresh-air-like CO₂ levels—not that an occupied sealed room permanently sits at one fixed concentration.

Sensirion’s software documentation describes the default expectation as exposure to air around a 400 ppm baseline at least once per week of accumulated operation, with adequate continuous measurement history. The actual outdoor baseline varies by location and time. If a room is continually occupied, a laboratory has controlled elevated CO₂, or the device is repeatedly powered down, ASC can gradually learn an inappropriate baseline.

A practical rule: keep ASC enabled for an ordinary home room that is ventilated to outdoor air regularly; consider disabling it for controlled atmospheres or spaces that never reach a representative fresh-air baseline, then use a verified calibration procedure.

sensor:
  - platform: scd4x
    automatic_self_calibration: false
    co2:
      name: "Controlled Room CO2"

Disabling ASC is not a magic accuracy upgrade. It means you own the long-term calibration process. Make that decision based on how the sensor is actually used, not because a forum post recommends always turning ASC off.

Forced recalibration: do not guess the reference concentration

ESPHome offers a scd4x.perform_forced_calibration action. Use it only after the device has operated for more than three minutes in its normal measurement mode, in air with a known, homogeneous and stable reference CO₂ concentration. A reading from a trusted reference instrument is preferable to assuming every outdoor location is precisely 400 ppm.

# Add this action to an existing ESPHome automation
# after stabilising in known reference air.
on_...:
  then:
    - scd4x.perform_forced_calibration:
        id: room_scd4x
        value: 425  # Example only: replace with measured reference ppm

The fragment above illustrates the documented action syntax; it is not standalone deployable YAML because on_... must be replaced with a real ESPHome trigger and room_scd4x must be the configured component ID. Do not expose an unprotected “calibrate to 400” button to casual Home Assistant dashboard use. One incorrect recalibration can make the entire trend misleading.

Temperature offset: the ESPHome default surprises people

ESPHome documents temperature_offset with a default of 4°C. This is compensation for the sensor’s temperature rise relative to surrounding air, influenced by measurement mode, airflow and neighbouring electronics. It is not an instruction to subtract an arbitrary extra four degrees from the Home Assistant temperature entity.

Check your installed device against a reference thermometer after thermal equilibrium. If the reading is consistently too warm because the ESP32 and regulator heat the shared enclosure, first improve physical layout. Then adjust the SCD4x component’s temperature offset based on a stable measurement rather than stacking contradictory filters.

sensor:
  - platform: scd4x
    temperature_offset: 4.0  # ESPHome default; tune only after a real comparison
    co2:
      name: "Room CO2"
    temperature:
      name: "SCD4x Temperature"
    humidity:
      name: "SCD4x Humidity"

The SCD4x’s temperature and RH outputs are valuable for internal compensation and convenient trends, but Sensirion’s published temperature/humidity accuracy for this optical sensor family is more modest than a dedicated precision environmental sensor. If tight room-temperature or humidity accuracy is your primary requirement, use a separate, appropriately placed sensor such as an AHT20 or SHT4x.

Place the sensor in real room air—not in an ESP32 hot box

The sensing module needs airflow representative of the room. Put it in a ventilated enclosure away from direct sunlight, radiators, exhausts, kitchen vapour jets and the ESP32 regulator. Position it where people breathe normally but not directly in someone’s exhaled plume; a person talking into the housing can produce a large short-lived spike.

  • Keep air openings unobstructed; do not remove or damage the sensor’s protective white membrane.
  • Provide separation from the ESP32 Wi-Fi module and power regulator, especially if using its temperature/RH outputs.
  • Avoid placing it at floor level, beside an open window, or directly in a ventilation supply jet when you want a representative room value.
  • Allow the housing to equalise after moving the sensor between rooms before treating the readings as a baseline.
  • Do not permanently seal it in a “waterproof” enclosure: the sensor must exchange gas with surrounding air.

Ambient pressure and altitude compensation

CO₂ optical measurement depends on gas pressure. ESPHome supports a static ambient_pressure_compensation value in hPa/mbar, or altitude_compensation in metres. If ambient pressure compensation is supplied, ESPHome ignores the altitude setting. Use actual local station pressure, not a weather service’s sea-level-adjusted pressure, for pressure compensation.

If a BME280 is installed on the same device, ESPHome can pass its local pressure reading to the SCD4x. This is especially useful if the installation has a meaningful elevation difference or varying local pressure.

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true

sensor:
  - platform: bme280_i2c
    address: 0x76
    pressure:
      id: local_pressure
      name: "Local Pressure"
    temperature:
      name: "BME280 Temperature"
    humidity:
      name: "BME280 Humidity"

  - platform: scd4x
    ambient_pressure_compensation_source: local_pressure
    co2:
      name: "Pressure-Compensated CO2"

This is an optional precision improvement. For a first bench test, make the SCD4x work and verify its I²C address before adding an extra sensor or attempting advanced compensation.

Home Assistant: use trends and hysteresis rather than one instant spike

When the Native API is connected, Home Assistant can graph CO₂ alongside temperature and humidity. For an initial dashboard, show current CO₂ in ppm, a 24-hour history chart and the device’s uptime/Wi-Fi RSSI. Look for the shape of the trend when occupants enter, windows open or ventilation starts rather than treating every five-second fluctuation as a separate event.

A simple example alert can use a sustained high threshold. The following is an illustrative comfort/ventilation automation, not a legal exposure limit or occupational safety system:

alias: Bedroom - Sustained high CO2 reminder
triggers:
  - trigger: numeric_state
    entity_id: sensor.bedroom_co2
    above: 1200
    for: "00:10:00"
actions:
  - action: persistent_notification.create
    data:
      title: "Bedroom ventilation"
      message: "CO2 has stayed above 1200 ppm for 10 minutes. Check ventilation."
mode: single

Add a lower reset threshold or use Home Assistant’s Threshold helper with hysteresis before switching a fan repeatedly. A stable high CO₂ trend is a reason to investigate ventilation; CO₂ alone does not measure particulate matter, carbon monoxide, total VOCs or every component of indoor air quality.

SCD40/SCD41 compared with BME680 “eCO₂”

QuestionSCD40 / SCD41BME680 + IAQ algorithm
Does it directly measure CO₂?Yes, optical/photoacoustic methodNo: calculated equivalent/estimate
Can cleaning chemicals distort the main indication?Not in the same VOC-proxy wayVOC-sensitive output can respond strongly
Reports temperature/humidity?YesYes
Measures VOC/gas-resistance trend?Not its main outputYes
Useful for ventilation tracking?Measured CO₂ indicatorComplementary VOC/odour indicator

Combining the two sensors gives a fuller picture than substituting one for the other. A room can have low CO₂ but elevated VOCs after painting, or elevated CO₂ from occupants without a strong VOC event.

Common faults and first checks

SymptomFirst diagnostic check
No device at 0x62Power, SDA/SCL swap, breakout pull-ups and I²C pins
I²C errors while ESP32 transmitsRegulator capacity, supply peaks, cable length and ground
SCD41 shows 0 ppmEnsure you did not select single_shot_rht_only
CO₂ drifts down unrealistically in sealed roomRevisit ASC assumptions; verify with known reference air
CO₂ always near high valuesPlacement, actual ventilation and sensor calibration history
Temperature reads warm / RH reads lowSensor/ESP32 self-heating and enclosure airflow
Publishing every 60s still draws powerUse measurement_mode, not just update_interval
Two sensors on one bus failBoth use fixed 0x62; use separate bus or multiplexer
HA unavailable while serial sensor values continueCheck Wi-Fi / Native API before blaming SCD4x

Which sensor should you choose?

Choose SCD40 for a mains-powered room monitor where you mainly need CO₂ trends in its 400–2,000 ppm specified range and a low-complexity ESPHome setup. Choose SCD41 when you specifically want the longer 400–5,000 ppm specified range, its improved accuracy specification, or the single-shot mode for an appropriately engineered lower-power node.

Both use the same ESPHome scd4x component, the same nominal I²C address and the same ordinary four-wire wiring. The largest gains in a home build often come from sensible enclosure design, representative placement, a stable supply and correct calibration assumptions—not from publishing the same reading to Home Assistant more frequently.

Related esp32.co.uk/ guides

Official documentation

Share your love