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");
}
iot/luftqualitaetsmonitor/programmversion_0.1.txt · Zuletzt geändert: 18.05.2023 12:34 von 127.0.0.1