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