Dies ist eine alte Version des Dokuments!


WWW: Platinen mit TESTCODE

Seite Inhalt
Startseite Übersicht
Hauptanleitung Gesamtüberblick, Begriffsklärung, Teil 1–3 (FRITZ!Box, Raspberry Pi/EmonCMS, Sensor-Boxen), Ein-/Ausschalten, Zugänge-Checkliste, SENSOR_ID-Übersicht
Sensoren in EmonCMS einbinden Wiederkehrender Ablauf für jede neue Sensor-Box: API-Key, Inputs, Feeds, Dashboard
Programmcode der Sensor-Boxen Vollständiger Arduino-Code: Testsensor, einfacher BME280-Sensor, Klimamessbox, Raummessbox
Raspberry Pi um Echtzeituhr (RTC) erweitern – HW-111 Nachrüstung eines DS1307-RTC-Moduls, Shutdown-Taster
Schaltung Platine
Gehäuse 3D-Druck oder LASER-Cutter
Schule Einsatz im Unterricht
Sonstiges dies und das….
    • Nach Erstellung benötigt die günstigste „Verschiffung“ 2 Wochen (kostet aber unter 5 EURO)
    • Bei unseren Platinen kostete eine Platine 1,5 Euro

Raumklima - Messbox

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 durch den BME280 ersetzt, was den Aufbau und den Code nochmalig vereinfacht.

ohne Spannungsteiler - er ist 5V tolerant

Verkabelung der Klimamessbox, erstellt mit Fritzing

Bestückung

Materialliste

  • auf Vollständigkeit prüfen

1. Drahtbrücke

  • überstehende Reste entfernen

2. Winkelstifte LED und BMP

  • ACHTUNG: auf die genaue Position achten

3. 3er Stifte DHT22

  • auf senkrechte Montage achten

4. 8er Buchsen D1-mini

  • auf senkrechte Montage achten
  • Reste nach dem Löten entfernen

5. 4er- und 5er-Buchse MHZ19B

  • auf senkrechte Montage achten
  • Reste nach dem Löten entfernen

6. Widerstand 470kΩ

  • Reste nach dem Löten entfernen

7. 2er-Buchse LDR

  • auf senkrechte Montage achten
  • Reste nach dem Löten entfernen

8. Externe Sensoren

9. fertiges Modul

Testcode - Raumklima

BMP180

/********************************************************************************************
 *  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);
}
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 -> ---

DHT22

/********************************************************************************************
 *  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
}
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 -> ---

MHZ19B

/********************************************************************************************
 *  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;
}
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 -> ---

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!
/********************************************************************************************
 *  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
 *  
 ********************************************************************************************/

LDR

/********************************************************************************************
 *  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("]");
}
-
 ---
18:47:22.468 -> Rohwert: [===                                     ] 69 / 1023 [Dunkel]
18:47:22.501 -> Invertiert (für EmonCMS): 381
18:47:22.501 -> ---

LED-Band

LED-Band

/********************************************************************************************
 *  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   2        // 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);
}


Klimamessbox

Sensor Pin (GPIO) Anschluss Besonderheit
MH-Z19B GPIO12/14 D6 / D5 Spannungsteiler für D6; SoftwareSerial
SDS011 GPIO13/15 D7 / D8 Spannungsteiler für D7 darf NICHT eingebaut werden, der SDS sendet bereits mit 3,3V Logik; SoftwareSerial
BMP180 GPIO4/5 D2 / D1 I²C (fest: SDA=SDA, SCL=SCL)
LDR ADC0 A0 analog
DHT22 GPIO3 RX Nur nach Upload einstecken!

Di-mini-Platine

ACHTUNG: Für den SDS011 KEINEN Spannungsteiler verwenden, er sendet bereits mit 3,3 V Logik

Sensorplatine

TESTCODE KLIMABOX BME, MHZ und SDS

BME280

#include <Wire.h>
#include <Adafruit_BME280.h>

#define BME280_ADDRESS 0x76    // ggf. 0x77 testen
Adafruit_BME280 bme;

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\nBME280 Test");

  Wire.begin(4, 5);            // SDA = GPIO4, SCL = GPIO5

  if (!bme.begin(BME280_ADDRESS)) {
    Serial.println("BME280 nicht gefunden, Adresse oder Verkabelung prüfen!");
    while (1) delay(100);
  }
  Serial.println("BME280 gefunden.");
}

void loop() {
  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0; // hPa

  Serial.print("Temp: "); Serial.print(t); Serial.println(" °C");
  Serial.print("Feuchte: "); Serial.print(h); Serial.println(" %");
  Serial.print("Druck: "); Serial.print(p); Serial.println(" hPa");
  Serial.println("----");
  delay(2000);
}
18:11:23.194 -> ----
18:11:25.191 -> Temp: 22.28 °C
18:11:25.191 -> Feuchte: 35.97 %
18:11:25.191 -> Druck: 1008.70 hPa
18:11:25.191 -> ----

MHZ-19B

#include <SoftwareSerial.h>

SoftwareSerial mhzSerial(12, 14);  // RX = GPIO12, TX = GPIO14
byte cmdReadCO2[] = {0xFF, 0x01, 0x86, 0, 0, 0, 0, 0, 0x79};

bool readMHZ19B(int &ppm, int &tempC) {
  while (mhzSerial.available()) mhzSerial.read();

  mhzSerial.write(cmdReadCO2, 9);

  uint8_t resp[9];
  int i = 0;
  uint32_t start = millis();
  while (millis() - start < 200) {
    if (mhzSerial.available()) {
      resp[i++] = mhzSerial.read();
      if (i == 9) break;
    }
  }
  if (i != 9) return false;
  if (resp[0] != 0xFF || resp[1] != 0x86) return false;

  uint8_t sum = 0;
  for (int k = 1; k < 8; k++) sum += resp[k];
  sum = 0xFF - sum + 1;
  if (sum != resp[8]) return false;

  ppm   = resp[2] * 256 + resp[3];
  tempC = resp[4] - 40;
  return true;
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\nMH-Z19B Test");

  mhzSerial.begin(9600);
  Serial.println("Sensor heizt auf (ca. 3 Minuten)...");
}

void loop() {
  int ppm, t;
  if (readMHZ19B(ppm, t)) {
    Serial.print("CO2: ");
    Serial.print(ppm);
    Serial.print(" ppm, T-Sensor: ");
    Serial.print(t);
    Serial.println(" °C");
  } else {
    Serial.println("Fehler beim Lesen (noch am Aufwärmen?)");
  }
  delay(2000);
}
18:10:07.806 -> CO2: 674 ppm, T-Sensor: 24 °C
18:10:09.844 -> CO2: 675 ppm, T-Sensor: 24 °C

SDS011

#include <SoftwareSerial.h>

SoftwareSerial sdsSerial(13, 15);  // RX = GPIO13, TX = GPIO15

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\nSDS011 Test");

  sdsSerial.begin(9600);
}

bool readSDS011(float &pm25, float &pm10) {
  uint8_t buf[10];
  int idx = 0;
  uint32_t start = millis();

  while (millis() - start < 2000) {
    if (sdsSerial.available()) {
      uint8_t b = sdsSerial.read();
      if (idx == 0 && b != 0xAA) continue;
      buf[idx++] = b;
      if (idx == 10) {
        if (buf[0] == 0xAA && buf[1] == 0xC0 && buf[9] == 0xAB) {
          pm25 = (buf[2] + 256 * buf[3]) / 10.0;
          pm10 = (buf[4] + 256 * buf[5]) / 10.0;
          return true;
        } else {
          idx = 0;
        }
      }
    }
  }
  return false;
}

void loop() {
  float pm25, pm10;
  if (readSDS011(pm25, pm10)) {
    Serial.print("PM2.5: ");
    Serial.print(pm25);
    Serial.print(" µg/m³, PM10: ");
    Serial.print(pm10);
    Serial.println(" µg/m³");
  } else {
    Serial.println("Kein gültiges SDS011 Paket empfangen.");
  }
  delay(2000);
}
18:29:09.513 -> PM2.5: 4.60 µg/m³, PM10: 8.10 µg/m³
18:29:11.513 -> PM2.5: 4.60 µg/m³, PM10: 8.00 µg/m³

Alle drei Sensoren

#include <Wire.h>
#include <Adafruit_BME280.h>
#include <SoftwareSerial.h>

// BME280 an I2C: SDA = GPIO4 (D2), SCL = GPIO5 (D1)
#define BME280_ADDRESS 0x76
Adafruit_BME280 bme;

// MH-Z19B an GPIO12/14
SoftwareSerial mhzSerial(12, 14);  // RX = 12, TX = 14
byte cmdReadCO2[] = {0xFF, 0x01, 0x86, 0, 0, 0, 0, 0, 0x79};

// SDS011 an GPIO13/15
SoftwareSerial sdsSerial(13, 15);  // RX = 13, TX = 15

byte sds011_sleep[]  = {0xAA, 0xB4, 0x06, 0x01, 0x00,
                        0x00, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00,
                        0xFF, 0xFF, 0x05, 0xAB};
byte sds011_wakeup[] = {0xAA, 0xB4, 0x06, 0x01, 0x01,
                        0x00, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00,
                        0xFF, 0xFF, 0x06, 0xAB};

// ---- Hilfsfunktionen ----

bool readMHZ19B(int &ppm, int &tempC) {
  while (mhzSerial.available()) mhzSerial.read();
  mhzSerial.write(cmdReadCO2, 9);

  uint8_t resp[9];
  int i = 0;
  uint32_t start = millis();
  while (millis() - start < 200) {
    if (mhzSerial.available()) {
      resp[i++] = mhzSerial.read();
      if (i == 9) break;
    }
  }
  if (i != 9) return false;
  if (resp[0] != 0xFF || resp[1] != 0x86) return false;

  uint8_t sum = 0;
  for (int k = 1; k < 8; k++) sum += resp[k];
  sum = 0xFF - sum + 1;
  if (sum != resp[8]) return false;

  ppm   = resp[2] * 256 + resp[3];
  tempC = resp[4] - 40;
  return true;
}

bool readSDS011(float &pm25, float &pm10) {
  uint8_t buf[10];
  int idx = 0;
  uint32_t start = millis();

  while (millis() - start < 2000) {
    if (sdsSerial.available()) {
      uint8_t b = sdsSerial.read();
      if (idx == 0 && b != 0xAA) continue;
      buf[idx++] = b;
      if (idx == 10) {
        if (buf[0] == 0xAA && buf[1] == 0xC0 && buf[9] == 0xAB) {
          pm25 = (buf[2] + 256 * buf[3]) / 10.0;
          pm10 = (buf[4] + 256 * buf[5]) / 10.0;
          return true;
        } else {
          idx = 0;
        }
      }
    }
  }
  return false;
}

void setSDS011Sleep(bool sleep) {
  if (sleep) {
    sdsSerial.write(sds011_sleep, sizeof(sds011_sleep));
  } else {
    sdsSerial.write(sds011_wakeup, sizeof(sds011_wakeup));
  }
  delay(100);
}

// ---- Setup & Loop ----

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\nGesamttest: BME280 + MH-Z19B + SDS011");

  // BME280
  Wire.begin(4, 5);  // SDA, SCL
  if (!bme.begin(BME280_ADDRESS)) {
    Serial.println("BME280 nicht gefunden!");
    while (1) delay(100);
  }
  Serial.println("BME280 OK.");

  // MH-Z19B
  mhzSerial.begin(9600);
  Serial.println("MH-Z19B UART gestartet (9600 Baud).");

  // SDS011
  sdsSerial.begin(9600);
  setSDS011Sleep(false);
  Serial.println("SDS011 aufgeweckt, Lüfter sollte laufen.");
}

void loop() {
  Serial.println("\n--- neuer Messzyklus ---");

  // BME280
  bme.takeForcedMeasurement();
  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0;

  Serial.print("BME280: ");
  Serial.print(t); Serial.print(" °C, ");
  Serial.print(h); Serial.print(" %, ");
  Serial.print(p); Serial.println(" hPa");

  // MH-Z19B
  int ppm, tco2;
  if (readMHZ19B(ppm, tco2)) {
    Serial.print("MH-Z19B: ");
    Serial.print(ppm); Serial.print(" ppm, ");
    Serial.print(tco2); Serial.println(" °C (Sensorsensor)");
  } else {
    Serial.println("MH-Z19B: kein gültiges Paket.");
  }

  // SDS011
  float pm25, pm10;
  if (readSDS011(pm25, pm10)) {
    Serial.print("SDS011: PM2.5=");
    Serial.print(pm25);
    Serial.print(" µg/m³, PM10=");
    Serial.print(pm10);
    Serial.println(" µg/m³");
  } else {
    Serial.println("SDS011: kein gültiges Paket.");
  }

  delay(10000);
}
18:33:44.196 -> --- neuer Messzyklus ---
18:33:44.196 -> BME280: 21.54 °C, 37.46 %, 1008.72 hPa
18:33:44.259 -> MH-Z19B: 673 ppm, 22 °C (Sensorsensor)
18:33:44.259 -> SDS011: PM2.5=4.20 µg/m³, PM10=7.20 µg/m³
QR-Code
QR-Code www-messdatenerfassung-sas:platinenschaltung (erstellt für aktuelle Seite)