Darstellung der Daten
Die Messdaten sollen auf einem LCD und im nächsten Schritt zusätzlich auf einer Webseite dargestellt werden. Die Veränderung der Luftqualität lässt sich gut beobachten:bei geschlossenen Fenster steigt der Wert, sobald für Durchlüftung gesorgt wird, sinkt der Wert kontinuierlich.
Die Hardware
Der Bosch Sensor BME680 misst Temperatur, Luftfeuchtigkeit und den Luftdruck. Außerdem enthält er einen MOX (Metalloxid)-Sensor. Das beheizte Metalloxid ändert seinen Widerstand je nach Konzentration der flüchtigen organischen Verbindungen (VOC = Volatile Organic Compounds) in der Luft.
Viele Materialien in Innenräumen setzen Gase frei:
- Reinigungs- und Pflegemittel
- Möbel, Einrichtungsgegenstände
- Teppiche, Tapeten
- Baumaterialien (Farben)
- elektronische Geräte (Computer, Drucker)
- Heizgeräte (Herde, Öfen)
- menschliche Atemluft, Schweiß
Die Luftqualität wird zunächst als Widerstand in Ohm gemessen. Je höher die Konzentration von VOC, desto geringer ist der Widerstandswert.
Ein interner Algorithmus der proprietären Bosch-Software (BSEC) wandelt diesen Rohwert in einen Wert für die Luftqualität (IAQ = Index for Air Quality) um.
Die Tabelle zeigt den IAQ und die Bewertung der Luftqualität:
Das Umweltbundesamt bewertet den CO2 Gehalt in der Raumluft in verschiedenen Stufen:
Luftqualität in Innenräumen
Raumluftkategorie IDA (indoor Air) | Beschreibung | Konzentration CO2 in ppm |
---|---|---|
IDA 1 | Hohe Raumluftqualität | ≤ 800 |
IDA 2 | Mittlere Raumluftqualität | > 800 - 1000 |
IDA 3 | Mäßige Raumluftqualität | > 1000 - 1400 |
IDA 4 | Niedrige Raumluftqualität | > 1400 |
Quelle: https://www.umweltbundesamt.de/sites/default/files/medien/pdfs/kohlendioxid_2008.pdf
Darstellung der Messwerte
alle Messwerte zu verschiedenen Zeitpunkten im gut durchlüftetem Raum
Auf dem LCD
Im Seriellen Monitor
Auf einer Webseite
Natürlich kommt die Genauigkeit dieser Messwerte nicht an die Präzision professioneller Messgeräte heran. Sie können aber zuverlässige Hinweise auf die Qualität der Raumluft geben. Die Daten für Temperatur, Luftfeuchtigkeit und Luftdruck stehen kurz nach der Aktivierung des Sensors zur Verfügung. Der MOX-Sensor benötigt bis zu 30 Minuten, um erste Messdaten anzuzeigen. Stabilität und Zuverlässigkeit der Messdaten sind erst nach mehreren Tagen gewährleistet.
Der Wert für die Genauigkeit IAQ muss 3 erreichen.
Benötigte Bauteile
- BME680
- LCD-Display I2C 1602
- Leitungsdrähte
Board installieren
Installiere mit dem Boardverwalter das passende Board:
Hardware anschließen
BME680
VCC ⇒ rot
GND ⇒ schwarz
SCL ⇒ gelb: 22
SDA ⇒ grün: 21
Die Hex-Adresse ist 0×77 (BME68X_I2C_ADDR_HIGH).
Ein Verbindung von SDO zu GND ändert die Adresse auf 0×76 (BME68X_I2C_ADDR_LOW).
Quelle: https://joy-it.net/files/files/Produkte/SEN-BME680/SEN-BME680_Anleitung_2024-04-11.pdf
LCD
VCC ⇒ 5 V
GND ⇒ GND
SDA ⇒ 21
SCL ⇒ 22
Benötigte Bibliotheken
Die Bibliothek BSEC ist eine proprietäre Bibliothek, der Quelltext steht nicht zur Verfügung.
Der Compiler zeigt eine entsprechende Meldung:
Bibliothek BSEC Software Library wurde als vorkompiliert angegeben:
Außerdem steht sie nur für die Architekturen SAMD (UNO R4/UNO R4 WiFi ) und ESP (ESP32 oder ESP8266) zur Verfügung.
Funktionen der Bibliothek BSEC
Schlüsselwort | Aktion | Ausgabe |
---|---|---|
runInStatus() | Status des Sensors feststellen | 0 = noch nicht bereit, 1 = bereit |
iaqAccuracy() | Genauigkeit des IAQ-Wertes | 0 - 3 |
temperature() | Temperatur messen | Messbereich: -40 - 85°C |
humidity() | Luftfeuchtigkeit messen | 0 - 100% |
pressure() | Luftdruck messen | 300 – 1100 hPa |
gasResistance() | Widerstandswert in kOhm des MOX-Sensors | Je niedriger der Wert, desto höher ist die Konzentration der VOCs |
breathVocEquivalent() | VOC-Konzentration auf Basis des statischen IAQs schätzen | Angabe in ppm (parts per million) 0,5 - 15 |
iaq() | Luftqualität berechnen | 0 - 500 |
staticIaq() | Statische Luftqualität berechnen | 0 -500 |
co2Equivalent() | CO2 auf Basis des statischen IAQs schätzen | Angabe in ppm (parts per million) |
Der Wert für staticIaq ist für stationäre Geräte optimiert. Er berechnet auf der Basis der bisherigen Daten den Messwert. iaq() ist für mobile Anwendungen gedacht.
Das Programm für die Darstellung der Messwerte auf dem LCD
Bibliotheken einbinden und Variable definieren
#include "bsec.h"
#include "LCDIC2.h"
// 4-zeiliges LCD
LCDIC2 lcd(0x27, 20, 4);
// Objekt (iaqSensor) der Klasse Bsec definieren
Bsec iaqSensor;
Der setup-Teil
void setup()
{
Serial.begin(9600);
// auf Serielle Verbindung warten
while (!Serial);
delay(1000);
// LCD starten
lcd.begin();
// Cursor "verstecken"
lcd.setCursor(false);
// Hex-Adresse des BME680
// BME68X_I2C_ADDR_HIGH = 0x77 (Standard)
// BME68X_I2C_ADDR_LOW = 0x76 (Verbindung SDO zu GND)
iaqSensor.begin(BME68X_I2C_ADDR_HIGH, Wire);
// Array der verfügbaren Sensoren
bsec_virtual_sensor_t sensorList[13] =
{
BSEC_OUTPUT_IAQ,
BSEC_OUTPUT_STATIC_IAQ,
BSEC_OUTPUT_CO2_EQUIVALENT,
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
BSEC_OUTPUT_RAW_TEMPERATURE,
BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_RAW_HUMIDITY,
BSEC_OUTPUT_RAW_GAS,
BSEC_OUTPUT_STABILIZATION_STATUS,
BSEC_OUTPUT_RUN_IN_STATUS,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
BSEC_OUTPUT_GAS_PERCENTAGE
};
// BSEC_SAMPLE_RATE_ULP: Ultra Low Powermode -> Messung alle 5 Minuten
// BSEC_SAMPLE_RATE_LP: Low Powermode -> Messung alle 3 Sekunden
iaqSensor.updateSubscription(sensorList, 13, BSEC_SAMPLE_RATE_LP);
}
Der loop-Teil
void loop()
{
// wenn der Sensor arbeitet ...
if (iaqSensor.run())
{
// Messwerte ermitteln, in Strings umwandeln, . durch , ersetzen
String Temperatur = String(iaqSensor.temperature);
Temperatur.replace(".", ",");
String Luftfeuchtigkeit = String(iaqSensor.humidity);
Luftfeuchtigkeit.replace(".", ",");
String Luftdruck = String(iaqSensor.pressure / 100);
Luftdruck.replace(".", ",");
String IAQ = String(iaqSensor.iaq);
IAQ.replace(".", ",");
String StatischerIAQ = String(iaqSensor.staticIaq);
StatischerIAQ.replace(".", ",");
String CO2 = String(iaqSensor.co2Equivalent);
CO2.replace(".", ",");
String VOC = String(iaqSensor.breathVocEquivalent);
VOC.replace(".", ",");
String GasInProzent = String(iaqSensor.gasPercentage);
GasInProzent.replace(".", ",");
String WiderstandMOXSensor = String(iaqSensor.gasResistance / 1000);
WiderstandMOXSensor.replace(".", ",");
// Ausgabe Serieller Monitor
Serial.println("Statusmeldungen:");
Serial.println("-----------------------------");
Serial.println("Sensor bereit (0-1): " + String(int(iaqSensor.runInStatus)));
Serial.println("Genauigkeit IAQ (0-3): " + String(int(iaqSensor.iaqAccuracy)));
Serial.println("-----------------------------");
Serial.println("Messwerte:");
Serial.println("-----------------------------");
Serial.println("Temperatur: " + Temperatur + " °C");
Serial.println("Feuchtigkeit: " + Luftfeuchtigkeit + " %");
Serial.println("Luftdruck: " + Luftdruck + " hPa");
Serial.println("IAQ: " + IAQ);
Serial.println("Statischer IAQ: " + StatischerIAQ);
Serial.println("Schätzung CO2: " + CO2 + " ppm");
Serial.println("Gas (0-100): " + GasInProzent + " %");
Serial.println("Widerstandswert MOX-Sensor: " + WiderstandMOXSensor + " kOhm");
Serial.println("VOC: " + VOC);
Serial.println("-----------------------------");
// Ausgabe LCD
lcd.setCursor(0, 0);
lcd.print("Temperatur: " + Temperatur + "\337C");
lcd.setCursor(0, 1);
lcd.print("Feuchtigkeit: " + Luftfeuchtigkeit + "%");
lcd.setCursor(0, 2);
lcd.print("IAQ: " + IAQ);
lcd.setCursor(0, 3);
lcd.print("CO2: " + CO2 + " ppm");
}
}
Das Programm mit der Darstellung der Messwerte auf einer Webseite
Bibliotheken einbinden und Variable definieren
#include "bsec.h"
#include "WiFi.h"
#include "WebServer.h"
#include "time.h"
// Objekt (iaqSensor) der Klasse Bsec definieren
Bsec iaqSensor;
char Router[] = "Router_SSID";
char Passwort[] = "xxxxxxxx";
// ip und gateway müssen an das lokale Netz angepasst werden
IPAddress ip(192, 168, 1, 100);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
/*
öffentliche DNS-Server
-----------------------------------------------
OpenDNS 208, 67, 222, 222 (USA)
Google 8, 8, 8, 8 (USA)
Cloudfare 1, 1, 1, 1 (USA)
DNSWWatch 84, 200, 69, 80 (Deutschland)
Quad9 9, 9, 9, 9 (Schweiz)
Neustar UltraDNS 56, 154, 70, 3 (USA, gefiltert)
Deutsche Telekom 217, 5,100, 185
------------------------------------------------
oder die im Router eingetragene IP
im Beispiel: 192, 168, 1, 20
*/
IPAddress primaryDNS(192, 168, 1, 20);
IPAddress secondaryDNS(9, 9, 9, 9);
// NTP-Server aus dem Pool
#define Zeitserver "de.pool.ntp.org"
/*
Liste der Zeitzonen
https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
Zeitzone CET = Central European Time -1 -> 1 Stunde zurück
CEST = Central European Summer Time von
M3 = März, 5.0 = Sonntag 5. Woche, 02 = 2 Uhr
bis M10 = Oktober, 5.0 = Sonntag 5. Woche 03 = 3 Uhr
*/
#define Zeitzone "CET-1CEST,M3.5.0/02,M10.5.0/03"
// time_t enthält die Anzahl der Sekunden seit dem 1.1.1970 0 Uhr
time_t aktuelleZeit;
/*
Struktur tm
tm_hour -> Stunde: 0 bis 23
tm_min -> Minuten: 0 bis 59
tm_sec -> Sekunden 0 bis 59
tm_mday -> Tag 1 bis 31
tm_wday -> Wochentag (0 = Sonntag, 6 = Samstag)
tm_mon -> Monat: 0 (Januar) bis 11 (Dezember)
tm_year -> Jahre seit 1900
tm_yday -> vergangene Tage seit 1. Januar des Jahres
tm_isdst -> Wert > 0 = Sommerzeit (dst = daylight saving time)
*/
tm Zeit;
WebServer server(80);
// benötigte Strings
String Nachricht;
String Temperatur;
String Luftfeuchtigkeit;
String Luftdruck;
String CO2;
String IAQ;
String StatischerIAQ;
Der setup-Teil
void setup()
{
// Zeitzone: Parameter für die zu ermittelnde Zeit
configTzTime(Zeitzone, Zeitserver);
// localtime_r -> Zeit in die lokale Zeitzone setzen
localtime_r(&aktuelleZeit, &Zeit);
Serial.begin(9600);
// auf Serielle Verbindung warten
while (!Serial);
delay(1000);
// WiFi starten
WiFi.begin(Router, Passwort);
WiFi.config(ip, gateway, subnet, primaryDNS, secondaryDNS);
Serial.print("Verbunden mit ");
Serial.println(Router);
// IP anzeigen
Serial.print("Statische IP: ");
Serial.println(ip);
server.begin();
server.on("/", handleRoot);
// Hex-Adresse des BME680
// BME68X_I2C_ADDR_HIGH = 0x77
// BME68X_I2C_ADDR_LOW = 0x76
iaqSensor.begin(BME68X_I2C_ADDR_HIGH, Wire);
// Array der verfügbaren Sensoren
bsec_virtual_sensor_t sensorList[13] =
{
BSEC_OUTPUT_IAQ,
BSEC_OUTPUT_STATIC_IAQ,
BSEC_OUTPUT_CO2_EQUIVALENT,
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
BSEC_OUTPUT_RAW_TEMPERATURE,
BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_RAW_HUMIDITY,
BSEC_OUTPUT_RAW_GAS,
BSEC_OUTPUT_STABILIZATION_STATUS,
BSEC_OUTPUT_RUN_IN_STATUS,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
BSEC_OUTPUT_GAS_PERCENTAGE
};
// BSEC_SAMPLE_RATE_ULP: Ultra Low Powermode -> Messung alle 5 Minuten
// BSEC_SAMPLE_RATE_LP: Low Powermode -> Messung alle 3 Sekunden
iaqSensor.updateSubscription(sensorList, 13, BSEC_SAMPLE_RATE_LP);
}
Der loop-Teil
void loop()
{
// wenn der Sensor arbeitet ...
if (iaqSensor.run())
{
// aktuelle Zeit holen
time(&aktuelleZeit);
// localtime_r -> Zeit in die lokale Zeitzone setzen
localtime_r(&aktuelleZeit, &Zeit);
// es kann bis zu 30 Sekunden dauern
// bis die Zeit ermittelt wird
// Name des Wochentages 0-6
switch (Zeit.tm_wday) {
case 0:
Serial.print("Sonntag");
break;
case 1:
Serial.print("Montag");
break;
case 2:
Serial.print("Dienstag");
break;
case 3:
Serial.print("Mittwoch");
break;
case 4:
Serial.print("Donnerstag");
break;
case 5:
Serial.print("Freitag");
break;
case 6:
Serial.print("Samstag");
break;
}
Serial.print(",");
if (Zeit.tm_mday < 10) Serial.print("0");
Serial.print(Zeit.tm_mday);
Serial.print(".");
// Monat: führende 0 ergänzen
if (Zeit.tm_mon < 10) Serial.print("0");
// Zählung beginnt mit 0 -> + 1
Serial.print(Zeit.tm_mon + 1);
Serial.print(".");
// Anzahl Jahre seit 1900
Serial.print(Zeit.tm_year + 1900);
Serial.print(" ");
// Stunde: wenn Stunde 10 -> 0 davor setzen
if (Zeit.tm_hour < 10) Serial.print("0");
Serial.print(Zeit.tm_hour);
Serial.print(":");
// Minuten
if (Zeit.tm_min < 10) Serial.print("0");
Serial.println(Zeit.tm_min);
server.handleClient();
// Messwerte ermitteln, in Strings umwandeln, . durch , ersetzen
Temperatur = String(iaqSensor.temperature);
Temperatur.replace(".", ",");
Luftfeuchtigkeit = String(iaqSensor.humidity);
Luftfeuchtigkeit.replace(".", ",");
Luftdruck = String(iaqSensor.pressure / 100);
Luftdruck.replace(".", ",");
IAQ = String(iaqSensor.iaq);
IAQ.replace(".", ",");
StatischerIAQ = String(iaqSensor.staticIaq);
StatischerIAQ.replace(".", ",");
CO2 = String(iaqSensor.co2Equivalent);
CO2.replace(".", ",");
String VOC = String(iaqSensor.breathVocEquivalent);
VOC.replace(".", ",");
String GasInProzent = String(iaqSensor.gasPercentage);
GasInProzent.replace(".", ",");
String WiderstandMOXSensor = String(iaqSensor.gasResistance / 1000);
WiderstandMOXSensor.replace(".", ",");
// Ausgabe Serieller Monitor
Serial.println("Statusmeldungen:");
Serial.println("-----------------------------");
Serial.println("Sensor bereit (0-1): " + String(int(iaqSensor.runInStatus)));
Serial.println("Genauigkeit IAQ (0-3): " + String(int(iaqSensor.iaqAccuracy)));
Serial.println("-----------------------------");
Serial.println("Messwerte:");
Serial.println("-----------------------------");
Serial.println("Temperatur: " + Temperatur + " °C");
Serial.println("Feuchtigkeit: " + Luftfeuchtigkeit + " %");
Serial.println("Luftdruck: " + Luftdruck + " hPa");
Serial.println("IAQ: " + IAQ);
Serial.println("Statischer IAQ: " + StatischerIAQ);
Serial.println("Schätzung CO2: " + CO2 + " ppm");
Serial.println("Gas (0-100): " + GasInProzent + " %");
Serial.println("Widerstandswert MOX-Sensor: " + WiderstandMOXSensor + " kOhm");
Serial.println("VOC: " + VOC);
Serial.println("-----------------------------");
}
}
HTML-Seite erstellen und senden
void handleRoot()
{
// Seite zusammenbauen
// Kopf der HTML-Seite: aktualisierung alle 60 Sekunden
// kann angepasst werden
Nachricht = "<head><meta http-equiv=\"refresh\" content=\"60\"></head>";
Nachricht += "<h1>Messwerte BME680</h1><hr>";
// Datum anzeigen
// formatieren: Schrift 14pt, ohne Serifen, Zeilenhöhe
Nachricht += "<div style = \"font-size:14pt; font-family:sans-serif; line-height:1.8em\">";
Nachricht += "Letzte Aktualisierung: ";
// wenn der Tag < 10
if (Zeit.tm_mday < 10) Nachricht += "0";
Nachricht += String(Zeit.tm_mday) + ".";
// wen der Monat < 10, Zählung beginnt mit 0
if ((Zeit.tm_mon + 1) < 10) Nachricht += "0";
Nachricht += String(Zeit.tm_mon + 1) + ". ";
// Zeit anzeigen
// wenn die Stunde < 10
if (Zeit.tm_hour < 10) Nachricht += "0";
Nachricht += String(Zeit.tm_hour) + ":";
// wenn die Minute < 10
if (Zeit.tm_min < 10) Nachricht += "0";
Nachricht+= String(Zeit.tm_min) + " Uhr<br><hr>";
Nachricht += "Temperatur: " + Temperatur + " °C<br>";
Nachricht += "Luftfeuchtigkeit: " + Luftfeuchtigkeit + " %<br>";
Nachricht += "Luftdruck: " + Luftdruck + " hPa<br>";
Nachricht += "IAQ: " + IAQ + "<br>";
Nachricht += "Statischer IAQ: " + StatischerIAQ + "<br>";
Nachricht += "Schätzung CO2: " + CO2 + " ppm<br>";
Nachricht += "Genauigkeit IAQ (0-3): " + String(int(iaqSensor.iaqAccuracy)) + "</div>";
// Button aktualisieren
Nachricht += "<hr><input style=\"font-size:16pt; font-weight:bold;";
Nachricht += "background-color:#55A96B;";
Nachricht += "display:block; cursor:pointer;\"type=\"button\"";
// IP für den Button aktualisieren (location.href)
// muss mit dem Wert für IPAdress übereinstimmen (. statt ,)
Nachricht += " onClick=\"location.href='http://192.168.1.100'\" value=\"aktualisieren\">";
Nachricht += "<hr>";
// Nachricht senden -> Seite anzeigen
server.send(200, "text/html", Nachricht);
}
Verwandte Anleitungen:
- BME280 -Messdaten anzeigen
- DHT11/DHT22 - Durchschnittstemperatur berechnen und auf einem OLED-Display anzeigen
- DHT11/DHT22 - Wetterdaten aufzeichnen
- DHT11/DHT22 - Wetterdaten mit Bluetooth-Modul übermitteln
- DHT11/DHT22 - Wetterstation LAN
- DHT11/DHT22 - Wetterstation Anzeige auf einem LCD
- DHT11/DHT22 - Wetterstation WiFi
- BMP280 - Temperatur und Luftdruck messen
- Temperaturanzeige auf einem LCD mit einem ESP32-Wroom
- TMP36 - Temperatur messen
- Wetterstation mit CO2-Messung
- Wetterstation mit Nano ESP32, ESP32-Wroom, NodeMCU, DHT11/DHT22 und OLED
- Wetterstation mit UNO R4 WiFi und LCD
- DHT11/DHT22 - Wetterstation LAN Daten speichern
letzte Aktualisierung: