Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Nächste Überarbeitung
Vorhergehende Überarbeitung
www-messdatenerfassung-sas:platinenschaltung [2026/09/12 13:33] – angelegt bieblwww-messdatenerfassung-sas:platinenschaltung [2026/09/12 16:09] (aktuell) biebl
Zeile 1: Zeile 1:
 +====== WWW: Platinen mit TESTCODE======
 +
 +  * [[platinenschaltung-Außenbox|WWW: Platinen mit TESTCODE für die Außenbox]]
  
 ^ Seite  ^ Inhalt  ^ ^ Seite  ^ Inhalt  ^
Zeile 10: Zeile 13:
 | [[Unterrichtseinsatz|Schule]]  | Einsatz im Unterricht | | [[Unterrichtseinsatz|Schule]]  | Einsatz im Unterricht |
 | [[Sonstiges|Sonstiges]]| dies und das....| | [[Sonstiges|Sonstiges]]| dies und das....|
 +
 +
 +
 +
 +  * [[https://easyeda.com|Platinenerstellung - extern]]
 +    * Nach Erstellung benötigt die günstigste "Verschiffung" 2 Wochen (kostet aber unter 5 EURO)
 +    * Bei unseren Platinen kostete eine Platine 1,5 Euro
 +
 +  * [[https://www.youtube.com/watch?v=WAt0aocWS6w&t=121s|Nach diesem Video hat es bei uns auf Anhieb funktioniert ;-)]]
 +
 +
 +  * [[lehrer:arduino:p52d1-raumklima-feinstaub-licht|nur intern für SAS-Lehrer: P52-D1-mini]]
 +
 +  * [[info:3d-druck:sds-011-feinstaubsensor|Gehäuse - intern]]
 +
 +===== Raumklima - Messbox =====
 +
 +  * [[info:arduino:p42d1-raumklima|P-42  intern]]
 +  * [[lehrer:arduino:p42d1-raumklima|L-P42  intern]]
 +
 +{{:info:arduino:esp8266-wemos-d1-mini-pinout-gpio-pin-2.png?300|}}
 +
 +
 +^ Sensor   ^ Pin am D1 mini                                              ^ GPIO   ^ Verwendung  ^ Status       ^
 +| DHT22    | D7                                                          | 13                 | ✅ gut       |
 +| MH-Z19B  | Tx nach Rx D6 beim D1 OHNE Spannungsteiler!; Rx nach Tx D5  | 14 12  | UART        | ✅           |
 +| BMP180   | D1 (SCL) + D2 (SDA)                                         | 5 + 4  | I2C         | ✅ Standard  |
 +| LED      | D4                                                          | 2      |             | ✅           |
 +| LDR      | A0                                                          | A0     | analogRead  | ✅ Standard  |
 +
 +==== D1-mini ====
 +
 +  * Inzwischen wird der DHT22 mit BMP180 bei neueren Messboxen durch den BME280 ersetzt, was den Aufbau und den Code nochmalig vereinfacht.
 +  * Der MHZ19B wird inzwischen **ohne Spannungsteiler** betrieben - Der D1-mini ist 5V tolerant!
 +  * Bei einem ESP32 wäre es nicht ratsam, da dieser nicht 5V tolerant ist...
 +
 +{{:www-messdatenerfassung-sas:d1-mini-raumklima-2026-01-08-dht22-bmp180.png?400 |Verkabelung der Klimamessbox, erstellt mit Fritzing}}
 +
 +{{:www-messdatenerfassung-sas:uart-mhz-19b-rxd-d6-spannungsteiler-nicht-notwendig.png?400 |}}
 +
 +
 +==== Bestückung ====
 +=== Materialliste ===
 +
 +  * auf Vollständigkeit prüfen
 +
 +{{:www-messdatenerfassung-sas:0-raumklima-platine-loeten-2026-06-13-01.png?400 |}}
 +
 +=== 1. Drahtbrücke ===
 +
 +  * überstehende Reste entfernen
 +
 +
 +
 +{{:www-messdatenerfassung-sas:1-raumklima-platine-loeten-2026-06-13-02.png?800 |}}
 +=== 2. Winkelstifte LED und BMP ===
 +
 +  * ACHTUNG: auf die genaue Position achten
 +
 +{{:www-messdatenerfassung-sas:2-raumklima-platine-loeten-2026-06-13-03.png?800 |}}
 +=== 3.  3er Stifte DHT22 ===
 +
 +  * auf senkrechte Montage achten
 +
 +{{:www-messdatenerfassung-sas:3-raumklima-platine-loeten-2026-06-13-04.png?800 |}}
 +=== 4. 8er Buchsen D1-mini ==
 +
 +  * auf senkrechte Montage achten
 +  * Reste nach dem Löten entfernen
 +
 +{{:www-messdatenerfassung-sas:4-raumklima-platine-loeten-2026-06-13-05.png?800 |}}
 +=== 5. 4er- und 5er-Buchse MHZ19B ===
 +
 +  * auf senkrechte Montage achten
 +  * Reste nach dem Löten entfernen
 +
 +{{:www-messdatenerfassung-sas:5-raumklima-platine-loeten-2026-06-13-06.png?800 |}}
 +=== 6. Widerstand 470kΩ ===
 +
 +  * Reste nach dem Löten entfernen
 +
 +{{:www-messdatenerfassung-sas:6-raumklima-platine-loeten-2026-06-13-07.png?800 |}}
 +=== 7.  2er-Buchse LDR ===
 +
 +  * auf senkrechte Montage achten
 +  * Reste nach dem Löten entfernen
 +
 +
 +{{:www-messdatenerfassung-sas:7-raumklima-platine-loeten-2026-06-13-08.png?800 |}}
 +=== 8. Externe Sensoren ===
 +
 +{{:www-messdatenerfassung-sas:8-raumklima-platine-loeten-2026-06-13-09.png?800 |}}
 +=== 9.  fertiges Modul ===
 +
 +
 +{{:info:platinen:raumklima-platine-loeten-2026-06-13-10.png?800|}}
 +
 +
 +
 +====== Testcode - Raumklima ======
 +
 +[[lehrer:arduino:p42d1-raumklima#testcode|Vollständiger TESTCODE]]
 +
 +===== BMP180 =====
 +
 +<code>
 +/********************************************************************************************
 +  BMP180 Test - Luftdruck und Temperatur
 + *  
 +  Verdrahtung (I2C):
 +  - SCL: D1 (GPIO5)
 +  - SDA: D2 (GPIO4)
 +  - VCC: 3.3V
 +  - GND: GND
 + ********************************************************************************************/
 +
 +#include <Wire.h>
 +#include <SFE_BMP180.h>
 +
 +#define ALTITUDE 42.0  // Höhe über Meeresspiegel in Metern (anpassen!)
 +
 +SFE_BMP180 pressure;
 +
 +void setup() {
 +  Serial.begin(115200);
 +  delay(500);
 +  Serial.println("\n=== BMP180 Test ===");
 +  
 +  // I2C initialisieren (Standard-Pins D1=SCL, D2=SDA)
 +  Wire.begin();
 +  
 +  if (pressure.begin()) {
 +    Serial.println("BMP180 erfolgreich initialisiert!");
 +  } else {
 +    Serial.println("FEHLER: BMP180 nicht gefunden!");
 +    Serial.println("Prüfe:");
 +    Serial.println("  - I2C-Verbindung (D1=SCL, D2=SDA)");
 +    Serial.println("  - 3.3V Versorgung");
 +    Serial.println("  - GND-Verbindung");
 +    while(1) delay(10);
 +  }
 +  
 +  Serial.println("\nStarte Messungen (alle 2 Sekunden)...\n");
 +}
 +
 +void loop() {
 +  char status;
 +  double T, P, p0;
 +  
 +  // 1. Temperatur messen
 +  status = pressure.startTemperature();
 +  if (status != 0) {
 +    delay(status);  // Warte auf Messung
 +    
 +    status = pressure.getTemperature(T);
 +    if (status != 0) {
 +      Serial.print("Temperatur: ");
 +      Serial.print(T, 2);
 +      Serial.println(" °C");
 +      
 +      // 2. Luftdruck messen (benötigt Temperatur)
 +      status = pressure.startPressure(3);  // Oversampling Level 3 (höchste Genauigkeit)
 +      if (status != 0) {
 +        delay(status);
 +        
 +        status = pressure.getPressure(P, T);
 +        if (status != 0) {
 +          Serial.print("Stationsdruck: ");
 +          Serial.print(P, 2);
 +          Serial.println(" hPa");
 +          
 +          // 3. Auf Meereshöhe korrigierter Druck
 +          p0 = pressure.sealevel(P, ALTITUDE);
 +          Serial.print("Meereshöhe (p0): ");
 +          Serial.print(p0, 2);
 +          Serial.println(" hPa");
 +        }
 +      }
 +    }
 +  } else {
 +    Serial.println("FEHLER: Messung fehlgeschlagen!");
 +  }
 +  
 +  Serial.println("---");
 +  delay(2000);
 +}
 +</code>
 +
 +<code>
 +18:33:53.491 -> ---
 +18:33:55.487 -> Temperatur: 24.95 °C
 +18:33:55.518 -> Stationsdruck: 1003.11 hPa
 +18:33:55.518 -> Meereshöhe (p0): 1008.12 hPa
 +18:33:55.518 -> ---
 +</code>
 +===== DHT22 =====
 +<code>
 +/********************************************************************************************
 +  DHT22 Test - Luftfeuchtigkeit und Temperatur
 + *  
 +  Verdrahtung:
 +  - DATA: D7 (GPIO13)
 +  - VCC: 3.3V oder 5V
 +  - GND: GND
 +  - Pull-up Widerstand (4.7k-10k) zwischen DATA und VCC (oft schon auf Modul vorhanden)
 + ********************************************************************************************/
 +
 +#include "DHT.h"
 +
 +#define DHTPIN 13      // D7
 +#define DHTTYPE DHT22
 +
 +DHT dht(DHTPIN, DHTTYPE);
 +
 +void setup() {
 +  Serial.begin(115200);
 +  delay(500);
 +  Serial.println("\n=== DHT22 Test ===");
 +  
 +  dht.begin();
 +  Serial.println("DHT22 initialisiert");
 +  Serial.println("Warte 2 Sekunden auf ersten Messwert...\n");
 +  
 +  delay(2000);  // DHT22 braucht kurze Aufwärmzeit
 +}
 +
 +void loop() {
 +  // Luftfeuchtigkeit und Temperatur auslesen
 +  float humidity = dht.readHumidity();
 +  float temperature = dht.readTemperature();
 +  
 +  // Prüfen ob Messung erfolgreich
 +  if (isnan(humidity) || isnan(temperature)) {
 +    Serial.println("FEHLER: DHT22-Messung fehlgeschlagen!");
 +    Serial.println("Prüfe:");
 +    Serial.println("  - Datenpin (D7)");
 +    Serial.println("  - Versorgungsspannung (3.3V oder 5V)");
 +    Serial.println("  - Pull-up Widerstand vorhanden?");
 +  } else {
 +    Serial.print("Luftfeuchtigkeit: ");
 +    Serial.print(humidity, 1);
 +    Serial.println(" %");
 +    
 +    Serial.print("Temperatur: ");
 +    Serial.print(temperature, 1);
 +    Serial.println(" °C");
 +    
 +    // Taupunkt berechnen (optional)
 +    float dewPoint = temperature - ((100 - humidity) / 5.0);
 +    Serial.print("Taupunkt (ca.): ");
 +    Serial.print(dewPoint, 1);
 +    Serial.println(" °C");
 +  }
 +  
 +  Serial.println("---");
 +  delay(2500);  // DHT22 sollte max. alle 2 Sekunden ausgelesen werden
 +}
 +</code>
 +<code>
 +18:41:12.618 -> ---
 +18:41:15.127 -> Luftfeuchtigkeit: 43.9 %
 +18:41:15.127 -> Temperatur: 22.3 °C
 +18:41:15.127 -> Taupunkt (ca.): 11.1 °C
 +18:41:15.127 -> ---
 +</code>
 +
 +===== MHZ19B =====
 +<code>
 +/********************************************************************************************
 +  MH-Z19B Test - CO₂-Sensor
 + *  
 +  Verdrahtung (UART):
 +  - RX (Sensor) → TX (D5 / GPIO14) des D1 Mini
 +  - TX (Sensor) → RX (D6 / GPIO12) des D1 Mini
 +  - VCC: 5V (wichtig! Sensor braucht 5V)
 +  - GND: GND
 + *  
 +  Hinweis: Sensor braucht ~3 Minuten Aufwärmzeit für stabile Werte!
 + ********************************************************************************************/
 +
 +#include <SoftwareSerial.h>
 +
 +// Software Serial: RX=D6 (GPIO12), TX=D5 (GPIO14)
 +SoftwareSerial mhzSerial(12, 14);
 +
 +// Kommando zum Auslesen der CO₂-Konzentration
 +byte cmdReadCO2[] = {0xFF, 0x01, 0x86, 0, 0, 0, 0, 0, 0x79};
 +
 +void setup() {
 +  Serial.begin(115200);
 +  delay(500);
 +  Serial.println("\n=== MH-Z19B CO₂-Sensor Test ===");
 +  
 +  mhzSerial.begin(9600);
 +  Serial.println("UART (9600 baud) initialisiert");
 +  Serial.println("\nHinweis: Sensor braucht ~3 Min. Aufwärmzeit!");
 +  Serial.println("Starte Messungen alle 5 Sekunden...\n");
 +  
 +  delay(2000);
 +}
 +
 +void loop() {
 +  int ppm = -1;
 +  int temp = -999;
 +  
 +  if (readMHZ19B(ppm, temp)) {
 +    Serial.print("CO₂: ");
 +    Serial.print(ppm);
 +    Serial.print(" ppm");
 +    
 +    // Bewertung
 +    if (ppm < 700) {
 +      Serial.print(" [Sehr gut - Außenluftniveau]");
 +    } else if (ppm < 1000) {
 +      Serial.print(" [Gut - Innenraumluft]");
 +    } else if (ppm < 1500) {
 +      Serial.print(" [Mäßig - Lüften empfohlen]");
 +    } else if (ppm < 2000) {
 +      Serial.print(" [Schlecht - Dringend lüften!]");
 +    } else {
 +      Serial.print(" [Sehr schlecht - SOFORT lüften!]");
 +    }
 +    Serial.println();
 +    
 +    Serial.print("Sensor-Temperatur: ");
 +    Serial.print(temp);
 +    Serial.println(" °C");
 +    
 +  } else {
 +    Serial.println("FEHLER: Keine gültigen Daten vom Sensor!");
 +    Serial.println("Prüfe:");
 +    Serial.println("  - RX/TX vertauscht? (RX Sensor → TX D1, TX Sensor → RX D1)");
 +    Serial.println("  - 5V Versorgung vorhanden?");
 +    Serial.println("  - Sensor aufgewärmt? (3 Min. warten)");
 +  }
 +  
 +  Serial.println("---");
 +  delay(5000);  // Alle 5 Sekunden (Sensor kann häufiger, aber nicht nötig)
 +}
 +
 +// MH-Z19B auslesen mit Checksummen-Prüfung
 +bool readMHZ19B(int &ppm, int &tempC) {
 +  // Puffer leeren (alte Daten verwerfen)
 +  while (mhzSerial.available()) {
 +    mhzSerial.read();
 +  }
 +  
 +  // Kommando senden
 +  mhzSerial.write(cmdReadCO2, 9);
 +  
 +  // Antwort lesen (9 Bytes, max. 200ms warten)
 +  byte response[9];
 +  int bytesRead = 0;
 +  unsigned long startTime = millis();
 +  
 +  while (bytesRead < 9 && millis() - startTime < 200) {
 +    if (mhzSerial.available()) {
 +      response[bytesRead++] = mhzSerial.read();
 +    }
 +    delay(1);
 +  }
 +  
 +  // Prüfen ob vollständiges Paket empfangen
 +  if (bytesRead != 9) {
 +    Serial.print("Nur ");
 +    Serial.print(bytesRead);
 +    Serial.println(" von 9 Bytes empfangen");
 +    return false;
 +  }
 +  
 +  // Header prüfen (sollte 0xFF 0x86 sein)
 +  if (response[0] != 0xFF || response[1] != 0x86) {
 +    Serial.print("Falscher Header: 0x");
 +    Serial.print(response[0], HEX);
 +    Serial.print(" 0x");
 +    Serial.println(response[1], HEX);
 +    return false;
 +  }
 +  
 +  // Checksumme berechnen und prüfen
 +  byte checksum = 0;
 +  for (int i = 1; i < 8; i++) {
 +    checksum += response[i];
 +  }
 +  checksum = 0xFF - checksum + 1;
 +  
 +  if (checksum != response[8]) {
 +    Serial.print("Checksumme falsch! Erwartet: 0x");
 +    Serial.print(checksum, HEX);
 +    Serial.print(" Erhalten: 0x");
 +    Serial.println(response[8], HEX);
 +    return false;
 +  }
 +  
 +  // Werte extrahieren
 +  ppm = response[2] * 256 + response[3];
 +  tempC = response[4] - 40;  // Temperatur ist offset um 40°C
 +  
 +  // Plausibilitätsprüfung
 +  if (ppm < 0 || ppm > 5000) {
 +    Serial.print("Unplausibel: ppm=");
 +    Serial.println(ppm);
 +    return false;
 +  }
 +  
 +  return true;
 +}
 +</code>
 +<code>
 +18:44:51.629 -> ---
 +18:44:56.637 -> CO₂: 1349 ppm [Mäßig - Lüften empfohlen]
 +18:44:56.637 -> Sensor-Temperatur: 18 °C
 +18:44:56.637 -> ---
 +</code>
 +
 +==== Kalibrierung ====
 +
 +Falls der MHZ-19B falsche Werte anzeigt hilft vielleicht eine Kalibrierung an der frischen Luft ;-)
 +
 +  ⚡ Start → ABC wird sofort ausgeschaltet
 +  ⏱️ 20 Minuten → Sensor stabilisiert sich in Frischluft (zeigt Countdown)
 +  🎯 Kalibrierung → Zero Point Calibration auf Frischluft
 +  📊 Messbetrieb → Kontinuierliche Messung alle 10 Sekunden
 +
 +Vorgehensweise:
 +
 +  * Code auf den D1 mini hochladen
 +  * Sofort mit Powerbank vor die Tür legen (wichtig: nicht erst drinnen testen!)
 +  * 20 Minuten warten (Sie sehen den Countdown am seriellen Monitor)
 +  * Nach Kalibrierung können Sie das Gerät wieder reinholen
 +  * Fertig - der Sensor zeigt jetzt korrekte Werte!
 +
 +<code>
 +/********************************************************************************************
 +  MH-Z19B CO2-Sensor KALIBRIERUNGS-PROGRAMM
 +  für D1 mini (ESP8266)
 + *  
 +  ABLAUF:
 +  =======
 +  1. Start: ABC (Automatic Baseline Correction) wird ausgeschaltet
 +  2. Warten: 20 Minuten Stabilisierungszeit in Frischluft
 +  3. Kalibrierung: Zero Point Calibration auf Frischluft (~420 ppm)
 +  4. Messbetrieb: Kontinuierliche Messung alle 10 Sekunden
 + *  
 +  VERWENDUNG:
 +  ===========
 +  - Sensor mit Powerbank versorgen
 +  - SOFORT nach dem Start vor die Tür legen (Frischluft!)
 +  - 20 Minuten warten
 +  - Nach Kalibrierung kann Sensor wieder ins Haus
 + *  
 +  VERDRAHTUNG MH-Z19B:
 +  ====================
 +  MH-Z19B  →  D1 mini
 +  Vin      →  5V (oder VIN bei Powerbank)
 +  GND      →  GND
 +  TX       →  D6 (GPIO12) = RX vom D1 mini
 +  RX       →  D5 (GPIO14) = TX vom D1 mini
 + *  
 +  Erstellt: 2025-01-08
 + ********************************************************************************************/
 +
 +#include <SoftwareSerial.h>
 +
 +/********************************************************************************************
 +  KONFIGURATION
 + ********************************************************************************************/
 +
 +// Serielle Schnittstelle für MH-Z19B (Software Serial)
 +#define MHZ_RX_PIN D6    // D1 mini D6 → MH-Z19B TX
 +#define MHZ_TX_PIN D5    // D1 mini D5 → MH-Z19B RX
 +
 +// Zeitsteuerung
 +const unsigned long WARTEZEIT_MS = 20UL * 60UL * 1000UL;  // 20 Minuten in Millisekunden
 +const unsigned long MESS_INTERVALL = 10000;                // 10 Sekunden Messintervall
 +
 +/********************************************************************************************
 +  GLOBALE VARIABLEN
 + ********************************************************************************************/
 +
 +SoftwareSerial mhzSerial(MHZ_RX_PIN, MHZ_TX_PIN);
 +
 +// Status-Flags
 +bool abcDeaktiviert = false;
 +bool kalibriert = false;
 +unsigned long startzeit = 0;
 +
 +/********************************************************************************************
 +  MH-Z19B BEFEHLE
 + ********************************************************************************************/
 +
 +// Befehl: CO2-Wert lesen
 +byte cmdReadCO2[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
 +
 +// Befehl: ABC ausschalten
 +byte cmdABCoff[9] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86};
 +
 +// Befehl: Zero Point Calibration (Frischluft-Kalibrierung)
 +byte cmdCalibrate[9] = {0xFF, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78};
 +
 +/********************************************************************************************
 +  SETUP
 + ********************************************************************************************/
 +void setup() {
 +  // Serielle Schnittstelle zum PC starten
 +  Serial.begin(115200);
 +  delay(200);
 +  
 +  Serial.println();
 +  Serial.println("============================================================");
 +  Serial.println("   MH-Z19B KALIBRIERUNGS-PROGRAMM");
 +  Serial.println("============================================================");
 +  Serial.println();
 +  Serial.println("⚠️  WICHTIG: Sensor JETZT sofort in Frischluft bringen!");
 +  Serial.println("    (Fenster öffnen oder vor die Tür legen)");
 +  Serial.println();
 +  
 +  // Serielle Schnittstelle zum MH-Z19B starten (9600 Baud)
 +  mhzSerial.begin(9600);
 +  delay(500);
 +  
 +  // SCHRITT 1: ABC ausschalten
 +  Serial.println("SCHRITT 1: ABC (Automatic Baseline Correction) ausschalten...");
 +  mhzSerial.write(cmdABCoff, 9);
 +  delay(1000);
 +  abcDeaktiviert = true;
 +  Serial.println("✓ ABC deaktiviert");
 +  Serial.println();
 +  
 +  // Startzeit merken
 +  startzeit = millis();
 +  
 +  // Berechne Kalibrierungszeitpunkt
 +  unsigned long minuten = WARTEZEIT_MS / 60000;
 +  Serial.println("============================================================");
 +  Serial.println("SCHRITT 2: Wartezeit für Stabilisierung");
 +  Serial.println("============================================================");
 +  Serial.printf("Kalibrierung erfolgt in %lu Minuten\n", minuten);
 +  Serial.println();
 +  Serial.println("Der Sensor stabilisiert sich jetzt in der Frischluft.");
 +  Serial.println("Sie können den Fortschritt am seriellen Monitor beobachten.");
 +  Serial.println();
 +  Serial.println("------------------------------------------------------------");
 +  Serial.println("  MESSWERTE WÄHREND STABILISIERUNG:");
 +  Serial.println("------------------------------------------------------------");
 +  Serial.println();
 +}
 +
 +/********************************************************************************************
 +  LOOP
 + ********************************************************************************************/
 +void loop() {
 +  unsigned long jetzt = millis();
 +  unsigned long vergangen = jetzt - startzeit;
 +  
 +  // CO2-Wert auslesen
 +  int co2 = leseCO2();
 +  
 +  // ========== VOR DER KALIBRIERUNG ==========
 +  if (!kalibriert && vergangen < WARTEZEIT_MS) {
 +    // Zeit bis zur Kalibrierung berechnen
 +    unsigned long verbleibend = WARTEZEIT_MS - vergangen;
 +    unsigned long minutenVerbleibend = verbleibend / 60000;
 +    unsigned long sekundenVerbleibend = (verbleibend % 60000) / 1000;
 +    
 +    // Fortschritt anzeigen
 +    Serial.print("⏱️  ");
 +    Serial.printf("%02lu:%02lu", minutenVerbleibend, sekundenVerbleibend);
 +    Serial.print("  |  CO2: ");
 +    
 +    if (co2 > 0) {
 +      Serial.print(co2);
 +      Serial.println(" ppm  (Stabilisierung läuft...)");
 +    } else {
 +      Serial.println("---  (Lesefehler)");
 +    }
 +    
 +    // Warte 10 Sekunden bis zur nächsten Messung
 +    delay(MESS_INTERVALL);
 +  }
 +  
 +  // ========== KALIBRIERUNG DURCHFÜHREN ==========
 +  else if (!kalibriert && vergangen >= WARTEZEIT_MS) {
 +    Serial.println();
 +    Serial.println("============================================================");
 +    Serial.println("SCHRITT 3: KALIBRIERUNG AUF FRISCHLUFT");
 +    Serial.println("============================================================");
 +    Serial.println();
 +    
 +    // Aktuellen Wert vor Kalibrierung anzeigen
 +    Serial.print("CO2-Wert vor Kalibrierung: ");
 +    if (co2 > 0) {
 +      Serial.print(co2);
 +      Serial.println(" ppm");
 +    } else {
 +      Serial.println("nicht verfügbar");
 +    }
 +    Serial.println();
 +    
 +    // Kalibrierungs-Befehl senden
 +    Serial.println("Sende Zero Point Calibration Befehl...");
 +    mhzSerial.write(cmdCalibrate, 9);
 +    delay(2000);  // Warten auf Verarbeitung
 +    
 +    Serial.println("✓ Kalibrierung abgeschlossen!");
 +    Serial.println();
 +    Serial.println("Der Sensor verwendet jetzt den aktuellen Wert als");
 +    Serial.println("Frischluft-Referenz (~420 ppm).");
 +    Serial.println();
 +    
 +    kalibriert = true;
 +    
 +    Serial.println("============================================================");
 +    Serial.println("SCHRITT 4: NORMALER MESSBETRIEB");
 +    Serial.println("============================================================");
 +    Serial.println();
 +    Serial.println("✓ Sensor ist kalibriert und einsatzbereit!");
 +    Serial.println();
 +    Serial.println("Sie können den Sensor jetzt wieder ins Haus bringen.");
 +    Serial.println("Die Messwerte werden jetzt alle 10 Sekunden angezeigt.");
 +    Serial.println();
 +    Serial.println("------------------------------------------------------------");
 +    Serial.println("  MESSWERTE:");
 +    Serial.println("------------------------------------------------------------");
 +    Serial.println();
 +  }
 +  
 +  // ========== NORMALER MESSBETRIEB ==========
 +  else if (kalibriert) {
 +    // Betriebszeit berechnen
 +    unsigned long betriebszeit = (jetzt - startzeit) / 1000;  // in Sekunden
 +    unsigned long stunden = betriebszeit / 3600;
 +    unsigned long minuten = (betriebszeit % 3600) / 60;
 +    unsigned long sekunden = betriebszeit % 60;
 +    
 +    // Messwert anzeigen
 +    Serial.printf("%02lu:%02lu:%02lu  |  ", stunden, minuten, sekunden);
 +    Serial.print("CO2: ");
 +    
 +    if (co2 > 0) {
 +      Serial.print(co2);
 +      Serial.print(" ppm");
 +      
 +      // Bewertung hinzufügen
 +      if (co2 < 600) {
 +        Serial.println("  ✓ Sehr gut (Frischluft)");
 +      } else if (co2 < 1000) {
 +        Serial.println("  ✓ Gut (normale Raumluft)");
 +      } else if (co2 < 1500) {
 +        Serial.println("  ⚠️  Mäßig (Lüften empfohlen)");
 +      } else {
 +        Serial.println("  ⚠️  Schlecht (Dringend lüften!)");
 +      }
 +    } else {
 +      Serial.println("---  (Lesefehler)");
 +    }
 +    
 +    delay(MESS_INTERVALL);
 +  }
 +}
 +
 +/********************************************************************************************
 +  HILFSFUNKTIONEN
 + ********************************************************************************************/
 +
 +/**
 + * Liest den CO2-Wert vom MH-Z19B Sensor
 + * @return CO2-Wert in ppm (0 bei Fehler)
 + */
 +int leseCO2() {
 +  // Puffer leeren
 +  while (mhzSerial.available()) {
 +    mhzSerial.read();
 +  }
 +  
 +  // Read-Befehl senden
 +  mhzSerial.write(cmdReadCO2, 9);
 +  
 +  // Auf Antwort warten (max. 1 Sekunde)
 +  unsigned long timeout = millis() + 1000;
 +  while (mhzSerial.available() < 9 && millis() < timeout) {
 +    delay(10);
 +  }
 +  
 +  // Prüfen, ob genug Daten empfangen wurden
 +  if (mhzSerial.available() < 9) {
 +    return 0;  // Timeout
 +  }
 +  
 +  // 9 Bytes lesen
 +  byte response[9];
 +  for (int i = 0; i < 9; i++) {
 +    response[i] = mhzSerial.read();
 +  }
 +  
 +  // Prüfen, ob Antwort gültig ist
 +  if (response[0] != 0xFF || response[1] != 0x86) {
 +    return 0;  // Ungültige Antwort
 +  }
 +  
 +  // Checksum überprüfen (optional, für mehr Zuverlässigkeit)
 +  byte checksum = 0;
 +  for (int i = 1; i < 8; i++) {
 +    checksum += response[i];
 +  }
 +  checksum = 0xFF - checksum + 1;
 +  
 +  if (checksum != response[8]) {
 +    return 0;  // Checksum-Fehler
 +  }
 +  
 +  // CO2-Wert aus Bytes 2 und 3 extrahieren
 +  int co2 = (response[2] << 8) | response[3];
 +  
 +  return co2;
 +}
 +
 +/********************************************************************************************
 +  ZUSÄTZLICHE INFORMATIONEN
 + ********************************************************************************************
 + *  
 +  MH-Z19B KALIBRIERUNG - HINTERGRUND:
 +  ====================================
 + *  
 +  Warum kalibrieren?
 +  ------------------
 +  - CO2-Sensoren können im Laufe der Zeit "driften"
 +  - Referenzwert verschiebt sich → falsche Messwerte
 +  - Kalibrierung stellt bekannten Referenzpunkt wieder her
 + *  
 + *  
 +  Was ist ABC (Automatic Baseline Correction)?
 +  --------------------------------------------
 +  - Sensor nimmt an: niedrigster CO2-Wert in 24h = Frischluft
 +  - Passt Referenz automatisch an
 +  - Problem: In geschlossenen Räumen ohne Lüftung funktioniert das nicht
 +  - Kann zu falschen Kalibrierungen führen (z.B. 800 ppm als "Frischluft")
 +  - Deshalb: ABC für manuelle Kalibrierung ausschalten!
 + *  
 + *  
 +  Zero Point Calibration:
 +  -----------------------
 +  - Manuelle Kalibrierung auf bekannten Referenzwert
 +  - Frischluft hat ca. 420 ppm CO2 (weltweiter Durchschnitt)
 +  - Sensor speichert diesen Wert als neue Referenz
 +  - Wichtig: Muss wirklich in Frischluft durchgeführt werden!
 + *  
 + *  
 +  Wartezeit 20 Minuten:
 +  ---------------------
 +  - Sensor braucht Zeit zur Stabilisierung
 +  - Temperatur muss sich angleichen
 +  - Interne Elektronik muss sich einpendeln
 +  - Erste Messwerte sind oft ungenau
 + *  
 + *  
 +  Typische CO2-Werte:
 +  -------------------
 +  400-420 ppm:  Frischluft (Außenluft)
 +  400-600 ppm:  Sehr gute Raumluft
 +  600-1000 ppm: Normale Raumluft
 +  1000-1500 ppm: Mäßig, Lüften empfohlen
 +  > 1500 ppm:   Schlecht, dringend lüften!
 +  > 2000 ppm:   Gesundheitlich bedenklich
 + *  
 + *  
 +  Fehlersuche:
 +  ------------
 +  - "Lesefehler": Verkabelung prüfen (TX/RX vertauscht?)
 +  - Wert ändert sich nicht: ABC ist noch aktiv oder Sensor defekt
 +  - Zu niedrige Werte: In Innenraum kalibriert statt in Frischluft
 +  - Zu hohe Werte: Sensor muss neu kalibriert werden
 + *  
 + ********************************************************************************************/
 +</code>
 +===== LDR =====
 +<code>
 +/********************************************************************************************
 +  LDR Test - Lichtsensor (Light Dependent Resistor)
 + *  
 +  Verdrahtung:
 +  - Analog Pin: A0
 +  - LDR in Spannungsteiler-Schaltung:
 +    - 3.3V → LDR → A0 → Widerstand (z.B. 10k) → GND
 +    oder
 +    - 3.3V → Widerstand → A0 → LDR → GND
 + *  
 +  Hinweis: Im Hauptcode ist invertiert (hell=klein, dunkel=groß)
 + ********************************************************************************************/
 +
 +#define LDR_PIN A0
 +
 +void setup() {
 +  Serial.begin(115200);
 +  delay(500);
 +  Serial.println("\n=== LDR Helligkeitssensor Test ===");
 +  Serial.println("\nZeigt Rohwert (0-1023) und invertiert wie im Hauptcode\n");
 +  
 +  pinMode(LDR_PIN, INPUT);
 +  
 +  delay(1000);
 +}
 +
 +void loop() {
 +  // Rohwert auslesen (0-1023)
 +  int rawValue = analogRead(LDR_PIN);
 +  
 +  // Invertiert wie im Hauptcode (450 - raw)
 +  // → hell wird zu kleinem Wert, dunkel zu großem Wert
 +  int inverted = 450 - rawValue;
 +  
 +  // Bargraph für Rohwert erstellen
 +  Serial.print("Rohwert: ");
 +  printBar(rawValue, 1023);
 +  Serial.print(" ");
 +  Serial.print(rawValue);
 +  Serial.print(" / 1023");
 +  
 +  // Interpretation
 +  if (rawValue > 800) {
 +    Serial.print(" [SEHR HELL]");
 +  } else if (rawValue > 500) {
 +    Serial.print(" [Hell]");
 +  } else if (rawValue > 200) {
 +    Serial.print(" [Mittel]");
 +  } else if (rawValue > 50) {
 +    Serial.print(" [Dunkel]");
 +  } else {
 +    Serial.print(" [SEHR DUNKEL]");
 +  }
 +  Serial.println();
 +  
 +  // Invertierter Wert (wie er an EmonCMS gesendet wird)
 +  Serial.print("Invertiert (für EmonCMS): ");
 +  Serial.println(inverted);
 +  
 +  Serial.println("---");
 +  delay(500);  // Schnelles Update zum Testen
 +}
 +
 +// Hilfsfunktion: Bargraph im Serial Monitor
 +void printBar(int value, int maxValue) {
 +  int barLength = map(value, 0, maxValue, 0, 40);
 +  Serial.print("[");
 +  for (int i = 0; i < 40; i++) {
 +    if (i < barLength) {
 +      Serial.print("=");
 +    } else {
 +      Serial.print(" ");
 +    }
 +  }
 +  Serial.print("]");
 +}
 +</code>
 +
 +<code
 +18:47:21.985 -> ---
 +18:47:22.468 -> Rohwert: [===                                     ] 69 / 1023 [Dunkel]
 +18:47:22.501 -> Invertiert (für EmonCMS): 381
 +18:47:22.501 -> ---
 +</code>
 +
 +===== LED-Band =====
 +===== LED-Band =====
 +<code>
 +/********************************************************************************************
 +  LED-Band Test - NeoPixel / WS2812B LED-Streifen (CO₂-Ampel)
 + *  
 +  Verdrahtung:
 +  - DATA: D4 (GPIO2)
 +  - VCC: 5V (für volle Helligkeit, 3.3V funktioniert auch mit reduzierter Leistung)
 +  - GND: GND
 + *  
 +  Hinweis: 5 LEDs, wie im Hauptcode definiert
 + ********************************************************************************************/
 +
 +#include <Adafruit_NeoPixel.h>
 +
 +#define LED_PIN          // D4
 +#define NUM_LEDS  5
 +
 +Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
 +
 +void setup() {
 +  Serial.begin(115200);
 +  delay(500);
 +  Serial.println("\n=== LED-Band NeoPixel Test ===");
 +  Serial.println("5 LEDs an D4 (GPIO2)\n");
 +  
 +  strip.begin();
 +  strip.show();  // Alle LEDs aus
 +  
 +  Serial.println("Test-Sequenz startet...\n");
 +}
 +
 +void loop() {
 +  // 1. Einzelne LEDs durchgehen (Rot)
 +  Serial.println("Test 1: Einzelne LEDs (Rot)");
 +  strip.clear();
 +  for (int i = 0; i < NUM_LEDS; i++) {
 +    strip.setPixelColor(i, strip.Color(255, 0, 0));
 +    strip.show();
 +    delay(300);
 +  }
 +  delay(1000);
 +  
 +  // 2. Alle LEDs in verschiedenen Farben
 +  Serial.println("Test 2: Alle LEDs - verschiedene Farben");
 +  
 +  Serial.println("  - Rot");
 +  strip.fill(strip.Color(255, 0, 0));
 +  strip.show();
 +  delay(1000);
 +  
 +  Serial.println("  - Grün");
 +  strip.fill(strip.Color(0, 255, 0));
 +  strip.show();
 +  delay(1000);
 +  
 +  Serial.println("  - Blau");
 +  strip.fill(strip.Color(0, 0, 255));
 +  strip.show();
 +  delay(1000);
 +  
 +  Serial.println("  - Gelb");
 +  strip.fill(strip.Color(255, 255, 0));
 +  strip.show();
 +  delay(1000);
 +  
 +  Serial.println("  - Weiß");
 +  strip.fill(strip.Color(255, 255, 255));
 +  strip.show();
 +  delay(1000);
 +  
 +  // 3. CO₂-Ampel Simulation (wie im Hauptcode)
 +  Serial.println("\nTest 3: CO₂-Ampel Simulation");
 +  
 +  Serial.println("  - 600 ppm: BLAU (sehr gut)");
 +  setCO2Color(600);
 +  delay(2000);
 +  
 +  Serial.println("  - 900 ppm: GRÜN (gut)");
 +  setCO2Color(900);
 +  delay(2000);
 +  
 +  Serial.println("  - 1400 ppm: GELB (mittel)");
 +  setCO2Color(1400);
 +  delay(2000);
 +  
 +  Serial.println("  - 2000 ppm: ROT (schlecht)");
 +  setCO2Color(2000);
 +  delay(2000);
 +  
 +  Serial.println("  - 2600 ppm: ALARM (blinken)");
 +  setCO2Color(2600);
 +  delay(3000);
 +  
 +  // 4. Helligkeitstest
 +  Serial.println("\nTest 4: Helligkeit (Rot)");
 +  for (int brightness = 0; brightness <= 255; brightness += 25) {
 +    Serial.print("  Helligkeit: ");
 +    Serial.println(brightness);
 +    strip.setBrightness(brightness);
 +    strip.fill(strip.Color(255, 0, 0));
 +    strip.show();
 +    delay(500);
 +  }
 +  
 +  // 5. Regenbogen-Effekt
 +  Serial.println("\nTest 5: Regenbogen");
 +  rainbowCycle(5);
 +  
 +  Serial.println("\n=== Sequenz beendet, wiederhole... ===\n");
 +  delay(2000);
 +}
 +
 +// CO₂-Ampel Funktion (aus Hauptcode übernommen)
 +void setCO2Color(int co2) {
 +  if (co2 < 700) {
 +    strip.setBrightness(2);
 +    strip.fill(strip.Color(0, 0, 255));  // Blau
 +  } else if (co2 < 1100) {
 +    strip.setBrightness(3);
 +    strip.fill(strip.Color(0, 255, 0));  // Grün
 +  } else if (co2 < 1800) {
 +    strip.setBrightness(4);
 +    strip.fill(strip.Color(255, 255, 0));  // Gelb
 +  } else if (co2 < 2500) {
 +    strip.setBrightness(120);
 +    strip.fill(strip.Color(255, 0, 0));  // Rot
 +  } else {
 +    // Alarm: Rot-Weiß blitzen
 +    for (int i = 0; i < 5; i++) {
 +      strip.setBrightness(255);
 +      strip.fill(strip.Color(255, 0, 0));
 +      strip.show();
 +      delay(200);
 +      strip.fill(strip.Color(255, 255, 255));
 +      strip.show();
 +      delay(200);
 +      strip.clear();
 +      strip.show();
 +      delay(200);
 +    }
 +    strip.setBrightness(120);
 +    strip.fill(strip.Color(255, 0, 0));
 +    strip.show();
 +    return;
 +  }
 +  strip.show();
 +}
 +
 +// Regenbogen-Effekt
 +void rainbowCycle(int cycles) {
 +  for (int j = 0; j < 256 * cycles; j++) {
 +    for (int i = 0; i < NUM_LEDS; i++) {
 +      strip.setPixelColor(i, Wheel((i * 256 / NUM_LEDS + j) & 255));
 +    }
 +    strip.show();
 +    delay(5);
 +  }
 +}
 +
 +// Hilfsfunktion für Regenbogen (Farbrad)
 +uint32_t Wheel(byte WheelPos) {
 +  WheelPos = 255 - WheelPos;
 +  if (WheelPos < 85) {
 +    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
 +  }
 +  if (WheelPos < 170) {
 +    WheelPos -= 85;
 +    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
 +  }
 +  WheelPos -= 170;
 +  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
 +}
 +</code>
  
QR-Code
QR-Code www-messdatenerfassung-sas:platinenschaltung (erstellt für aktuelle Seite)