20210212

Ihr müsst richtig lüften!!!
Das hört man immer wieder, aber was ist „richtig“?
Nach den Grenzwerten vom Umweltsamt, an denen ich mich für meinen Luftqualitätsmesser orientiert habe, dürfen unter anderem die Werte für den CO2-Gehalt und für die Konzentration flüchtiger organischer Verbindungen nicht überschritten werden. Temperatur und Luftfeuchte erfasst mein Sensor auch noch.
Um anzuzeigen, dass es Zeit zu Lüften ist, hat mein Sensor drei Neopixel spendiert bekommen.

Grenzwerte und Erklärung (aus dem Artikel der make 5/2020):

//Das Umweltbundesamt hat allgemeine Leitlinien
//zur "Gesundheitlichen Bewertung von
//Kohlendioxid in der Innenraumluft" verfasst,
//an denen wir uns orientieren, um die Schwellwerte
//für die Ampel festzulegen. Demnach ist
//eine CO2-Konzentration unter 1000 ppm hygienisch
//unbedenklich. Eine Konzentration zwischen
//1000 und 2000 ppm stuft die Leitlinie
//als bedenklich und alles darüber als inakzeptabel
//ein. Ab 1000 ppm sollte danach mit dem
//Lüften begonnen werden und über 2000 ppm
//muss gelüftet werden.
//Der CO2-Wert ist dabei nicht nur Indikator
//für die Verbreitung von Aerosolen, sondern
//auch für das allgemeine Wohlbefinden. Allerdings
//beeinflussen dies vor allem die chemisch
//reaktiven VOCs, welche mit dem menschlichen
//Organismus wechselwirken und somit beispielsweise
//zu Unwohlsein führen können. In
//einer weiteren Handreichung empfiehlt das
//Umweltbundesamt, die Summe der organischen
//Verunreinigungen immer unter 10 bis
//25 mg/m3 (entspricht ca. 5 bis 10 ppm, abhängig
//vom jeweiligen VOC) zu halten. Schließlich
//sollte die relative Luftfeuchtigkeit im Raum bei
//40 bis 60 Prozent liegen, da Aerosole bei geringerer
//Luftfeuchte länger in der Luft bleiben.
//Diese Empfehlungen gelten aber vor allem für
//den Einsatz von Lüftungsanlagen.


Was sind Neopixel?
Neopixel sind RGB-LEDs, also LEDs, die in einem Gehäuse drei einzelne LEDs in den Farben Rot, Grün und Blau beherbergen. Die LEDs haben vier Anschlüsse (5V, GND, DataIN, und DataOUT) Die Besonderheit an den WS2811b-LEDs ist, dass man die einfach hintereinander schalten kann und den jeweiligen DataOUT an den DataIN der nächsten LED klemmt. Dadurch wird jede LED einzeln ansteuerbar und man kann jeder LED eine andere Farbe geben, Lauflichter, Blinklichter und andere Effekte realisieren.

Als Hardware habe ich mich für einen ESP8266-basierten Wemos D1 mini Lite entschieden, weil der günstig ist und ich mehrere davon sowieso immer daheim habe. Ausserdem hat er auch schon WLAN, was eine spätere Integration ins SmartHome erlaubt (wenn ich es hinbekomme…). Als Sensoren kommen ein DHT11 für die Temperatur und Luftfeuchte und ein CCS811 für CO2 und die flüchtigen organischen Verbindungen (TVOC).

Nachdem ich die Schaltung auf einem Breadboard aufgebaut habe und das Programm soweit fertig hatte hab ich einen Schaltplan erstellt.

Den Test-Code findet ihr hier:

//Programmbeschreibung:
//Luftqualitätsmessung mit einem CS-811 und DHT11

// CO2 Gelb ab 1000, Rot ab 2000ppm
// TVOC Gelb ab 5000, Rot ab 10000ppb
// Temp grün zwischen 18 und 25°C
// Humidity Grün zwischen 40 und 60%rF

// select the required libraries

//#include <Adafruit_SSD1331.h>
//#include <PID_v1.h>
//#include <Bounce2.h>
//#include <Stepper.h>
//#include <WiFi.h>
//#include <WiFiClient.h>
//#include <WiFiServer.h>
//#include <WiFiUdp.h>
//#include <ESP8266WiFi.h>
//#include <LiquidCrystal.h>
//#include <max6675.h>
//#include <Servo.h>
//#include <OneWire.h>
//#include <DallasTemperature.h>
#include <Wire.h>    // I2C library
#include "ccs811.h"  // CCS811 library
#include "DHT.h" // DHT library
#include <Adafruit_NeoPixel.h>

#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif


//    _____            _                 _   _
//   |  __ \          | |               | | (_)
//   | |  | | ___  ___| | __ _ _ __ __ _| |_ _  ___  _ __  ___
//   | |  | |/ _ \/ __| |/ _` | '__/ _` | __| |/ _ \| '_ \/ __|
//   | |__| |  __/ (__| | (_| | | | (_| | |_| | (_) | | | \__ \
//   |_____/ \___|\___|_|\__,_|_|  \__,_|\__|_|\___/|_| |_|___/
//
//

//---------------------------------------------------------------------
// these variables are meant to be used for calling functions by time
// in the "loop", because not every function has to be called in every
// loop of the programm.

const int tasks = 9;
unsigned long millis_current;
unsigned long millis_old_task[tasks];
bool task[tasks];
int task_time[] = {5, 10, 25, 100, 250, 500, 1000, 5000, 10000, 60000};

//---------------------------------------------------------------------

// Wiring for ESP8266 NodeMCU boards: VDD to 3V3, GND to GND, SDA to D2, SCL to D1, nWAKE to D3 (or GND)
CCS811 ccs811(D3); // nWAKE on D3

#define DHTPIN 2     // Digital  connected to the DHT sensor
#define DHTTYPE DHT11   // DHT 11

// Which  on the Arduino is connected to the NeoPixels?
#define LEDPIN       12
#define NUMPIXELS 3 // how many NeoPixel
Adafruit_NeoPixel pixels(NUMPIXELS, LEDPIN, NEO_GRB + NEO_KHZ800);
#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels

//---------------------------------------------------------------------
// Settings for WiFi -
//
//byte my_WiFi_Mode = 0;  // WIFI_STA = 1 = Workstation  WIFI_AP = 2  = Accesspoint
//
////Station
//const char * ssid_sta     = "<Your SSID>";
//const char * password_sta = "<Your Password>";
//
////AccessPoint
//const char * ssid_ap      = "Project_name";
//const char * password_ap  = "";    // alternativ : "12345678"
//IPAddress AP_IP(10, 0, 1, 1);
//IPAddress AP_GW(10, 0, 1, 1);
//IPAddress AP_SNet(255, 255, 255, 0);
//
//WiFiServer server(80);
//WiFiClient client;
//
//#define MAX_PACKAGE_SIZE 2048
//char HTML_String[5000];
//char HTTP_Header[150];

//---------------------------------------------------------------------
// generic variables:

int call_counter = 0;
uint16_t eco2, etvoc, errstat, raw;
float humidity = 0;
float tempc = 0;

// Initialize DHT sensor.
DHT dht(DHTPIN, DHTTYPE);

//---------------------------------------------------------------------
//     _____      _
//    / ____|    | |
//   | (___   ___| |_ _   _ _ __
//    \___ \ / _ \ __| | | | '_ \
//    ____) |  __/ |_| |_| | |_) |
//   |_____/ \___|\__|\__,_| .__/
//                         | |
//                         |_|

void setup() {
  Serial.begin(115200);             // initialize serial communications at 115200bps
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
#endif

  // INITIALIZE NeoPixel strip object
  pixels.begin();

  // Enable I2C
  Wire.begin();

  // Enable DHT
  dht.begin();

  // Enable CCS811
  ccs811.set_i2cdelay(50); // Needed for ESP8266 because it doesn't handle I2C clock stretch correctly
  bool ok = ccs811.begin();
  if ( !ok ) Serial.println("setup: CCS811 begin FAILED");

  // Start measuring CCS
  ok = ccs811.start(CCS811_MODE_1SEC);
  if ( !ok ) Serial.println("setup: CCS811 start FAILED");

  // Start measuring DHT11
  if (isnan(humidity) || isnan(tempc)) {
    Serial.println(F("Failed to read from DHT sensor!"));
    return;

    // Welcome message
    Serial.println  ("------------------------------------");
    Serial.println  ("Luftmessung");
    Serial.println  ("");
    //  Serial.print("setup: ccs811 lib  version: "); Serial.println(CCS811_VERSION);
    // Print CCS811 versions
    //  Serial.print("setup: hardware    version: "); Serial.println(ccs811.hardware_version(), HEX);
    //  Serial.print("setup: bootloader  version: "); Serial.println(ccs811.bootloader_version(), HEX);
    //  Serial.print("setup: application version: "); Serial.println(ccs811.application_version(), HEX);
    //  Serial.println  ("");
    //  Serial.println  ("------------------------------------");

  }

  //---------------------------------------------------------------------
  // Start WiFi

  //  WiFi_Start_STA();
  //  if (my_WiFi_Mode == 0) WiFi_Start_AP();

  //---------------------------------------------------------------------
}


//    _
//   | |
//   | |     ___   ___  _ __
//   | |    / _ \ / _ \| '_ \
//   | |___| (_) | (_) | |_) |
//   |______\___/ \___/| .__/
//                     | |
//                     |_|

void loop() {
  // generate slopes for tasks
  millis_current = millis();
  for (int i = 0; i < tasks; i++) {
    if (millis_current - millis_old_task[i] > task_time[i]) {
      millis_old_task[i] = millis_current;
      task[i] = 1;
    }
  }
  pixels.clear(); // Set all pixel colors to 'off'
  if (task[0] == 1) {
    // 5ms calls
    //Serial.println("5ms");
  }
  if (task[1] == 1) {
    // 10ms calls
    //Serial.println("10ms");
  }
  if (task[2] == 1) {
    // 25 calls
    //Serial.println("25ms");
  }
  if (task[3] == 1) {
    // 100ms calls
    //Serial.println("100ms");
  }
  if (task[4] == 1) {
    // 250ms calls
    //Serial.println("250ms");
  }
  if (task[5] == 1) {
    // 500ms calls
    //Serial.println("500ms");
  }
  if (task[6] == 1) {
    // 1s calls
    //Serial.println("1s");
  }
  if (task[7] == 1) {
    // 5s calls
    //Serial.println("5s");
    // Read DHT11
    humidity = dht.readHumidity();
    // Read temperature as Celsius (the default)
    tempc = dht.readTemperature();
    delay(100);
    //Read CCS
    ccs811.read(&eco2, &etvoc, &errstat, &raw);
    delay(100);

    //Serial output
    if ( errstat == CCS811_ERRSTAT_OK ) {
      Serial.println("");
      Serial.print("CO2: ");  Serial.print(eco2);     Serial.println("ppm  ");
      Serial.print("TVOC: "); Serial.print(etvoc);    Serial.println("ppb  ");
      Serial.print("temp: "); Serial.print(tempc);    Serial.println("°C  ");
      Serial.print("humidity: "); Serial.print(humidity);    Serial.println("%rF  ");
      //Serial.print("raw6=");  Serial.print(raw/1024); Serial.print(" uA  ");
      //Serial.print("raw10="); Serial.print(raw%1024); Serial.print(" ADC  ");
      //Serial.print("R="); Serial.print((1650*1000L/1023)*(raw%1024)/(raw/1024)); Serial.print(" ohm");
    } else if ( errstat == CCS811_ERRSTAT_OK_NODATA ) {
      Serial.println("CCS811: waiting for (new) data");
    } else if ( errstat & CCS811_ERRSTAT_I2CFAIL ) {
      Serial.println("CCS811: I2C error");
    } else {
      Serial.print("CCS811: errstat="); Serial.print(errstat, HEX);
      //Serial.print("="); Serial.println(ccs811.errstat_str(errstat));
    }
    Serial.println("");
  }
  if (task[8] == 1) {
    // 10s calls
    //Serial.println("10s");

    // Temparatur-LED
    // Rot
    if ((tempc < 15) || (tempc > 28)) {
      pixels.setPixelColor(0, pixels.Color(50, 0, 0));
      Serial.println("Temperatur ++ oder --");
    }
    // Gelb
    if (((tempc >= 15) && (tempc <= 18)) || ((tempc > 24) && (tempc <= 28))) {
      pixels.setPixelColor(0, pixels.Color(50, 20, 0));
      Serial.println("Temperatur + oder -");
    }
    //Grün
    if (((tempc >= 18) && (tempc <= 24) )) {
      pixels.setPixelColor(0, pixels.Color(0, 50, 0));
      Serial.println("Temperatur ok");
    }
    // CO2-LED
    // Rot
    if ((eco2 > 2000)) {
      pixels.setPixelColor(1, pixels.Color(50, 0, 0));
      Serial.println("CO2 ++");
    }
    // Gelb
    if (((eco2 > 1000) && (eco2 <= 2000))) {
      pixels.setPixelColor(1, pixels.Color(50, 20, 0));
      Serial.println("CO2 +");
    }

    //Grün
    if (((eco2 <= 1000))) {
      pixels.setPixelColor(1, pixels.Color(0, 50, 0));
      Serial.println("CO2 ok");
    }

    // TVOC-LED
    // Rot
    if (etvoc > 10000) {
      pixels.setPixelColor(2, pixels.Color(50, 0, 0));
      Serial.println("TVOC ++");
    }
    // Gelb
    if (((etvoc > 5000) && (etvoc <= 10000))) {
      pixels.setPixelColor(2, pixels.Color(50, 20, 0));
      Serial.println("TVOC +");
    }
    //Grün
    if (((etvoc <= 5000))) {
      pixels.setPixelColor(2, pixels.Color(0, 50, 0));
      Serial.println("TVOC ok");
    }


    pixels.show();

  }

  if (task[9] == 1) {
    // 1m calls
    //Serial.println("1m");
  }

  // reset slopes for the tasks
  for (int i = 0; i < tasks; i++) {
    task[i] = 0;
  }

  //for debug
  //  Serial.println  ("DEBUG INFO");
  //  Serial.println  ("----------------------------------------------");
  //  Serial.println  ("");
}


Die Messwerte werden alle 5 Sekunden über die serielle Schnittstelle ausgegeben. Zusätzlich werden alle 10 Sekunden die Grenzwerte geprüft und die LEDs ensprechend farbig gemacht.

Den Aufbau hab ich dann noch von dem Breadboard auf eine Streifenrasterplatine übertragen. Nachdem ich eine Weile auf den Sensor geatmet habe, zeigt das die mittlere LED auch an.

Die Luftfeuchte habe ich nicht auf eine LED gelegt, da die am Aufstellort (klimatisierter Raum der max 22%rF haben darf) immer außerhalb des empfohlenen Bereichs (40-60%rF) läge. Für den Sensor für Zuhause kommt eine vierte LED dazu.
Nach einigen Optimierungen und Erweiterungen am Code funken die Luftqualitätssensoren ihr Werte fröhlich per mqtt an meine Smarthome-Zentrale:

Mittlerweile habe ich auch eine Version mit einem BME280 anstatt dem DHT11. Der BME kann zusätzlich noch Luftdruck messen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.