Benutzer-Werkzeuge

Webseiten-Werkzeuge


iot:luftqualitaetsmonitor:programmversion_0.1

Innenraumluftqualitätsmonitor: Programmversion 0.1

Diese erste Programmversion fokussiert auf die grundlegendsten Funktionen:

  • Das Display wird angesteuert.
  • Die Sensoren SGP30 und BME280 werden ausgelesen.
  • Von allen Sensoren werden die Daten über einen Zeitraum von sechs Stunden in Arrays angelegt.
  • Die Daten einiger Sensoren werden im Zeitverlauf auf dem Display dargestellt.

Weil die Fonts der Adafruit GFX Bibliothek viel zu groß sind, habe ich die Bibliothek U8g2_for_Adafruit_GFX eingebunden. Damit lassen sich zahllose, auch relativ kleine Fonts verwenden, die dennoch dem Auge schmeicheln.

// Innenraumluftqualitätsmonitor
// Ab dieser Version wird anstatt eines Teensy 3.6 ein Lolin D32 verwendet

// include library, include base class, make path known
#include <Wire.h>
#include <SPI.h>  
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "Adafruit_SGP30.h"
#include <GxEPD.h>
#include <GxGDEW075T8/GxGDEW075T8.cpp>      // 7.5" b/w
#include <GxIO/GxIO_SPI/GxIO_SPI.cpp>
#include <GxIO/GxIO.cpp>
#include <U8g2_for_Adafruit_GFX.h>
#include "RunningAverage.h"

U8G2_FOR_ADAFRUIT_GFX u8g2_for_adafruit_gfx;


/*
 * Anschluss des Waveshare 7,5 Zoll e-Paper Display am Lolin D32
 * DIN (blau):     Pin 23
 * CLK (gelb):     Pin 18
 * CS (orange):    Pin 5
 * DC (grün):      Pin 17
 * RST (weiß):     Pin 16
 * BUSY (violett): Pin 4
 */

GxIO_Class io(SPI, SS, 17, 16);
GxEPD_Class display(io, 16, 4);



Adafruit_SGP30 sgp;

Adafruit_BME280 bme; // I2C

RunningAverage averageCO2(60);        // RunningAverage-Objekt für Glättung der CO2-Messwerte
RunningAverage averageTVOC(60);       // RunningAverage-Objekt für Glättung der TVOC-Messwerte
RunningAverage averageTEMP(1);        // RunningAverage-Objekt für Glättung der Temperatur-Messwerte (Muss später auf eine sinnvolle Länge geändert werden.)
RunningAverage averageHUMI(1);        // RunningAverage-Objekt für Glättung der Luftfeuchtigkeits-Messwerte (Muss später auf eine sinnvolle Länge geändert werden.)
RunningAverage averagePRES(1);        // RunningAverage-Objekt für Glättung der Luftdruck-Messwerte (Muss später auf eine sinnvolle Länge geändert werden.)

// Sechs-Stunden-Arrays
unsigned int arrayCO2_6H[291];         // Messwerte für ECO2
unsigned int arrayTVOC_6H[291];        // Messwerte für TVOC
float arrayTEMP_6H[290];               // Messerte für Temperatur
float arrayHUMI_6H[290];               // Messwerte für Luftfeuchtigkeit
float arrayPRES_6H[290];               // Messwerte für Luftdruck
int validMeasures6H = 0;               // Zählt den "Füllstand" der Sechs-Stunden Arrays.

int counter = 0;

// Definiert die Tracking-Variablen für die IF-Abfragen
unsigned long previousMillisBME280 = 0;          // Adafruit BME280
unsigned long previousMillisSGP30 = 0;          // Adafruit SGP30
unsigned long previousMillisArrays = 0;          // Arrays
unsigned long previousMillisDisplay = 0;          // Display

// Definiert die Intervalle für die IF-Abfragen in Millisekunden
const unsigned long intervalBME280 = 78000;        // konstanter Delay für Adafruit BME280
const unsigned long intervalSGP30 = 1000;          // konstanter Delay für Adafruit SGP30 (Der Sensor muss einmal pro Sekunde abgefragt werden, damit die automatische Kalibrierung funktioniert.)
const unsigned long intervalArrays = 78000;        // konstanter Delay für Arrays
const unsigned long intervalDisplay = 78000;        // konstanter Delay für Display


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

  Wire.begin(25, 26);           // SDA, SCL
  
  // e-Paper Display Waveshare 7,5 Zoll
  Serial.println();
  Serial.println("Setup Display");
  display.init(115200); // enable diagnostic output on Serial
  display.setRotation(0);
  Serial.println("Setup done");


  // Air Quality Sensor SGP30
  if (! sgp.begin()){
    Serial.println("Sensor not found :(");
    while (1);
  }
  Serial.print("SGP30 serial number ");
  Serial.print(sgp.serialnumber[0], HEX);
  Serial.print(sgp.serialnumber[1], HEX);
  Serial.println(sgp.serialnumber[2], HEX);

  // If you have a baseline measurement from before you can assign it to start, to 'self-calibrate'
  sgp.setIAQBaseline(0x899D, 0x8D36);

 
  // Temperature, Humidity, Pressure Sensor BME280
  bool status;
  status = bme.begin();  
  if (!status) {
      Serial.println("Could not find a valid BME280 sensor, check wiring!");
      while (1);
  }
  bme.setSampling(Adafruit_BME280::MODE_FORCED,
                    Adafruit_BME280::SAMPLING_X1, // temperature
                    Adafruit_BME280::SAMPLING_X1, // pressure
                    Adafruit_BME280::SAMPLING_X1, // humidity
                    Adafruit_BME280::FILTER_OFF   );
  
  // Leeren der Arrays der RunningAverage-Objekte
  averageCO2.clear();
  averageTVOC.clear();
  averageTEMP.clear();
  averageHUMI.clear();
  averagePRES.clear();
  
  u8g2_for_adafruit_gfx.begin(display);                 // connect u8g2 procedures to Adafruit GFX


  // Startbildschirm
  display.fillScreen(GxEPD_WHITE);
  display.setTextColor(GxEPD_BLACK);                    // Systemschrift
  display.setFontMode(1);                               // u8g2 Fonts
  display.setBackgroundColor(GxEPD_WHITE);              // u8g2 Fonts
  display.setForegroundColor(GxEPD_BLACK);              // u8g2 Fonts
  
  displayStatus();
   
  displayGrid (0, 99, 400, 480, "Kohlendioxid", arrayCO2_6H[0], "ppm"); 
  displayGrid (0, 254, 0, 80, "Organische Verbindungen", arrayTVOC_6H[0], "ppb");
  displayGrid (320, 99, 00, 40, "Feinstaub 10", arrayCO2_6H[0], "ug/m3"); 
  displayGrid (320, 254, 0, 40, "Feinstaub 2.5", arrayTVOC_6H[0], "ug/m3");
  
  display.update();
  
  
}

void loop() {
// Aktuelle Zeit abfragen
  unsigned long currentMillis = millis();


// Adafruit SGP30 abfragen
  if ((unsigned long)(currentMillis - previousMillisSGP30) >= intervalSGP30) {
    
    // Werte für Temperatur und Luftfeuchtigkeit werden an den Sensor übergeben
    float temperature, humidity;
    if (millis() < 78000) {                                       // Gültige Messwerte liegen erst nach 78 Sekunden vor
      temperature = 25.0;                      // [°C]
      humidity = 50.0;                         // [%RH]
    }
    else {
      temperature = averageTEMP.getAverage(); // [°C]
      humidity = averageHUMI.getAverage();    // [%RH]
    }
    sgp.setHumidity(getAbsoluteHumidity(temperature, humidity));

    // Daten auslesen und an die Glättung übergeben
    if (! sgp.IAQmeasure()) {
      Serial.println("Measurement failed");
      return;
    }
    
    //Serial.print("TVOC "); Serial.print(sgp.TVOC); Serial.print(" ppb\t");
    //Serial.print("eCO2 "); Serial.print(sgp.eCO2); Serial.println(" ppm");   

    averageCO2.addValue(sgp.eCO2);        // Fügt dem Objekt einen neuen Wert hinzu
    averageTVOC.addValue(sgp.TVOC);       // Fügt dem Objekt einen neuen Wert hinzu

    Serial.print(".");
    counter = counter + 1;
    if (counter >= 71) {
      Serial.println();
      counter = 0;      
    }
    //Speichere die aktuelle Zeit in die zughörige Variable
    previousMillisSGP30 = currentMillis;
  }


  // Adafruit BME280 abfragen
  if ((unsigned long)(currentMillis - previousMillisBME280) >= intervalBME280) {
    
    bme.takeForcedMeasurement();
    averageTEMP.addValue(bme.readTemperature());
    averageHUMI.addValue(bme.readHumidity());
    averagePRES.addValue(bme.readPressure() / 100.0F);

    //Speichere die aktuelle Zeit in die zughörige Variable
    previousMillisBME280 = currentMillis;
  }


// Arrays mit Daten füllen 
  if ((unsigned long)(currentMillis - previousMillisArrays) >= intervalArrays) {

    // Die Sechs-Stunden-Arrays werden mit Daten befüllt. Die aktuelle Messung steht immer ganz links im Array. Ältere Messungen werden automatisch verschoben.
    addUnsignedIntegerToArray (arrayCO2_6H, averageCO2.getAverage());
    addUnsignedIntegerToArray (arrayTVOC_6H, averageTVOC.getAverage());
    addFloatToArray (arrayTEMP_6H, averageTEMP.getAverage());
    addFloatToArray (arrayHUMI_6H, averageHUMI.getAverage());
    addFloatToArray (arrayPRES_6H, averagePRES.getAverage());

    // Der "Füllstand" der Sechs-Stunden-Arrays wird gezählt
    validMeasures6H = validMeasures6H + 1;
    if (validMeasures6H >= 290) {
      validMeasures6H = 290;
    }
    Serial.print("validMeasures6H: "); Serial.println(validMeasures6H);
    
    //Speichere die aktuelle Zeit in die zughörige Variable
    previousMillisArrays = currentMillis;
  }
  
  
  // Display
  if ((unsigned long)(currentMillis - previousMillisDisplay) >= intervalDisplay) {

    display.fillScreen(GxEPD_WHITE);                      // Der Displayinhalt wird gelösch
    display.setTextColor(GxEPD_BLACK);

    // Statusleiste
    displayStatus ();
  
    // Temperatur, Luftfeuchtigkeit und Luftdruck
    displayValue (0, 14, "Temperatur", arrayTEMP_6H[0], "°C");
    displayValue (150, 14, "Luftfeuchtigkeit", arrayHUMI_6H[0], "%rH");
    displayValue (300, 14, "Luftdruck", arrayPRES_6H[0], "hPa");
    
    
    // Anzeige der CO2-Werte
    unsigned int minCO2 = minValue(arrayCO2_6H, validMeasures6H);  // Berechnet den Minimalwert im Array für alle "gültigen" Messwerte)
    Serial.print("minCO2: ");
    Serial.println(minCO2);
    
    unsigned int maxCO2 = maxValue(arrayCO2_6H, validMeasures6H);  // Berechnet den Minimalwert im Array für alle "gültigen" Messwerte)
    if (maxCO2 <= 480) {                                           // maxCO2 muss immer größer als minCO2 sein, sonst stürzt der ESP32 ab
      maxCO2 = 480;
    }
    Serial.print("maxCO2: ");
    Serial.println(maxCO2);
    
    // Zeigt das Sechs-Stunden-Array für eCO2 an
    if (validMeasures6H == 1) {
      display.drawPixel((314), (map(arrayCO2_6H[0], 400, maxCO2, 218, 98)), GxEPD_BLACK);
    }
    else {                                     // Es müssen zwei Messwerte vorliegen, um den Graph zeihenen zu können
      for (int i=0; i < validMeasures6H - 1; i++) {
          display.drawLine((314 - i), (map(arrayCO2_6H[i], 400, maxCO2, 218, 98)), (314 - i - 1), (map(arrayCO2_6H[i + 1], 400,  maxCO2, 218, 98)), GxEPD_BLACK);
      }
    }
    
    // Zeichnet das Grid
    displayGrid (0, 99, 400, maxCO2, "Kohlendioxid", arrayCO2_6H[0], "ppm");       // (offsetX, offsetY, minCO2, maxCO2, Titel, aktueller Messwert, Einheit)

    
    // Anzeige der TVOC-Werte
    unsigned int minTVOC = minValue(arrayTVOC_6H, validMeasures6H);             // Berechnet den Minimalwert im Array für die ersten 290 Elemente)
    Serial.print("minTVOC: ");
    Serial.println(minTVOC);
    
    unsigned int maxTVOC = maxValue(arrayTVOC_6H, validMeasures6H);             // Berechnet den Minimalwert im Array für die ersten 290 Elemente)
    if (maxTVOC <= 80) {                                                        // maxCO2 muss immer größer als minCO2 sein, sonst stürzt der ESP32 ab
      maxTVOC = 80;
    }
    Serial.print("maxTVOC: ");
    Serial.println(maxTVOC);

    // Zeigt das Sechs-Stunden-Array für TVOC an
    if (validMeasures6H == 1) {                                                    // Der erste Messwert wird nur als Punkt dargestellt
      display.drawPixel((314), (map(arrayTVOC_6H[0], 0, maxTVOC, 373, 253)), GxEPD_BLACK); 
    }
    else {                                                                      // Ab dem zweiten Messwert werden alle Werte mit Linien verbunden
      for (int i=0; i < validMeasures6H - 1; i++) {
        display.drawLine((314 - i), (map(arrayTVOC_6H[i], 0, maxTVOC, 373, 253)), (314 - i - 1), (map(arrayTVOC_6H[i + 1], 0,  maxTVOC, 373, 253)), GxEPD_BLACK);
      }
    }  
    
    // Zeichnet das Grid
    displayGrid (0, 254, 0, maxTVOC, "Organische Verbindungen", arrayTVOC_6H[0], "ppb");                // (offsetX, offsetY, minTVOC, maxTVOC, Titel, aktueller Messwert, Einheit)


    // Anzeige der Feinstaubwerte 10 ug/m3
    displayGrid (320, 99, 00, 40, "Feinstaub 10", arrayCO2_6H[0], "ug/m3"); 
    
    
    // Anzeige der Feinstaubwerte 2,5 ug/m3
    displayGrid (320, 254, 0, 40, "Feinstaub 2.5", arrayTVOC_6H[0], "ug/m3");

   
    display.update();
     
    //Speichere die aktuelle Zeit in die zughörige Variable
    previousMillisDisplay = currentMillis;
  } 
  
}

//######################################### Functions ##############################################

// Richtet Ziffern rechtsbündig aus, indem Leerzeichen vorangestellt werden
void displayprintAlignRight(int digits) {
  if(digits < 10) {
    display.print("   ");
  }
  else if (digits < 100) {
    display.print("  ");
  }
  else if (digits < 1000) {
    display.print(" ");
  }
  display.print(digits);
}

// Verschiebt alle Elemente in dem Array um eine Position nach rechts und fügt links einen neuen Unsigned-Integer-Wert hinzu
void addUnsignedIntegerToArray (unsigned int arrayName[], unsigned int value) {
  for (int i = 289; i >= 1; i--) {                          // Länge des Arrays - 1!
    arrayName[i] = arrayName[i - 1]; 
  }
  arrayName[0] = value;

    Serial.print("Array Test: ");
    for (int i=0; i < 290; i++) {
      Serial.print(arrayName[i]);
      Serial.print("; ");
    }
    Serial.println();
}

// Verschiebt alle Elemente in dem Array um eine Position nach rechts und fügt links einen neuen Floating-Point-Wert hinzu
void addFloatToArray (float arrayName[], float value) {
  for (int i = 289; i >= 1; i--) {                          // Länge des Arrays - 1!
    arrayName[i] = arrayName[i - 1]; 
  }
  arrayName[0] = value;

    Serial.print("Array Test: ");
    for (int i=0; i < 290; i++) {
      Serial.print(arrayName[i]);
      Serial.print("; ");
    }
    Serial.println();
}

// Bestimmt den Maximalwert in einem Array: "scope" gibt den Bereich an (beginnend bei 0)
unsigned int maxValue (unsigned int arrayName[], int scope) {
  unsigned int maxValue;
  unsigned int kmax = 0;
  unsigned int max = 0;
  for (int k = 0; k <= scope; k++) {
    if (arrayName[k] > max) {
      max = arrayName[k];
      kmax = k;
    }
  }
  return maxValue = arrayName[kmax];
}

// Bestimmt den Minimalwert in einem Array: "scope" gibt den Bereich an (beginnend bei 0)
unsigned int minValue (unsigned int arrayName[], int scope) {
  unsigned int minValue;
  unsigned int kmin = 0;
  unsigned int min = 60000;
  for (int k = 0; k < scope; k++) {
    if (arrayName[k] < min) {
      min = arrayName[k];
      kmin = k;
    }
  }
  return minValue = arrayName[kmin];
}

// Berechnet einen Korrekturwert für den Sensor SGP30
uint32_t getAbsoluteHumidity(float temperature, float humidity) {
  // approximation formula from Sensirion SGP30 Driver Integration chapter 3.15
  const float absoluteHumidity = 216.7f * ((humidity / 100.0f) * 6.112f * exp((17.62f * temperature) / (243.12f + temperature)) / (273.15f + temperature)); // [g/m^3]
  const uint32_t absoluteHumidityScaled = static_cast<uint32_t>(1000.0f * absoluteHumidity); // [mg/m^3]
  return absoluteHumidityScaled;
}  

// Statusleiste
void displayStatus () {
  display.setFontMode(1);
  display.setBackgroundColor(GxEPD_WHITE);
  display.setForegroundColor(GxEPD_BLACK);

  display.setFont(u8g2_font_helvR08_tf);

  display.setCursor(1, 10);
  display.print("IP: 000.000.000.000");
  
  display.setCursor(255, 10);
  display.print("Sonntag, der 01:01:2018");

  if (validMeasures6H < 1) {
    display.setCursor(536, 10);
    display.print("Erste Messung läuft ...");
  }
  else {
    display.setCursor(505, 10);
    display.print("Aktualisiert um 00:00:00 Uhr");
  }
  
  display.drawLine(0, 12, 640, 12, GxEPD_BLACK);
}

// Gibt Einzelwerte aus
void displayValue (int offsetX, int offsetY, String title, float currentValue, String unit) {
  display.setFontMode(1);
  display.setBackgroundColor(GxEPD_WHITE);
  display.setForegroundColor(GxEPD_BLACK);
  
  
  display.setCursor(offsetX + 1, offsetY + 10);
  display.setFont(u8g2_font_helvB08_tf);
  display.print(title);

  display.setCursor(offsetX + 1, offsetY + 29);  
  display.setFont(u8g2_font_helvB14_tf);
  display.print(currentValue, 1);

  if (currentValue > 0.0) {
    display.setCursor(offsetX + 30, offsetY + 24);
    Serial.println("currentValue > 0");
  }
  if (currentValue > 10.0) {
    display.setCursor(offsetX + 40, offsetY + 24);
    Serial.println("currentValue > 10");
  }
  if (currentValue > 100.0) {
    display.setCursor(offsetX + 50, offsetY + 24);
    Serial.println("currentValue > 100");
  }
  if (currentValue > 1000.0) {
    display.setCursor(offsetX + 60, offsetY + 24);
    Serial.println("currentValue > 1000");
  }
  display.setFont(u8g2_font_helvB08_tf);
  display.print(unit);
}

// Zeichnet das Grid, Hilfslinien und die Skalierung
void displayGrid (int offsetX, int offsetY, unsigned int minY, unsigned int maxY, String title, unsigned int currentValue, String unit) {
  // offsetX bezieht sich auf das erste Pixel von links; offsetY auf die oberste Linie des Rahmens

  // Überschrift
  display.setForegroundColor(GxEPD_BLACK);
  display.setFont(u8g2_font_7x13B_mr);
  display.setCursor(offsetX + 25, offsetY - 5);
  display.print(title);
  display.setCursor(offsetX + 248, offsetY - 5);
  displayprintAlignRight(currentValue);
  display.print(unit);
  
  // Rahmen
  display.drawRect(offsetX + 25, offsetY, 290, 120, GxEPD_BLACK);       // Kasten

  //Hilfslinien
  for (int i = offsetX + 30; i < offsetX + 310; i = i + 6) {              
    display.drawLine(i, offsetY + 30, (i + 3), offsetY + 30, GxEPD_BLACK);   // Hilfslinie 75%           
    display.drawLine(i, offsetY + 60, (i + 3), offsetY + 60, GxEPD_BLACK);   // Hilfslinie 50%
    display.drawLine(i, offsetY + 90, (i + 3), offsetY + 90, GxEPD_BLACK);   // Hilfslinie 25%
  }
  // Beschriftung y-Achse
    display.setFont();
    display.setTextSize(1);        
    display.setCursor(offsetX, offsetY- 4);                              // Beschriftung 100%
    displayprintAlignRight(maxY);
    display.setCursor(offsetX, offsetY + 26);                              // Beschriftung 75%                  
    displayprintAlignRight(((maxY - minY) * 0.75) + minY);
    display.setCursor(offsetX, offsetY + 56);                             // Beschriftung 75%                  
    displayprintAlignRight(((maxY - minY) * 0.5) + minY);
    display.setCursor(offsetX, offsetY + 86);                             // Beschriftung 50%                  
    displayprintAlignRight(((maxY - minY) * 0.25) + minY);
    display.setCursor(offsetX, offsetY + 116);                             // Beschriftung 0%
    displayprintAlignRight(minY);

    // Beschriftung x-Achse
    display.setFont();
    display.setTextSize(1);
    display.setCursor(offsetX + 25, offsetY + 123);
    display.println("Stunden");
    display.setCursor(offsetX + 120, offsetY + 123);
    display.println("-4");
    display.setCursor(offsetX + 216, offsetY + 123);
    display.println("-2");
    display.setCursor(offsetX + 312, offsetY + 123); 
    display.println("0");
}
Auf dieser Seite werden Cookies für die Bereitstellung personalisierter Inhalte und die Seitenanalyse mit Matomo verwendet. Durch die Nutzung dieser Seiten erklären Sie sich damit einverstanden, dass Cookies auf Ihrem Computer gespeichert werden. Weitere Information
iot/luftqualitaetsmonitor/programmversion_0.1.txt · Zuletzt geändert: 21.10.2018 13:17 von Frickelpiet