I Built a Real CO2 Monitor for $25 Because the Good Ones Cost $250 Home Automation

I Built a Real CO2 Monitor for $25 Because the Good Ones Cost $250

by Joule P. Kraft · June 29, 2026

As an Amazon Associate I earn from qualifying purchases. No affiliate relationship influences my recommendations.

The good consumer CO2 monitors cost between $200 and $300. The Aranet4 is $250. The Awair Element is $200 and wants a cloud account. Both are excellent, and for years I told people to just buy one. Then I watched the stuffy-bedroom problem I was trying to solve get fixed by a sensor that costs $25, sits on my dresser, and reports to Home Assistant with no app and no account. So I stopped recommending the expensive ones for anyone who already runs Home Assistant.

This is the build log. By the end you will have a genuinely accurate CO2 sensor, real ppm numbers you can trust, a Home Assistant entity, and an automation that cracks a window fan when the room gets stale. Total cost is about $25 in parts and about twenty minutes of work. No soldering required if you buy the right parts.

Why CO2 Is the Air-Quality Number That Actually Matters

Most air quality fuss is about particulates and VOCs, and those matter. But the number that quietly wrecks your sleep and your focus is carbon dioxide. A closed bedroom with two people breathing overnight routinely hits 1500 to 2500 ppm by morning. Above about 1000 ppm you start losing reasoning ability, and above 1400 you feel it as a headache and that groggy “why am I so tired” fog. You cannot smell it, you cannot see it, and your expensive purifier does nothing about it because the only fix is fresh air.

I knew my bedroom felt stuffy. I did not know it was hitting 2200 ppm by 6am until I measured it. That single data point changed how I sleep, and it cost almost nothing to collect.

The reason CO2 is worth monitoring over everything else is that it is a near-perfect proxy for ventilation. If CO2 is climbing, fresh air is not getting in, and whatever else is in your air, cooking particulates, off-gassing furniture, the dog, is also accumulating. Fix the ventilation problem CO2 reveals and you fix most of the rest. It is the single most useful number, and the one almost nobody measures.

The One Sensor Rule: Buy a Real CO2 Chip

This is where most cheap builds go wrong, so read this before you buy anything. There are two kinds of “CO2” sensors:

  • Real ones measure CO2 directly. The Sensirion SCD41 uses photoacoustic NDIR sensing and reports actual ppm from 400 to 5000. This is the chip in several $200 monitors.
  • Fake ones like the MQ-135 measure VOCs and a chip estimates an “equivalent CO2” from them. The number is invented. It will say 400 ppm when you exhale on it and 8000 when you wave alcohol nearby. Useless for sleep.

Spend the extra ten dollars on the SCD41 (the SCD40 is its slightly less accurate sibling and is fine too). Everything in this guide assumes a real Sensirion sensor. If the listing says MQ-135 or “eCO2,” skip it.

The Parts

If your SCD41 board has a Qwiic port and your ESP32 has one too, this is a plug-together job. Otherwise you solder four wires: 3.3V, GND, SDA, SCL. That is the whole wiring diagram.

A note on board choice: I use the ESP32-C3 SuperMini because it is tiny, costs four dollars, and has plenty of headroom for one I2C sensor. Any ESP32 works. If you already have an ESP8266 in a drawer it will run an SCD41 too, but I would buy the C3 for new builds because it has native USB and flashes without a finicky boot-button dance. Skip the absolute cheapest no-name boards with sketchy 3.3V regulators, since the SCD41 pulls real current during a measurement and a weak regulator browns out and freezes the readings. A reputable C3 board solves that for the price of a coffee.

Step 1: Flash ESPHome

You do not need a command line. Install the ESPHome Device Builder add-on in Home Assistant, create a new device, plug the ESP32-C3 into the computer over USB, and flash once using the web installer in Chrome or Edge. After that first flash, every change goes over Wi-Fi and you never plug it in again.

Step 2: The Config

This is the entire device. The SCD41 talks over I2C, so you declare the bus and the sensor, and ESPHome does the rest:

esphome:
  name: co2-bedroom

esp32:
  board: esp32-c3-devkitm-1

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

api:
  encryption:
    key: !secret api_key

logger:
ota:

i2c:
  sda: GPIO8
  scl: GPIO9
  scan: true

sensor:
  - platform: scd4x
    co2:
      name: "Bedroom CO2"
    temperature:
      name: "Bedroom Temp"
    humidity:
      name: "Bedroom Humidity"
    update_interval: 60s

Keep the real Wi-Fi password, encryption key, and SSID in your ESPHome secrets.yaml. The !secret references above are exactly how you should write the device YAML so you never paste a credential into a config you might share. That is best practice and it keeps the file clean.

That is it. One SCD41 gives you CO2, temperature, and humidity from a single chip. Three sensors, twelve lines. Save, install over Wi-Fi, and Home Assistant discovers three new entities within a minute.

Step 3: Calibration, the Part Everyone Skips

The SCD41 ships with automatic self-calibration (ASC) on. It assumes that at least once a week the sensor sees genuine fresh outdoor air around 420 ppm, and it uses that as its zero. In a normal house with windows that open sometimes, this works fine and you do nothing.

If your sensor lives somewhere that never airs out, it drifts. Two fixes:

  1. Forced recalibration: take it outside for a few minutes in fresh air, then trigger one forced calibration to 420 ppm. ESPHome exposes a button for this if you add it.
  2. Trust ASC and ventilate weekly: open a window once a week, which you should do anyway. The numbers stay honest.

Do not calibrate indoors against your phone app. Calibrate against the only reference that is always the same: outdoor air at 420 ppm.

One more placement note that matters for calibration: do not put the sensor where you breathe directly on it. Pointed at a pillow or right next to your face it reads your exhale, not the room, and ASC will fight that. Put it on a dresser, shelf, or wall a few feet from the bed at roughly seated head height. That gets you the number that matches the air you are actually breathing without spiking every time you roll over.

Step 4: The Automation That Pays for Everything

A number on a dashboard is mildly interesting. An automation that fixes the air is the point. Mine notifies me at 1000 ppm and turns on the bedroom fan at 1200:

trigger:
  - platform: numeric_state
    entity_id: sensor.bedroom_co2
    above: 1200
action:
  - service: fan.turn_on
    target:
      entity_id: fan.bedroom_window
  - service: notify.mobile_app
    data:
      message: "Bedroom CO2 high, ventilating"

Below 800 ppm is fresh. 800 to 1200 is stuffy. Above 1400 you feel it. Pick your threshold and let the house breathe on its own. I sleep noticeably better since the fan kicks on at 1200 instead of me waking up foggy at 2200.

Should You Add the Display?

The optional e-paper screen is a nice touch for a bedroom or office because it shows the current number without opening an app, and e-paper draws almost nothing so it can run on USB forever. ESPHome drives it natively. If you only care about automations and dashboards, skip it and save the $15. If you want a visible nudge to open a window, add it.

What About Just Buying an Aranet4?

If you do not run Home Assistant, buy the Aranet4 and stop reading. It is excellent, fully self-contained, and the battery lasts years. The DIY build only wins when you already have Home Assistant, because then the sensor becomes part of a system: it can trigger fans, log trends, and alert you, all locally. A $250 standalone gadget cannot do that without a hub. The $25 build does it on day one.

The other quiet win is data. Because every reading lands in Home Assistant, I can pull up a week of CO2 history and see exactly when the bedroom goes bad and how fast the fan clears it. That turned a vague “it feels stuffy” into a chart, and the chart is what convinced me to crack a window an hour before bed. You do not get that from a number on a battery gadget. Multiply the sensor by three at $25 each and you can map the whole house for the price of one Aranet, then automate every room independently.

The Bottom Line

Carbon dioxide is the air quality number that actually changes how you feel, and you have been guessing at it. A real Sensirion SCD41, a $4 ESP32, and twelve lines of ESPHome give you accurate ppm, three sensors in one, and an automation that ventilates the room before you ever feel the fog. It is local, it has no account, and nobody can switch it off. Buy the real sensor, skip the MQ-135 junk, and build the thing. It is the highest impact $25 I have spent on my house, and I sleep better for it.

Frequently Asked Questions

Is the SCD41 a real CO2 sensor or one of the fake estimated ones?+
It is a true NDIR-class photoacoustic sensor that measures CO2 directly, not an MQ-135 or eCO2 sensor that guesses ppm from VOC levels. The SCD41 reports actual carbon dioxide from 400 to 5000 ppm. If a cheap sensor claims CO2 but costs five dollars, it is estimating, and the number is fiction. The SCD41 is the real thing.
Do I need to write any code to add a CO2 sensor to Home Assistant?+
No. ESPHome has a native scd4x component, so the whole device is a dozen lines of YAML and Home Assistant auto-discovers the sensor over the API. No custom firmware, no programming, no cloud.
Why does my CO2 reading drift over time and how do I fix it?+
The SCD41 uses automatic self-calibration, which assumes it sees fresh 420 ppm air at least once a week. If a room never airs out it can drift. Run a forced recalibration outdoors, or disable ASC and calibrate manually once. I cover both in the post.
What CO2 level should trigger a ventilation alert?+
Outdoor air is about 420 ppm. Below 800 is fine, 800 to 1200 is stuffy, and above 1400 ppm you will feel the cognitive fog. I trigger a notification at 1000 ppm and turn on the bedroom fan automatically above 1200.
Will this work without internet or a cloud account?+
Yes, completely. ESPHome talks to Home Assistant over your LAN with an encrypted local API. No vendor account, no subscription, no remote server. That is the entire reason to build it instead of buying an Awair.