Zum Inhalt

Sensoren

Auf dieser Seite verbinden Sie zwei Sensoren und geben ihre Daten auf dem Portal aus. Zunächst SHT31 (Schrank-Klima), dann Thermistor (Heizer-Temperatur). Dies ist der Schritt „Daten erhalten" vor dem Hinzufügen der Steuerlogik.

Das Prinzip der Arbeit mit dem Kern ist einfach: Ihr Code in loop() schreibt frische Messwerte in die Felder s_link.telemetry.*, und die Fassade veröffentlicht sie automatisch jeden telemetryPeriodMs aus Config in die Cloud. Ein manueller Aufruf der Veröffentlichung ist nicht erforderlich.

Telemetrie-Felder

Für unseren Schrank werden drei Felder verwendet (Index [0] — erste und einzige Kammer):

Feld Speicherinhalt Flag in Config
s_link.telemetry.airTempC[0] Lufttemperatur, °C hasAirTemp
s_link.telemetry.airHumidityPct[0] Luftfeuchte, % hasAirHumidity
s_link.telemetry.heaterTempC[0] Heizer-Temperatur, °C hasHeaterTemp

Diese drei Flags werden genau hier in Config aktiviert (siehe das vollständige Listing am Ende des Kapitels). Das Flag teilt Portal und App mit, dass das Gerät einen solchen Sensor hat: ohne es erscheint die Zelle auf der Karte nicht.

Regel: Sensoren-Code darf loop() nicht blockieren

Die idryer-core Fassade bedient Wi-Fi und MQTT in derselben loop(). Daher können Sie beim Auslesen von Sensoren nicht delay() aufrufen — eine Pause unterbricht die Netzsitzung. Der Sensor wird nach einem Timer abgefragt, und der fertige Wert wird einfach gelesen. Fertige Treiber aus dem Ökosystem sind bereits so konstruiert.

Schritt 1. SHT31: Schrank-Klima

Es ist nicht nötig, den SHT31-Treiber von Grund auf zu schreiben — die fertige Klasse Sht31ClimateSensor liegt im Beispiel dieses Kapitels, example/09-cabinet. Sie verwendet die robtillaart/SHT31 Bibliothek und liest den Sensor blockierungsfrei.

  1. Fügen Sie die SHT31-Bibliothek in lib_deps Ihrer platformio.ini hinzu:

    lib_deps =
        robtillaart/SHT31 @ ^0.5.0
    
  2. Kopieren Sie vier Treiber-Dateien in Ihren src/-Ordner:

    git clone https://github.com/pavluchenkor/Build-Your-Own-iDryer.git ~/byo-idryer
    cp ~/byo-idryer/example/09-cabinet/src/{Sht31ClimateSensor.h,Sht31ClimateSensor.cpp,IClimateSensor.h,sensor_reading.h} src/
    
  3. Verbinden Sie den Sensor über I2C (siehe Schaltplan) und lesen Sie ihn in src/main.cpp:

#include <Wire.h>
#include <iDryer.h>
#include "Sht31ClimateSensor.h"

static Sht31ClimateSensor s_climate(&Wire);
static bool               s_climateOk = false;

void setup() {
    Serial.begin(115200);
    Wire.begin(8, 9);                 // SDA, SCL — Pins Ihres Boards
    s_climateOk = s_climate.begin();  // findet die Adresse 0x44 oder 0x45 selbst
    s_link.begin();
    // Gerät im Portal entkoppelt: Geheimnis löschen, auf neue Kopplung warten.
    s_link.onCommand("revoke", [](JsonObjectConst) { s_link.handleRevoke(); });
}

void loop() {
    s_link.loop();

    if (s_climateOk) {
        s_climate.tick(millis());
        SensorReading r = s_climate.get();
        if (r.ok) {
            s_link.telemetry.airTempC[0]       = r.temperature;
            s_link.telemetry.airHumidityPct[0] = r.humidity;
        }
    }
}

Eine einzelne Messwertaufnahme gibt der Treiber als Struktur SensorReading aus sensor_reading.h zurück:

struct SensorReading {
    float    temperature = NAN;   // °C, NAN wenn es keinen Wert gibt
    float    humidity    = NAN;   // % RH, NAN wenn es keinen Wert gibt
    float    pressure    = NAN;   // hPa, für künftige Sensoren
    uint32_t ts_ms       = 0;     // millis() zum Zeitpunkt des Lesens
    bool     ok          = false; // true, wenn Temperatur und Feuchte gültig sind
    int      err         = 0;     // Fehlercode, 0 — kein Fehler
};

Nach dem Firmware-Upload werden Temperatur und Feuchte des Schranks auf dem Portal angezeigt — dies ist die erste Rückmeldung vom Gerät.

Schritt 2. Thermistor: Heizer-Temperatur

Es gibt keine fertige Thermistor-Klasse für den ESP32, daher schreiben wir sie direkt in src/main.cpp. Der Thermistor ist an einem ADC-Pin über einen Spannungswandler angeschlossen (siehe Schaltplan): Der Controller misst die Spannung an der mittleren Leitung, berechnet daraus den Thermistor-Widerstand und dann die Temperatur.

#include <math.h>

static const int   THERM_PIN  = 2;         // ADC-Pin
static const float SERIES_R   = 4700.0f;   // Teiler-Widerstand, Ohm
static const float NOMINAL_R  = 100000.0f; // Thermistor-Widerstand bei 25 °C, Ohm
static const float NOMINAL_T  = 25.0f;     // °C
static const float BETA       = 3950.0f;   // B-Koeffizient aus dem Thermistor-Datenblatt

// Gibt die Heizer-Temperatur in °C zurück.
static float readHeaterTempC() {
    int   raw = analogRead(THERM_PIN);          // 0..4095 auf ESP32
    float v   = (float)raw / 4095.0f;           // Bruchteil der Gesamtskala
    float r   = SERIES_R * (1.0f - v) / v;      // Thermistor-Widerstand, Ohm
    // Steinhart-Hart-Gleichung in der B-Parameter-Form — siehe Wikipedia:
    // https://en.wikipedia.org/wiki/Steinhart%E2%80%93Hart_equation
    float tK  = 1.0f / (1.0f / (NOMINAL_T + 273.15f) + logf(r / NOMINAL_R) / BETA);
    return tK - 273.15f;
}

Schreiben Sie das Ergebnis in loop() in die Telemetrie neben der SHT31-Ablesung:

s_link.telemetry.heaterTempC[0] = readHeaterTempC();

Dies ist vereinfachtes Auslesen — passen Sie die Parameter an Ihren Thermistor an

Die Konstanten NOMINAL_R und BETA hängen vom spezifischen Thermistor ab — nehmen Sie sie aus dem Datenblatt (ein verbreiteter gewöhnlicher Thermistor — Generic 3950, 100 kΩ). Die Teiler-Formel entspricht der Schaltung aus Schaltplan: Thermistor auf 3.3V, Widerstand auf GND. Bei anderer Verdrahtung ändert sich die Formel. Der ADC auf dem ESP32 ist nichtlinear, daher werden die Messwerte für genaue Messungen kalibriert — in Serie iDryer-Controllern wird dafür eine Thermistor-Tabelle verwendet (Bibliothek Thermistor).

Überprüfung des Thermistors mit einem Multimeter — Thermistor überprüfen.

Schritt 3. Keine Sensoren zur Hand? Demo-Modus

Den Weg bis zur Karte können Sie auch ohne Hardware gehen: die Messwerte berechnet ein Modell des Schranks. Kopieren Sie die Datei demo_sensors.h aus demselben Beispiel:

cp ~/byo-idryer/example/09-cabinet/src/demo_sensors.h src/

und fügen Sie in platformio.ini das Build-Flag hinzu:

build_flags =
    -DDEMO_SENSORS=1

Beide Zweige stecken hinter einer einzigen Funktion, und loop() weiß nicht, woher die Werte kommen:

static void readSensors() {
#ifdef DEMO_SENSORS
    demoSensors(s_link.telemetry);
#else
    // Auslesen von SHT31 und Thermistor — wie oben
#endif
}

Das Modell verhält sich wie ein echter Schrank: der Raum schwankt langsam um 24 °C, ein eingeschalteter Heizer erwärmt die Luft, ein ausgeschalteter lässt sie abkühlen, beim Aufheizen sinkt die Feuchte. Die Heizleistung liest das Modell aus der Telemetrie, deshalb sieht die Logik aus dem Kapitel Heizungssteuerung eine Reaktion und die Hysterese funktioniert. Die Screenshots in diesem Abschnitt sind genau so entstanden.

Für ein Arbeitsgerät setzt man das Flag nicht: dann wird der Zweig mit den echten Sensoren gebaut.

Vollständig src/main.cpp nach diesem Kapitel

Unten ist die gesamte Datei. Neue Zeilen relativ zum letzten Kapitel sind mit // ← Kapitel 5 gekennzeichnet; der Rest hat sich nicht geändert.

Was bisher da war — src/main.cpp nach Kapitel 4
#include <iDryer.h>

static const iDryer::Config CFG = {
    .deviceType        = iDryer::DeviceType::Unknown,   // eigenes Gerät: die Karte baut das Manifest
    .unitsCount        = 1,
    .telemetryPeriodMs = 5000,
    .statusPeriodMs    = 10000,
    .hardwareVersion   = "1.0",
    .firmwareVersion   = "0.1.0",
    .model             = "DIY Storage Cabinet",
};
static iDryer::Link s_link(CFG);

void setup() {
    Serial.begin(115200);
    s_link.begin();
    // Gerät im Portal entkoppelt: Geheimnis löschen, auf neue Kopplung warten.
    s_link.onCommand("revoke", [](JsonObjectConst) { s_link.handleRevoke(); });
}

void loop() {
    s_link.loop();
}
#include <iDryer.h>
#include <Wire.h>                  // ← Kapitel 5
#include <math.h>                  // ← Kapitel 5
#include "Sht31ClimateSensor.h"    // ← Kapitel 5
#include "demo_sensors.h"    // ← Kapitel 5: Messwerte ohne Sensoren (-DDEMO_SENSORS=1)

static const iDryer::Config CFG = {
    .deviceType        = iDryer::DeviceType::Unknown,   // eigenes Gerät: die Karte baut das Manifest
    .unitsCount        = 1,
    .hasAirTemp        = true,     // ← Kapitel 5
    .hasAirHumidity    = true,     // ← Kapitel 5
    .hasHeaterTemp     = true,  // ← Kapitel 5
    .telemetryPeriodMs = 5000,
    .statusPeriodMs    = 10000,
    .hardwareVersion   = "1.0",
    .firmwareVersion   = "0.1.0",
    .model             = "DIY Storage Cabinet",
};
static iDryer::Link s_link(CFG);

// ← Kapitel 5: SHT31 Klimasensor
static Sht31ClimateSensor s_climate(&Wire);
static bool               s_climateOk = false;

// ← Kapitel 5: Heizer-Thermistor
static const int   THERM_PIN  = 2;
static const float SERIES_R   = 4700.0f;
static const float NOMINAL_R  = 100000.0f;
static const float NOMINAL_T  = 25.0f;
static const float BETA       = 3950.0f;

static float readHeaterTempC() {
    int   raw = analogRead(THERM_PIN);
    float v   = (float)raw / 4095.0f;
    float r   = SERIES_R * (1.0f - v) / v;
    float tK  = 1.0f / (1.0f / (NOMINAL_T + 273.15f) + logf(r / NOMINAL_R) / BETA);
    return tK - 273.15f;
}

// ← Kapitel 5: Sensoren oder, mit -DDEMO_SENSORS=1, das Modell des Schranks
static void readSensors() {
#ifdef DEMO_SENSORS
    demoSensors(s_link.telemetry);
#else
    if (s_climateOk) {
        s_climate.tick(millis());
        SensorReading r = s_climate.get();
        if (r.ok) {
            s_link.telemetry.airTempC[0]       = r.temperature;
            s_link.telemetry.airHumidityPct[0] = r.humidity;
        }
    }
    s_link.telemetry.heaterTempC[0] = readHeaterTempC();
#endif
}

void setup() {
    Serial.begin(115200);
    Wire.begin(8, 9);                 // ← Kapitel 5  (SDA, SCL — Pins Ihres Boards)
    s_climateOk = s_climate.begin();  // ← Kapitel 5
    s_link.begin();
    // Gerät im Portal entkoppelt: Geheimnis löschen, auf neue Kopplung warten.
    s_link.onCommand("revoke", [](JsonObjectConst) { s_link.handleRevoke(); });
}

void loop() {
    s_link.loop();

    readSensors();   // ← Kapitel 5
}

Überprüfung des Ergebnisses

Geräteseite im Portal: Messwerte und Diagramm Messwerte auf der Karte und Telemetrie-Diagramm. Die Heizer-Temperatur läuft als eigene Linie im Diagramm. Das Menü unten auf der Seite ist noch leer — darum kümmert sich das nächste Kapitel.

Nach diesem Schritt sollten drei Werte auf dem Portal angezeigt werden:

  • Lufttemperatur im Schrank;
  • Feuchte im Schrank;
  • Heizer-Temperatur.

Wenn die Messwerte „schwankend" sind oder offensichtlich falsch:

  • überprüfen Sie die Gesamterde und die Verdrahtung (Störungen von Stromkabeln) — Verdrahtungsfehler;
  • überprüfen Sie den Nominalwert des Teiler-Widerstands und den Typ des Thermistors;
  • stellen Sie sicher, dass der SHT31 über I2C antwortet (richtige Adresse und Leitungen).

Diagnose „Sensor zeigt Müll" — Thermistor überprüfen und Typische Fehler.

Was ist als Nächstes

Wir haben Daten von den Sensoren. Jetzt werden wir die Geräteeinstellungen (Zieltemperatur, Hysterese) im Menü aus YAML beschreiben, um sie vom Portal aus ändern und im Speicher speichern zu können.