Luftkvalitetsmåler (CO₂ + VOC) for inneklima — Pust bedre hjemme
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.
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 må 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 Pin | Neste punkt | ESP32 Pin | Merknad |
|---|---|---|---|
| VCC | VIN (5V) | — | Sensoren trenger 5V for varmeelement |
| GND | GND | GND | Felles jord |
| AO | 10kΩ → 15kΩ → GND | GPIO34 (ADC) | Midtpunktet mellom 10kΩ og 15kΩ → GPIO34 |
| DO | — | GPIO35 (valgfritt) | Digital terskelutgang (ingen spenningsdeler nødvendig) |
| OLED Pin | ESP32 Pin | Merknad |
|---|---|---|
| VCC | 3.3V | |
| GND | GND | |
| SDA | GPIO21 | Standard I2C data |
| SCL | GPIO22 | Standard I2C klokke |
| Buzzer Pin | Kobling | ESP32 Pin |
|---|---|---|
| + (positiv) | Via 100 Ω motstand | GPIO25 |
| − (negativ) | Direkte | GND |
Programmering og Konfigurasjon
Installer disse bibliotekene via Arduino Library Manager:
Adafruit SSD1306Adafruit 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 | ✅ Utmerket | Ingen tiltak |
| 800 – 1 000 | 🟡 Bra | Normalt inneklima |
| 1 000 – 1 500 | 🟠 Advarsel | Luft rommet — åpne et vindu |
| 1 500 – 2 500 | 🔴 Høyt | Ventiler umiddelbart |
| > 2 500 | 🚨 Kritisk | Forlat 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: singleVeien 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