DIY CO₂-Monitor (SCD41 + ESPHome + Home Assistant) – unter 40€ & ohne Cloud

ich hab mir letzte Woche einen CO₂-Sensor ins Studio gelegt und war ehrlich schockiert: Nach ~1 Stunde mit geschlossener Tür lag ich bei >2000 ppm. Laut Umweltbundesamt ist das schon „inakzeptabel“ – und das Gemeine: ich hab’s nicht gemerkt.

Dann hab ich mir angeschaut, was oft als „CO₂-Sensor“ verkauft wird (Display, Akku, günstig). Kurzer Reality-Check: Whiteboard-Reiniger daneben → angeblich 3400 ppm → piept los. Spoiler: Das misst nicht CO₂, das reagiert auf irgendwas (VOC/Alkohol etc.).

Deshalb recherchiert, was ist der beste DIY Co2 Sensor →

Sogar ein Schweizer Hersteller :wink:

Minimal

  • ESP32C3 Super Mini (klein = geil, aber WLAN-Antenne klein → Empfang kann schlechter sein)

  • SCD41 (CO₂ + Temp + rLF, NDIR)

  • ein paar Dupont-Kabel / Lötkram

Nice-to-have

  • kleines I²C OLED (monochrom)

  • NeoPixel (WS2812) als Ampel-LED (grün/gelb/rot)

  • Gehäuse (3D-Druck, damit’s im Wohnzimmer überlebt :grinning_face_with_smiling_eyes:)

Wichtig: Lass beim SCD41 die Staubschutz-/Öffnung so wie sie ist (die weiße haube). Nicht „optimieren“. Das Ding will Luft, aber keinen Staub.


2) Verkabelung (super simpel, weil alles über I²C läuft)

Ich hab Sensor + Display auf denselben I²C-Bus gelegt. So brauchst du nur 4 Leitungen fürs „Grundsystem“.

Verbaut habe ich dieses mal einen kleinen ESP32. Bis jetzt auch ohne Wifi Probleme.

CO2 Sensor Amazon.de *

Laut Hersteller darauf achten, dass ein Lasermarkierung am Gehäuse ist und er eine Seriennummer hat.

ESP32 C3 Super Mini Amazon.de *

Display Amazon.de *

Neopix Amazon.de *

Schrauben Set M2 M2,5 M3 Schrauben Set, 845 Stück selbstschneidende Schrauben, Rundkopf Holzschrauben Set, Blechschrauben Rundkopfschrauben Linsenkopfschrauben, Kreuzschlitzschrauben für Kunststoff Holz Metal (845) : Amazon.de: Baumarkt *

( mit * sind affiliate links, ich erhalte eine Provision und Werbung für Amazon)

Ich hab das Gehäuse so gebaut, dass:

  • der Sensor unten eine Luftkammer hat (sauberer Luftstrom)

  • der ESP nicht zu viel Abwärme Richtung Sensor ballert (kleine Trennplatte)

  • es kompakt ist und nicht nach “Breadboard-Projekt” aussieht

Wenn du ein Gehäuse nachbaust: bitte nicht „zu dicht“. CO₂ will Luftaustausch.

Meine Frau ist immer noch nicht ultra glücklich mit dem Gehäuse :smiley: aber besser wie einfach in der Luft verkabelt.

Hier gehts zum Baubericht: https://youtu.be/s1seRBvoHVo

Welche CO₂-Sensoren nutzt ihr (DIY oder gekauft) und was war wirklich stabil?

Muss ich meinen Drucker mal wieder Kalibrieren oder Upgrades ;D ?

2 „Gefällt mir“

Ich habe davon 4 im Einsatz und bin sehr zufrieden

Zusätzlich habe ich noch einen ALPSTUGA (IKEA) im Einsatz und der gibt auch realistische (gemessene) Messwerte im Display und HA Entitäten

ALPSTUGA (LED Indikator am display geht bei 1000ppm an) :

Stark, dass du schon 4 davon verbaut hast :smiley:
Coole Darstellung in Grafana! Deine Maximalwerte sind ja auch echt hoch, welcher Raum ist das bei dir?
Welcher Raum ist am kritischsten?

Das oben ist ein Jugendzimmer, relativ großer Raum

Am schlimmsten ist natürlich unser (kleines) Schlafzimmer mit geschlossener Tür => da gibs auch mal Werte von über 4000 ppm :zany_face:

1 „Gefällt mir“

Cool! Ist es damit auch möglich, ein Zigbeesensor (am besten batteriebetrieben) zu bauen? Soweit ich weiß, ist das bei ESPHome doch auch irgendwie möglich :slight_smile:

Ein ESP32 C6 könnte auch Zigbee, aber meines Erachtens noch nicht mit ESPHome

Das ganze mit Zigbee wäre mega

ich hatte neulich in der Macherwerkstatt Live auf Wunsch von einem den ESP32 H2 angeschaut. Der kann kein Wifi nur Zigbee / Thread.
Und ich muss sagen, Thread war ultra überraschend einfach. Zigbee ist wohl deutlich schwerer.
Hatte das hier kurz dokumentiert.
https://youtube.com/shorts/i6x1OtrkMr4?feature=share

was erhofft ihr euch von Zigbee mehr wie Wifi?

Nur Akku oder auch besseren Empfang?

1 „Gefällt mir“

Ja, genau. Für so kleinere Sensoren gefällt mir Zigbee immer ein bisschen besser, da die mein WLAN-Netzwerk nicht „zumüllen“ und mit Akku/ Batterien betrieben werden können (idealerweise zumindest :smiley: ).

Für so einen kleinen Sensor Steckdose + WLAN zu belegen, ist in meinem Usecase etwas umständlich/ kompliziert

@Alkly Hallo Alex, Du sagst und zeigst in deinem Video (bei 14:45), dass der Sensor eine Seriennummer meldet. Muss ich dafür etwas in den Code eintragen? Hab den verlinkten Sensor gekauft und sehe keine Seriennummer im Log. :face_with_raised_eyebrow:

INFO ESPHome 2026.1.3
INFO Reading configuration /config/esphome/test-co2-esp32-s3.yaml...
INFO Starting log output from 192.168.178.102 using esphome API
INFO Successfully resolved test-co2-esp32-s3 @ 192.168.178.102 in 0.000s
INFO Successfully connected to test-co2-esp32-s3 @ 192.168.178.102 in 0.043s
INFO Successful handshake with test-co2-esp32-s3 @ 192.168.178.102 in 0.060s
[16:48:20.663][I][app:206]: ESPHome version 2026.1.3 compiled on 2026-03-08 20:05:19 +0100
[16:48:20.669][I][app:213]: ESP32 Chip: ESP32-S3 r0.2, 2 core(s)
[16:48:20.669][C][logger:316]: Logger:
[16:48:20.669][C][logger:316]:   Max Level: DEBUG
[16:48:20.669][C][logger:316]:   Initial Level: DEBUG
[16:48:20.679][C][logger:322]:   Log Baud Rate: 115200
[16:48:20.679][C][logger:322]:   Hardware UART: USB_SERIAL_JTAG
[16:48:20.679][C][logger:332]:   Task Log Buffer Size: 768 bytes
[16:48:20.690][C][i2c.idf:093]: I2C Bus:
[16:48:20.697][C][i2c.idf:094]:   SDA Pin: GPIO5
[16:48:20.697][C][i2c.idf:094]:   SCL Pin: GPIO6
[16:48:20.697][C][i2c.idf:094]:   Frequency: 100000 Hz
[16:48:20.697][C][i2c.idf:104]:   Recovery: bus successfully recovered
[16:48:20.697][C][i2c.idf:114]: Results from bus scan:
[16:48:20.704][C][i2c.idf:120]: Found device at address 0x62
[16:48:20.704][C][scd4x:112]: SCD4X:
[16:48:20.704][C][scd4x:113]:   Address: 0x62
[16:48:20.711][C][scd4x:130]:   Automatic self calibration: OFF
[16:48:20.711][C][scd4x:130]:   Measurement mode: Periodic (5s)
[16:48:20.711][C][scd4x:130]:   Temperature offset: 4.00 °C
[16:48:20.715][C][scd4x:145]:   Ambient pressure compensation disabled
[16:48:20.715][C][scd4x:145]:   Altitude compensation: 0m
[16:48:20.715][C][scd4x:452]:   Update Interval: 5.0s
[16:48:20.720][C][scd4x:016]:   CO2 'CO2'
[16:48:20.720][C][scd4x:016]:     State Class: 'measurement'
[16:48:20.720][C][scd4x:016]:     Unit of Measurement: 'ppm'
[16:48:20.720][C][scd4x:016]:     Accuracy Decimals: 0
[16:48:20.720][C][scd4x:026]:     Device Class: 'carbon_dioxide'
[16:48:20.720][C][scd4x:030]:     Icon: 'mdi:molecule-co2'
[16:48:20.821][C][scd4x:016]:   Temperature 'Temperatur'
[16:48:20.821][C][scd4x:016]:     State Class: 'measurement'
[16:48:20.821][C][scd4x:016]:     Unit of Measurement: '°C'
[16:48:20.821][C][scd4x:016]:     Accuracy Decimals: 2
[16:48:20.821][C][scd4x:026]:     Device Class: 'temperature'
[16:48:20.821][C][scd4x:030]:     Icon: 'mdi:thermometer'
[16:48:20.821][C][scd4x:016]:   Humidity 'Luftfeuchtigkeit'
[16:48:20.821][C][scd4x:016]:     State Class: 'measurement'
[16:48:20.821][C][scd4x:016]:     Unit of Measurement: '%'
[16:48:20.821][C][scd4x:016]:     Accuracy Decimals: 2
[16:48:20.821][C][scd4x:026]:     Device Class: 'humidity'
[16:48:20.821][C][scd4x:030]:     Icon: 'mdi:water-percent'
[16:48:22.296][C][captive_portal:128]: Captive Portal:
[16:48:22.296][C][wifi:1304]: WiFi:
[16:48:22.296][C][wifi:1304]:   Local MAC: D8:3B:DA:46:3E:E4
[16:48:22.296][C][wifi:1304]:   Connected: YES
[16:48:22.297][C][wifi:1037]:   IP Address: 192.168.178.102
[16:48:22.297][C][wifi:1048]:   SSID: [redacted]
[16:48:22.297][C][wifi:1048]:   BSSID: [redacted]
[16:48:22.297][C][wifi:1048]:   Hostname: 'test-co2-esp32-s3'
[16:48:22.297][C][wifi:1048]:   Signal strength: -62 dB ▂▄▆█
[16:48:22.297][C][wifi:1048]:   Channel: 6
[16:48:22.297][C][wifi:1048]:   Subnet: 255.255.255.0
[16:48:22.297][C][wifi:1048]:   Gateway: 192.168.178.1
[16:48:22.297][C][wifi:1048]:   DNS1: 192.168.178.1
[16:48:22.297][C][wifi:1048]:   DNS2: 0.0.0.0
[16:48:22.297][C][esphome.ota:075]: Over-The-Air updates:
[16:48:22.297][C][esphome.ota:075]:   Address: test-co2-esp32-s3.local:3232
[16:48:22.297][C][esphome.ota:075]:   Version: 2
[16:48:22.297][C][esphome.ota:082]:   Password configured
[16:48:22.297][C][safe_mode:021]: Safe Mode:
[16:48:22.297][C][safe_mode:021]:   Successful after: 60s
[16:48:22.297][C][safe_mode:021]:   Invoke after: 10 attempts
[16:48:22.297][C][safe_mode:021]:   Duration: 300s
[16:48:22.298][C][safe_mode:038]:   Bootloader rollback: support unknown
[16:48:22.298][C][web_server.ota:231]: Web Server OTA
[16:48:22.298][C][api:221]: Server:
[16:48:22.298][C][api:221]:   Address: test-co2-esp32-s3.local:6053
[16:48:22.298][C][api:221]:   Listen backlog: 4
[16:48:22.298][C][api:221]:   Max connections: 8
[16:48:22.298][C][api:228]:   Noise encryption: YES
[16:48:22.298][C][mdns:177]: mDNS:
[16:48:22.298][C][mdns:177]:   Hostname: test-co2-esp32-s3
[16:48:22.298][D][sensor:129]: 'CO2' >> 1244 ppm
[16:48:22.298][D][sensor:129]: 'Temperatur' >> 27.64 °C
[16:48:22.298][D][sensor:129]: 'Luftfeuchtigkeit' >> 72.23 %
[16:48:28.292][D][sensor:129]: 'CO2' >> 1239 ppm
[16:48:28.292][D][sensor:129]: 'Temperatur' >> 27.48 °C
[16:48:28.292][D][sensor:129]: 'Luftfeuchtigkeit' >> 72.97 %
[16:48:31.846][D][sensor:129]: 'CO2' >> 1236 ppm
[16:48:31.849][D][sensor:129]: 'Temperatur' >> 27.35 °C
[16:48:31.853][D][sensor:129]: 'Luftfeuchtigkeit' >> 73.65 %
[16:48:36.843][D][sensor:129]: 'CO2' >> 1239 ppm
[16:48:41.292][D][sensor:129]: 'Temperatur' >> 27.24 °C
[16:48:41.292][D][sensor:129]: 'Luftfeuchtigkeit' >> 74.32 %
[16:48:41.854][D][sensor:129]: 'CO2' >> 1238 ppm
[16:48:52.296][D][sensor:129]: 'Temperatur' >> 27.09 °C
[16:48:52.296][D][sensor:129]: 'Luftfeuchtigkeit' >> 74.95 %
[16:48:52.297][D][sensor:129]: 'CO2' >> 1237 ppm
[16:48:52.297][D][sensor:129]: 'Temperatur' >> 26.96 °C
[16:48:52.298][D][sensor:129]: 'Luftfeuchtigkeit' >> 75.52 %
[16:48:52.298][I][safe_mode:066]: Boot seems successful; resetting boot loop counter
[16:48:52.298][D][esp32.preferences:155]: Writing 1 items: 0 cached, 1 written, 0 failed
[16:48:52.298][D][sensor:129]: 'CO2' >> 1237 ppm
[16:48:52.300][D][sensor:129]: 'Temperatur' >> 26.87 °C
[16:48:52.300][D][sensor:129]: 'Luftfeuchtigkeit' >> 76.09 %
[16:48:56.850][D][sensor:129]: 'CO2' >> 1238 ppm
[16:48:56.855][D][sensor:129]: 'Temperatur' >> 26.79 °C
[16:48:56.859][D][sensor:129]: 'Luftfeuchtigkeit' >> 76.60 %

Grüße
Fabian

Warum ist der bei dir off?

Seriennummer habe ich bei 2 auch nicht, bei den anderen muss ich noch nach schauen

Der ist OFF weil ich aktuell am testen bin, und einen Button dafür habe.

# --- I2C Bus ---
i2c:
  sda: GPIO05
  scl: GPIO06
  frequency: 100kHz
  scan: true

# --- SCD41 CO2 Sensor ---
sensor:
  - platform: scd4x
    id: scd41_sensor
    co2:
      name: "CO2"
      id: "co2_wert"
    temperature:
      name: "Temperatur"
      id: temperatur
    humidity:
      name: "Luftfeuchtigkeit"
      id: luftfeuchtigkeit
    temperature_offset: 4
    automatic_self_calibration: false
    update_interval: 5s

button:
  - platform: template
    name: "CO2 manual calibration (only in fresh air for at least 20min)"
    #entity_category: "config"
    on_press:
      then:
        - scd4x.perform_forced_calibration:
            id: scd41_sensor
            value: 420

Frequenz habe ich bei mir auch nicht
Aber es funktioniert ja bei dir und mir

Das mit dem temperature_offset: ging bei mir auch nicht wirklich, habe den Offset also selber gemacht (filters)

Ich habe an 1-2 Stellen besseres Zigbee Netz wie WLAN :smiley: @Alkly
Aber wie @MarzyHA sagt will man nicht sein WLAN bzw. Netzwerk voll haben.

Und für User die nur einen Consumer-Router (FritzBox, Speedport, etc…) können halt nicht das hingehen uns sagen das der IP Bereich 10.10.10.x für die Hauptgeräte sind und 10.10.20.x für IOT.

Daher wäre Zigbee oder Thread echt geschickter.

ggf. könnte man mit Thread was bauen was den Sensor bespielt und dann einen QR-Code rauswirft. Oder gibt’s sowas schon?

Warum soll das z.B. mit einer FB5690Pro nicht gehen
In meinem Netzwerk sind weit mehr als 180 WLAN Clienten
Hauptnetzwerk ist die alt bekannte xxx.178.xxx
Die meisten IOT sind auf der xxx.179.xxx
Dann habe ich noch xxx.177.xxx und xxx.176.xxx

Und Thread ist ja nun gar kein Problem, habe 2x ESP32C6 relativ frisch eingebunden über Thread und da würde natürlich auch der SCD41 funktionieren :hugs:
Siehe hier:

Du könntest natürlich auch einen ESP32 mit LAN einbinden

1 „Gefällt mir“

wie hast du das dort eingerichtet? sind die IOT Geräte (179 netz) komplett getrennt von dem Hauptnetzwerk (178)?

hast du es per Subnetzmaske getrennt? Weil dann kannst du zwar verschiedene IP-Adressebereiche haben (177, 178, etc) aber die können doch dann eh aufeinander zu greifen.

Auch in einem Video von alky glaube ich wars das man ein Komplett getrenntes Netz hat und nur dem IOT netz erlaubt auf HA zuzugreifen aber nichts aufs Netz.

Das klappt mit einer FB nicht wie z.B.: bei Unifi

Nein, finde ich auch nicht notwendig
Habe keine Geräte die über eine Cloud komunizieren müssen (aber das ist ein anderes Thema)

ok da hast du keine Trennung bzw. Regeln.

Aber wie hast du die Trennung gemacht der Netze?

Heimnetz => Netzwerk => Erweiterte Netzwerkeinstellung => IPV4
Dort Subnetzmaske umstellen auf
255.255.252.0

danke, war mir nicht ganz sicher :slight_smile:

1 „Gefällt mir“