• Hallo Zusammen, Aufgrund der aktuellen Situation setzten wir die Möglichkeit aus, sich mit Gmail zu registrieren. Wir bitten um Verständnis Das RCMP Team

Praxisbericht Luftqualitätsmessung mit ESP8266, DHT11 und CCS811

froetz

Mitglied
Projekttitel: Luftqualitätsmessung

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:
//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 Code findet ihr hier:
Code:
//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 maximal 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.


ToDo:
- WiFi aktivieren und die Daten ins NodeRed (Smarthome auf meinem Raspberry) schicken
- Gehäuse bauen
 
Zuletzt bearbeitet:

Optikks

Mitglied
Sehr schönes Projekt ich hab derzeit ähnliches vor nur will das ganze irgendwie noch nicht so wie ich.
Ich hab da mal ein paar Fragen an dich. Wieso nutzt du nicht die Temperaturmessung vom ccs811?
Könntest du mir evtl bei meinem Projekt helfen?
Bekomme die Tage ein neues Display da das alte leider den Welpenzähnen nicht Stand gehalten hat.
Würde gern nur den CO2 wert per Ampel auf die NeoPixel legen. Smarthomeintegration brauch ich dabei nichtmal.

Lg
 

froetz

Mitglied
Klar kannst du mir Fragen dazu stellen, am Besten direkt hier im Thread - evtl. ist es für einen oder anderen auch interessant.

Der CCS881 misst nur CO2 und flüchtige Verbindungen. Man kann einen Temperatursensor daran anschließen, der dient aber soviel ich weiß nur der Temperaturkompensation um den Messwert etwas zu korrigieren. Ich weiß nicht ob man den Wert mit einem Arduino und den normalen Libraries ausgelesen bekommt. Ausserdem hab ich mit einem DHT11 angefangen und da stand die Temperaturmessung schon, so dass ich das nur um den CCS811 erweitern musste.

Meine Sensoren funken ihre Daten mittlerweile fröhlich ins Heimnetz:
1641790296647.png
 

Optikks

Mitglied
Das sieht sehr gut aus, erstmal bräuchte Ich das zweimal stationär. Einen evtl per Akku aber das kann ich dann selbst basteln mit der Versorgung.

Ich hätte gedacht du hast den anderen ccs benutzt gibt ja noch den mit integrierter Temperaturnmessung, meine da ist nen hdc1080 oder wie das Teil heißt mit drauf.

Ich würde es gern machen wir bei dir aber meine vorhandenen am2302 nutzen und dazu halt die vorhandenen NeoPixelsticks mit je 8 LEDs drauf. Evtl auch nen einzelner die hab ich auch noch da aber zum testen wäre die Anzahl der Pixel ja erstmal egal. Könnte ich da deinen Code übernehmen und müsste nur den Sensor zur Temperaturnmessung ändern?

Lg und danke schonmal
 

froetz

Mitglied
Meinen Code übernehmen? Da muss ich schon mal die Anwälte vorglühen!
Klar kannst den nehmen.

Je nachdem wie flott deine Aktualisierung der Messwerte sein soll, kannst du die "Aufgaben" in andere Task-Blöcke schreiben.
Ich weiss nicht ob das gleich durchschaut hast, im SETUP bei
Code:
bool task[tasks];
int task_time[] = {5, 10, 25, 100, 250, 500, 1000, 5000, 10000, 60000};
lege ich ein paar Zeitintervalle in ms fest,
und im LOOP später wird dann immer geprüft ob ein Intervall abgelaufen ist indem die aktuelle Laufzeit mit der des vorigen Durchgangs verglichen wird und entsprechend der Intervalle die "Slopes" für die Durchführung gesetzt.
Code:
  if (task[9] == 1) {
    // 1m calls
    //Serial.println("1m");
Könnte man sicher eleganter lösen - aber für mich hat sich das als praktkabel erwiesen. Dadurch rauscht man nicht immer durchs komplette Programm und kann zeitkritische Sachen in kürzeren Intervallen erledigen lassen und bspw. die Aktualisierung der Neopixel etwas weniger häufig. Meine gewählten Intervalle für Temperaturen sind sicher auch viel zu schnell, das kommt daher, dass ich beim Testen auch was sehen und nicht immer ein paar Minuten zwischen Wertänderungen warten will.


Wenn du auch einen Wemos nutzt, da gibt es gute Akkushields, an die einfach einen kleinen LiPo oder eine 18650er Zelle anschließen kannst. Den AM2023 kenn ich nicht, hab zur Temperaturmessung wie gesagt den BME280. Aber wenn die entsprechende Lib einbindest und die Read-Aufrufe anpasst sollte das gehen.
 
Zuletzt bearbeitet:

Optikks

Mitglied
Danke schonmal dafür, ich werde jetzt am Wochenende nach meiner Crawlertour mal schauen ob ich es zumindest fertig gesteckt bzw gecodet bekomme.
Mein Sensor ist eigentlich ein normaler dht22 nur halt direkt auf einem PCB dann nennt er sich am2302
 

Optikks

Mitglied
Ich nochmal,

Weitestgehend läuft es jetzt jedoch will er die NeoPixel nicht, hab da das meiste von dir übernommen nur die Anzahl und den Pin geändert.
Jetzt kommt aber immer die Fehlermeldung das Pixels nicht definiert sind.

Kann ich dir den Code mal schicken und du schaust drüber?


lg Maik
 
Zuletzt bearbeitet:

Optikks

Mitglied
Ja ich hab hab noch ne Weile dran Rum gemacht. Als Strip konnte ich es ansteuern. Muss so zwar jede Led einzeln ansteuern aber das ist ja nicht das große Problem.
Jetzt funktioniert alles soweit. Danke schonmal für deinen Code. Ich poste morgen oder die Tage je nach Zeit mal meinen evtl entdeckst du ja noch Verbesserungspotential denke das ist Recht wild zusammengeschriebenlg

Maik
 
Top Bottom