A Harvard study in 2016 looked at office workers' cognitive performance at different CO2 levels and found a clear, measurable drop above 1000 ppm. The finding has been replicated. Most offices, homes, and classrooms cross that threshold within an hour of being occupied with doors and windows closed, and almost nobody realises it. The solution is cheap: a sensor that tells you when to ventilate.
This build is the one we use. It reads CO2, temperature, and humidity every thirty seconds, displays the values on an OLED with color-coded status, and posts to a dashboard. Total cost around $40.
Why CO2 matters more than "air quality index"
Most cheap air quality devices report a single AQI number based on a MQ-135 or similar analog gas sensor. Those sensors drift, require long burn-in, and cannot distinguish CO2 from humidity from perfume from the smell of your neighbour cooking. Useless for ventilation decisions.
Actual CO2 sensors (NDIR: non-dispersive infrared) shine an IR beam through a cell of air and measure how much is absorbed at the wavelength CO2 absorbs. Expensive (five years ago) but now available in hobby-friendly modules for $25–40. They give a real parts-per-million number that corresponds to how much someone has been breathing in the room.
The rough scale:
- 400 ppm — fresh outdoor air
- 600–800 ppm — occupied room with good ventilation
- 1000 ppm — threshold where studies start showing cognitive effects
- 1500 ppm — noticeable drowsiness, stuffy feeling
- 2000+ ppm — headaches, strongly impaired concentration
- 5000 ppm — OSHA workplace limit over 8 hours
Bill of materials
- ESP32-S3 DevKit — $10
- Sensirion SCD41 NDIR CO2 sensor breakout — $35 (Adafruit, SparkFun, or the SCD40 which is slightly cheaper)
- 128×64 SSD1306 OLED, I2C — $4
- Breadboard, jumpers — $3
- 3D-printed or laser-cut enclosure with airflow slots — optional
If $35 for the SCD41 stings, the MH-Z19B is a $12 alternative with UART interface. Less accurate, slower response, but genuinely measures CO2.
Wiring
I2C addr 0x62] OLED[SSD1306 OLED
I2C addr 0x3C] ESP ---|SDA GPIO8| Bus1[SDA bus] ESP ---|SCL GPIO9| Bus2[SCL bus] Bus1 --- SCD Bus2 --- SCD Bus1 --- OLED Bus2 --- OLED ESP ---|3.3V| SCD ESP ---|3.3V| OLED
Both sensors share a single I2C bus. Different addresses mean no conflicts.
The SCD41 has internal pull-up resistors, so no external pull-ups are required if you use the Adafruit or SparkFun breakout. Most OLED breakouts also include pull-ups. Check with a multimeter if unsure; if the bus does not respond, add 4.7 kOhm pull-ups from SDA and SCL to 3.3V.
The firmware
Display design
A lesson from running the first prototype for a week: the actual CO2 number is not what you want on the display by default. You want to know whether to care. The three-zone colour/label scheme (FRESH / OK / OPEN WINDOW) is what people actually read. The number is secondary.
If you have a colour display (a $7 0.96" TFT is a direct swap), green under 1000 ppm, yellow 1000–1500, red above is even clearer. We have tried both; the text label is 90% of the value.
Going further
- Particulate matter (PM2.5/PM10): the PMS5003 sensor adds fine-dust monitoring for $20. Useful if you cook often, live near traffic, or want wildfire smoke alerts.
- VOCs (volatile organic compounds): the SGP40 or SGP41 measures total VOC index. Matters when you paint, install new furniture, or use solvent-based cleaners.
- HVAC integration: publish to MQTT and let Home Assistant turn on a fan or crack open a window actuator when CO2 crosses the warning threshold.
- Classroom / office monitoring: multiple units in different rooms, all posting to a shared dashboard. An A4-sheet printout with the current status next to each door is surprisingly useful.
- Calibration: SCD4x sensors self-calibrate against the assumption that the lowest CO2 reading over seven days was fresh outdoor air (400 ppm). If the sensor lives in a room that is never fully ventilated, disable automatic self-calibration and expose it to outdoor air monthly.
Frequently Asked Questions
Do I really need CO2 above $35 when a $10 sensor is available?
Yes, unfortunately. Cheap "CO2" sensors (MQ-135, MG-811) read something correlated with air quality but not CO2 specifically. They drift badly with temperature and humidity. If you care about the reading, spend the $35 on a real NDIR sensor.
Can I run this on battery?
Possible but not ideal. The SCD41 draws 15 mA during measurement and has a low-power mode that takes a reading every five minutes at under 1 mA average. With deep sleep between readings, a 2000 mAh battery lasts several months. The OLED is usually off in battery mode; wake it with a button.
Why does the sensor read 400 ppm outdoors but 600 ppm in an empty room?
Normal. Residual CO2 takes hours to clear through passive infiltration even in an empty room, especially in modern well-insulated buildings. Cracking a window for ten minutes brings a closed room from 1500 ppm back to 600 — you can watch it happen on the dashboard.
Should I worry about my own breath affecting the reading when I place the sensor?
A bit, for the first minute. A lungful of breath is about 40000 ppm; a close sensor briefly spikes. Place the sensor somewhere you do not routinely breathe on (a shelf, not a desk edge) and give it a minute to stabilise after handling.
What is the most useful alert threshold?
Below 800 ppm is fresh, so no action. 800–1200 ppm is an early nudge to consider ventilation. 1200–1500 is the point where the room genuinely benefits from a window cracked. Above 1500 ppm, act promptly. If a specific person's concentration matters (study, work, exam), aim to stay below 1000 ppm continuously.
Get the complete project package
The article above shows the core firmware and the principles behind it. The complete project package — assembled, tested, and ready to flash — is available by email request. We send it manually, and we read every request.
- Complete Arduino sketch (.ino) with full error handling
- List of required libraries with version numbers
- Printable wiring diagram (PDF)
- Bill of materials with current part numbers
- Build guide and troubleshooting tips
- Configuration template (WiFi, MQTT, etc.)
Share your thoughts
Worked with this in production and have a story to share, or disagree with a tradeoff? Email us at support@mybytenest.com — we read everything.