Luftkvalitetsmåler (CO₂ + VOC) for inneklima — Pust bedre hjemme

3 timer + Byggetid
Mellomnivå vanskelighet
Ferdigheter
3D-printing
Lodding

Visste du at CO₂-nivåer over 1 000 ppm kan gi hodepine, tretthet og konsentrasjonssvikt? Med denne monitoren ser du inneklimaet i sanntid — og kan integrere det direkte i Home Assistant.

Start Project

Med stadig flere som jobber hjemmefra har inneklima blitt et reelt helsespørsmål. Denne guiden viser deg hvordan du bygger en kompakt, WiFi-tilkoblet luftkvalitetsmåler basert på ESP32, en NDIR CO₂-sensor og en VOC-sensor — med live data på et OLED-display og varsling via buzzer.

Hjernen

Sensor

Skjerm

Tilbehør (Valgfritt)

🔧 Lab-verktøy tips: Bruk Fibels Spenningsdeler-kalkulator under Lab-verktøy for å verifisere at du får riktig utpenning til ESP32-en din. Skriv inn V_inn = 5V, R1 = 10kΩ og R2 = 100kΩ — du skal få ca. 3.0V ut, godt innenfor ESP32 ADC-grensen på 3.3V.

Koblingslogikk og Spenningsnivåer

ESP32 opererer med 3.3V logikk. ADC-inngangene tåler maks 3.3V. MQ-135 sin analoge utgang (AO) kan nå opp mot 4–5V. Du bruke en spenningsdeler på AO-pinnen. Bruk gjerne den digitale utgangen (DO) for enkel terskeldeteksjon uten spenningsdeler.
MQ-135 krever 5V forsyning på VCC, men TX/RX-signalene er 3.3V-kompatible — du kobler dem direkte til ESP32 uten nivåomformer.

MQ-135 PinNeste punktESP32 PinMerknad
VCCVIN (5V)Sensoren trenger 5V for varmeelement
GNDGNDGNDFelles jord
AO10kΩ → 15kΩ → GNDGPIO34 (ADC)Midtpunktet mellom 10kΩ og 15kΩ → GPIO34
DOGPIO35 (valgfritt)Digital terskelutgang (ingen spenningsdeler nødvendig)
OLED PinESP32 PinMerknad
VCC3.3V
GNDGND
SDAGPIO21Standard I2C data
SCLGPIO22Standard I2C klokke
Buzzer PinKoblingESP32 Pin
+ (positiv)Via 100 Ω motstandGPIO25
− (negativ)DirekteGND

Programmering og Konfigurasjon

Installer disse bibliotekene via Arduino Library Manager:

  • Adafruit SSD1306
  • Adafruit GFX Library
/*
 * ============================================================
 * Fibel.no | Luftkvalitetsmåler — MQ-135 + SSD1306 OLED
 * ============================================================
 */

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// --- OLED ---
#define OLED_BREDDE  128
#define OLED_HOYDE    64
#define OLED_ADRESSE 0x3C
Adafruit_SSD1306 display(OLED_BREDDE, OLED_HOYDE, &Wire, -1);

// --- MQ-135 ---
#define MQ135_PIN       34    // ADC (via spenningsdeler 10kΩ + 15kΩ!)
#define MQ135_OPPVARM   30000 // 30 sek før avlesninger er stabile

// --- Buzzer ---
#define BUZZER_PIN      25
#define ADVARSEL_TERSKEL 2500  // ADC-verdi (0–4095) — juster etter kalibrering
#define ALARM_TERSKEL    3500

unsigned long sisteAvlesning = 0;
const unsigned long INTERVALL = 3000;

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

  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADRESSE)) {
    Serial.println("FEIL: Finner ikke OLED!");
    while (true);
  }

  pinMode(BUZZER_PIN, OUTPUT);
  analogReadResolution(12); // 12-bit ADC: 0–4095

  // Vis oppstartsmelding
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(10, 20);
  display.println("Varmer opp sensor...");
  display.setCursor(10, 36);
  display.println("Vent 30 sekunder");
  display.display();

  // Vent på at MQ-135 når driftstemp
  delay(MQ135_OPPVARM);
}

void loop() {
  if (millis() - sisteAvlesning >= INTERVALL) {
    sisteAvlesning = millis();

    // Les råverdi (0–4095). Høyere verdi = dårligere luft.
    int raverdi = analogRead(MQ135_PIN);

    // Beregn enkel luftkvalitetsscore (100 = ren, 0 = dårlig)
    int score = constrain(map(raverdi, 0, 4095, 100, 0), 0, 100);

    Serial.printf("MQ-135 råverdi: %d | Luftscore: %d/100\n", raverdi, score);

    oppdaterDisplay(raverdi, score);

    // Varsling
    if (raverdi >= ALARM_TERSKEL) {
      pipAlarm(3, 200);
    } else if (raverdi >= ADVARSEL_TERSKEL) {
      pipAlarm(1, 100);
    }
  }
}

void oppdaterDisplay(int raverdi, int score) {
  display.clearDisplay();
  display.setTextColor(WHITE);

  // Score stort
  display.setTextSize(3);
  display.setCursor(0, 0);
  display.printf("%3d", score);

  display.setTextSize(1);
  display.setCursor(58, 2);
  display.print("/ 100");
  display.setCursor(58, 14);
  display.print("Luftscore");

  // Statuslinje
  display.setCursor(0, 36);
  if (raverdi < 1500)       display.print("Utmerket  :)");
  else if (raverdi < 2500)  display.print("Bra        OK");
  else if (raverdi < 3500)  display.print("Luft rommet !");
  else                      display.print("KRITISK    !!");

  // Råverdi nederst
  display.setCursor(0, 52);
  display.printf("ADC: %d / 4095", raverdi);

  display.display();
}

void pipAlarm(int antall, int varighet_ms) {
  for (int i = 0; i < antall; i++) {
    digitalWrite(BUZZER_PIN, HIGH);
    delay(varighet_ms);
    digitalWrite(BUZZER_PIN, LOW);
    delay(150);
  }
}

Foretrekker du ESPHome fremfor Arduino IDE? Dette er den anbefalte integrasjonsmetoden mot Home Assistant. Last inn YAML-filen via ESPHome Dashboard, og sensordataene dukker opp automatisk som entiteter.

# Fibel.no | Luftkvalitetsmåler — kun MQ-135

esphome:
  name: luftkvalitet-stue
  friendly_name: "Luftkvalitetsmåler Stue"

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "Luftkvalitet Fallback"
    password: "fibel1234"

api:
  encryption:
    key: !secret api_encryption_key

ota:
  - platform: esphome
    password: !secret ota_password

logger:

i2c:
  sda: GPIO21
  scl: GPIO22

sensor:
  # MQ-135 råverdi (spenning etter spenningsdeler)
  - platform: adc
    pin: GPIO34
    name: "Luftkvalitet råverdi (MQ-135)"
    id: mq135_volt
    attenuation: 11db        # Tillater 0–3.9V inngang
    unit_of_measurement: "V"
    accuracy_decimals: 3
    update_interval: 5s

  # Normalisert luftscore 0–100 (100 = ren luft)
  - platform: template
    name: "Luftkvalitetsscore"
    id: luftscore
    unit_of_measurement: "%"
    icon: "mdi:air-filter"
    lambda: |-
      // Spenning fra ADC (0–3.3V). Høyere spenning = dårligere luft.
      // Juster 0.1 og 3.3 etter kalibrering i ditt miljø.
      float score = (1.0 - (id(mq135_volt).state / 3.3)) * 100.0;
      return constrain(score, 0.0, 100.0);
    update_interval: 5s

text_sensor:
  - platform: template
    name: "Luftkvalitetsstatus"
    lambda: |-
      float v = id(mq135_volt).state;
      if (v < 0.8)       return {"Utmerket"};
      else if (v < 1.5)  return {"Bra"};
      else if (v < 2.5)  return {"Luft rommet"};
      else               return {"Kritisk – ventiler nå!"};
    update_interval: 5s

output:
  - platform: gpio
    pin: GPIO25
    id: buzzer_out

# Buzzer-automasjon ved dårlig luft
interval:
  - interval: 10s
    then:
      - if:
          condition:
            lambda: 'return id(mq135_volt).state >= 2.5;'
          then:
            - output.turn_on: buzzer_out
            - delay: 300ms
            - output.turn_off: buzzer_out

display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    lambda: |-
      it.printf(0, 0, id(font_stor), "%.0f", id(luftscore).state);
      it.print(54, 4, id(font_liten), "/ 100");
      it.print(54, 18, id(font_liten), "Luftscore");
      it.printf(0, 38, id(font_liten), "%.3f V", id(mq135_volt).state);
      it.print(0, 52, id(font_liten), id(luftkvalitetsstatus).state.c_str());

font:
  - file: "gfonts://Roboto"
    id: font_stor
    size: 30
  - file: "gfonts://Roboto"
    id: font_liten
    size: 12

📊 CO₂-referanseskala

Bruk denne tabellen som veileder for displaylogikken og Home Assistant-automajoner:

< 800✅ UtmerketIngen tiltak
800 – 1 000🟡 BraNormalt inneklima
1 000 – 1 500🟠 AdvarselLuft rommet — åpne et vindu
1 500 – 2 500🔴 HøytVentiler umiddelbart
> 2 500🚨 KritiskForlat rommet og luft grundig

Home Assistant — eksempelautomasjon

Legg dette inn under automations: i configuration.yaml for å få pushvarsel på telefonen:

alias: "Luftkvalitet – CO2 advarsel"
description: "Sender pushvarsel når CO2 overstiger 1000 ppm"
trigger:
  - platform: numeric_state
    entity_id: sensor.luftkvalitet_stue_co2_konsentrasjon
    above: 1000
    for:
      minutes: 3       # Unngå falske alarmer
condition: []
action:
  - service: notify.mobile_app_din_telefon
    data:
      title: "🌬️ Luftkvalitet – Luft rommet!"
      message: >
        CO₂ på stua er nå {{ states('sensor.luftkvalitet_stue_co2_konsentrasjon') }} ppm.
        Anbefalt grense er 1000 ppm. Åpne et vindu.
      data:
        push:
          sound: default
mode: single

Veien Videre

Når grunnprosjektet virker, er dette naturlige veier videre:
Forbedret nøyaktighet Bytt ut MQ-135 med en dedikert SGP30 (TVOC + eCO₂, I2C) eller SCD40 (NDIR CO₂ + temp + fuktighet i én pakke) for vesentlig bedre nøyaktighet og kalibrering. SCD40 er spesielt populær i ESP32-prosjekter.

Kabinett og design 3D-print et veggmontert kabinett. Husk luftespalter foran MQ-135 sensoren trenger god luftsirkulasjon for korrekte avlesninger.

Dataloggin og historikk ESPHome + Home Assistant lagrer automatisk historikk. Kombiner med InfluxDB + Grafana for avanserte grafer og trendanalyse over tid.

Utvidelse til flere rom Repeter oppsettet med billigere ESP32-C3 mini-moduler for ekstra rom, og samle alt data i Home Assistant-dashbordet ditt.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *