Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen RevisionVorhergehende Überarbeitung
www-messdatenerfassung-sas:sensor_code [2026/10/19 19:59] – [Programmcode der Sensor-Boxen] bieblwww-messdatenerfassung-sas:sensor_code [2026/10/19 20:01] (aktuell) – biebl
Zeile 1533: Zeile 1533:
   strip.show();   strip.show();
 } }
 +</code>
 +
 +----
 +
 +===== Temperatursensor Innen/Außen (2× DS18B20) =====
 +
 +Zwei DS18B20-Temperatursensoren an einem gemeinsamen Datenpin (1-Wire-Bus): einer misst die Innentemperatur (''Ti_C''), einer die Außentemperatur (''Ta_C''). Nützlich z. B. für den Vergleich zwischen Raum und Außenluft.
 +
 +**Verkabelung:**
 +
 +^ Anschluss ^ Verbunden mit ^ Hinweis ^
 +| DS18B20 VCC (beide) | 3,3V oder 5V | |
 +| DS18B20 GND (beide) | GND | |
 +| DS18B20 DATA (beide) | D2 (GPIO4) | Beide Sensoren parallel an denselben Pin |
 +| 4,7 kΩ Widerstand | zwischen DATA und VCC | Pull-Up, nur **ein** Widerstand für beide Sensoren |
 +
 +**Sensor-Adressen ermitteln:** Jeder DS18B20 hat eine feste, einmalige 8-Byte-Adresse. Den Sketch zuerst mit den Beispieladressen hochladen und den seriellen Monitor (115200 Baud) öffnen – beim Start werden alle am Bus gefundenen Sensoren mit ihrer Adresse ausgegeben (Zeilen wie ''Sensor #0: 28:04:EE:71:00:00:00:9D → UNBEKANNT''). Diese Adressen anschließend bei ''SENSOR_ADDR_INNEN'' und ''SENSOR_ADDR_AUSSEN'' eintragen (Format: ''28:04:EE:71:00:00:00:9D'' wird zu ''{0x28, 0x04, 0xEE, 0x71, 0x00, 0x00, 0x00, 0x9D}''). Welcher Sensor innen und welcher außen sitzt, lässt sich durch kurzes Erwärmen eines Sensors in der Hand erkennen.
 +
 +**Messintervall:** koppelt sich automatisch an den gewählten Netzwerk-Bereich – Bereich A (mobil/Test) = 10 Sekunden, Bereich B/C (Schulnetz) = 20 Sekunden. Der EmonCMS-Feed muss mit dem passenden Interval angelegt werden (10 bzw. 20).
 +
 +**Gesendete Felder:** ''Ti_C'' (Innen) und ''Ta_C'' (Außen), in °C. Der Wert ''-127'' bedeutet „Sensor nicht lesbar“.
 +
 +<code cpp>
 +/********************************************************************************************
 + *  D1 mini + 2 × DS18B20 – Innen/Außen Temperatur (statische IP, selbstheilend)
 + *
 + *  Einstellen:
 + *   - #define SENSOR_ID 207  → Clientname "207" & IP 192.168.1.207
 + *   - Netzwerk-Auswahl (Bereich A/B/C) weiter unten: genau EINE Zeile auf true
 + *   - SENSOR_ADDR_INNEN und SENSOR_ADDR_AUSSEN mit echten Adressen füllen
 + *
 + *  Messung:
 + *   - beide Temperaturen lesen, Intervall koppelt sich an den Netzwerk-Bereich:
 + *     Bereich A (mobil/Test) = 10 s, Bereich B/C (Schulnetz) = 20 s
 + *   - als JSON { "Ti_C": <°C>, "Ta_C": <°C> } an EmonCMS senden
 + *   - Ti_C = Temperatur Innen (indoor), Ta_C = Temperatur Außen (ambient)
 + *
 + *  Verdrahtung:
 + *   - Beide Sensoren parallel an D2
 + *   - VCC → 5V (oder 3V3), GND → GND, DATA → D2
 + *   - 4,7 kΩ Pull-Up zwischen DATA und VCC (nur einer für beide!)
 + ********************************************************************************************/
 +
 +#include <ESP8266WiFi.h>
 +#include <ESP8266HTTPClient.h>
 +#include <WiFiClient.h>
 +#include <OneWire.h>
 +#include <DallasTemperature.h>
 +
 +/*** 1) Identität **************************************************************************/
 +#define SENSOR_ID 207                    // eindeutige Sensor-Nummer → IP .207
 +
 +/*** 2) Netzwerk ***************************************************************************/
 +// Aus SENSOR_ID werden Client-Name ("207") & IP 192.168.1.207:
 +char      CLIENT[8];
 +IPAddress ip(192, 168, 1, SENSOR_ID);
 +
 +IPAddress SERVER_IP(192, 168, 1, 2);     // EmonCMS-Server (gilt für alle drei Netzwerkprofile)
 +const int httpPort = 80;
 +
 +// ============================================================
 +//  NETZWERK-AUSWAHL - genau EINE der drei Zeilen auf "true" setzen
 +// ============================================================
 +#define NETZ_A_MOBIL  true   // SAS-ARDUINO-mobil (persönliches Testnetz) 10 s
 +#define NETZ_B_TEST   false  // SAS-ARDUINO, Schul-Testbetrieb (TEST-API-Key) 20 s
 +#define NETZ_C_LIVE   false  // SAS-ARDUINO, Schul-Livebetrieb (LIVE-API-Key) 20 s
 +
 +#if (NETZ_A_MOBIL + NETZ_B_TEST + NETZ_C_LIVE) != 1
 +  #error "Bitte genau EINE der drei NETZ_-Zeilen oben auf true setzen!"
 +#endif
 +
 +// ---- BEREICH A: SAS-ARDUINO-mobil (persönliches Testnetz) -------------
 +// Vor Weitergabe des Codes an andere Schulen DIESEN BLOCK LÖSCHEN!
 +#if NETZ_A_MOBIL
 +  const char* ssid     = "SAS-ARDUINO-mobil";
 +  const char* password = "<MOBIL-WLAN-PASSWORT>";
 +  const char* apikey   = "<MOBIL-API-KEY>";
 +#endif
 +
 +// ---- BEREICH B: SAS-ARDUINO, Schul-Testbetrieb -------------------------
 +#if NETZ_B_TEST
 +  const char* ssid     = "SAS-ARDUINO";
 +  const char* password = "<SCHUL-WLAN-PASSWORT>";
 +  const char* apikey   = "<TEST-API-KEY>";
 +#endif
 +
 +// ---- BEREICH C: SAS-ARDUINO, Schul-Livebetrieb --------------------------
 +#if NETZ_C_LIVE
 +  const char* ssid     = "SAS-ARDUINO";
 +  const char* password = "<SCHUL-WLAN-PASSWORT>";
 +  const char* apikey   = "<LIVE-API-KEY>";
 +#endif
 +
 +IPAddress gateway(192, 168, 1, 1);
 +IPAddress subnet (255, 255, 255, 0);
 +IPAddress dns    (0,   0,   0,   0);
 +static bool g_lastSendFailed = false;    // für die WLAN-Verbindungsqualität
 +
 +/*** 3) Intervall **************************************************************************/
 +// Pause am Ende jedes Zyklus. Messung (ca. 0,8 s) und Senden kommen noch dazu, daher
 +// 1 s weniger als das gewünschte Gesamtintervall: A = 10 s, B/C = 20 s
 +const int SCHREIBPAUSE_MS = NETZ_A_MOBIL ? 9000 : 19000;
 +
 +/*** 4) Sensoren: 2× DS18B20 ***************************************************************/
 +#define ONE_WIRE_BUS D2                  // DATA-Pin für beide Sensoren
 +
 +OneWire oneWire(ONE_WIRE_BUS);
 +DallasTemperature sensors(&oneWire);
 +
 +// WICHTIG: Hier die echten Sensor-Adressen eintragen!
 +// Die Adressen erscheinen beim Start im seriellen Monitor (115200 Baud):
 +// "Sensor #0: 28:04:EE:71:00:00:00:9D → UNBEKANNT (Adresse nicht konfiguriert)"
 +// Format: {0x28, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX}
 +
 +// Sensor INNEN (Ti)
 +DeviceAddress SENSOR_ADDR_INNEN  = {0x28, 0x04, 0xEE, 0x71, 0x00, 0x00, 0x00, 0x9D};
 +
 +// Sensor AUSSEN (Ta)
 +DeviceAddress SENSOR_ADDR_AUSSEN = {0x28, 0x6B, 0x31, 0x7A, 0x00, 0x00, 0x00, 0xDF};
 +
 +// Flags, ob Sensoren gefunden wurden
 +bool sensorInnenOK  = false;
 +bool sensorAussenOK = false;
 +
 +/*** 5) Zustände / Selbstheilung ***********************************************************/
 +static bool     g_uploadedOnce  = false;
 +static uint32_t g_bootMs        = 0;
 +const  uint32_t BOOT_DEADLINE   = 180000; // 3 min: bis dahin 1 Upload
 +const  uint32_t WIFI_DOWN_LIMIT = 120000; // 2 min offline → Neustart
 +const  uint8_t  WIFI_BOOT_TRIES = 3;      // WLAN-Startversuche mit Funk-Reset
 +static uint32_t g_wifiDownSince = 0;
 +
 +/*** 6) Prototypen *************************************************************************/
 +bool ensureWifi(uint32_t timeoutMs = 15000);
 +bool wifiBootConnect();
 +bool netSoftReset(uint32_t connectTimeoutMs = 15000);
 +bool tcpPreflight(uint16_t port = 80, uint16_t graceMs = 50);
 +void sendData(float tempInnen, float tempAussen);
 +void initSensors();
 +
 +/*** 7) SETUP ******************************************************************************/
 +void setup() {
 +  Serial.begin(115200);
 +  delay(150);
 +  snprintf(CLIENT, sizeof(CLIENT), "%d", SENSOR_ID);
 +  Serial.printf("\n=== Boot: 2x DS18B20 Innen/Außen ===\nSensor-ID=%s  IP=%s\n",
 +                CLIENT, ip.toString().c_str());
 +
 +  // WLAN-Start: 3 saubere Versuche mit Funk-Reset & Backoff
 +  if (!wifiBootConnect()) {
 +    Serial.println("[BOOT] WLAN nicht erreichbar → Neustart");
 +    delay(800);
 +    ESP.restart();
 +  }
 +
 +  // Beide DS18B20 Sensoren initialisieren
 +  initSensors();
 +
 +  delay(250);
 +
 +  // Startverzögerung (0..Schreibpause) entkoppelt Massenstart bei vielen Geräten
 +  randomSeed(ESP.getChipId());
 +  delay(random(0, SCHREIBPAUSE_MS));
 +
 +  g_bootMs = millis();
 +}
 +
 +/*** 8) LOOP ********************************************************************************/
 +void loop() {
 +  // Beide Sensoren gleichzeitig messen lassen
 +  sensors.requestTemperatures();
 +  delay(800);  // Warten auf 12-bit Konvertierung (~750ms)
 +
 +  // Temperatur INNEN auslesen
 +  float tempInnen = -127.0;  // Standard-Fehlerwert
 +  if (sensorInnenOK) {
 +    tempInnen = sensors.getTempC(SENSOR_ADDR_INNEN);
 +    if (tempInnen == DEVICE_DISCONNECTED_C || isnan(tempInnen) || tempInnen == -127.0) {
 +      tempInnen = -127.0;  // "ungültig"
 +      Serial.println("[SENSOR] Innen-Sensor: Lesefehler!");
 +    }
 +  }
 +
 +  // Temperatur AUSSEN auslesen
 +  float tempAussen = -127.0;  // Standard-Fehlerwert
 +  if (sensorAussenOK) {
 +    tempAussen = sensors.getTempC(SENSOR_ADDR_AUSSEN);
 +    if (tempAussen == DEVICE_DISCONNECTED_C || isnan(tempAussen) || tempAussen == -127.0) {
 +      tempAussen = -127.0;  // "ungültig"
 +      Serial.println("[SENSOR] Außen-Sensor: Lesefehler!");
 +    }
 +  }
 +
 +  Serial.printf("[MESSUNG] Ti=%.1f°C  Ta=%.1f°C\n", tempInnen, tempAussen);
 +
 +  // WLAN pflegen & ggf. reconnecten
 +  ensureWifi(10000);
 +
 +  // Beide Werte an EmonCMS senden
 +  sendData(tempInnen, tempAussen);
 +
 +  // Selbstheilung / Watchdog
 +  uint32_t now = millis();
 +  if (!g_uploadedOnce && (now - g_bootMs > BOOT_DEADLINE)) {
 +    Serial.println("[WD] Kein Upload binnen 3 min → Reboot");
 +    delay(300); ESP.restart();
 +  }
 +  if (g_wifiDownSince && (now - g_wifiDownSince > WIFI_DOWN_LIMIT)) {
 +    Serial.println("[WD] WLAN > 120 s weg → Reboot");
 +    delay(300); ESP.restart();
 +  }
 +
 +  // Intervall + kleiner Jitter (±0,3 s) für verteilte Last auf dem Server
 +  delay(SCHREIBPAUSE_MS + random(-300, 300));
 +}
 +
 +/*** 9) Sensor-Initialisierung *************************************************************/
 +void initSensors() {
 +  sensors.begin();
 +  sensors.setWaitForConversion(true);
 +
 +  int count = sensors.getDeviceCount();
 +  Serial.printf("Gefundene Sensoren am Bus: %d\n", count);
 +
 +  if (count < 2) {
 +    Serial.println("[WARNUNG] Weniger als 2 Sensoren gefunden!");
 +  }
 +
 +  // Alle gefundenen Sensoren durchgehen und zuordnen
 +  DeviceAddress tempAddr;
 +  for (int i = 0; i < count; i++) {
 +    if (sensors.getAddress(tempAddr, i)) {
 +
 +      // Adresse ausgeben
 +      Serial.printf("Sensor #%d: ", i);
 +      for (uint8_t j = 0; j < 8; j++) {
 +        if (tempAddr[j] < 16) Serial.print("0");
 +        Serial.print(tempAddr[j], HEX);
 +        if (j < 7) Serial.print(":");
 +      }
 +
 +      // Prüfen, ob es der Innen- oder Außen-Sensor ist (Vergleich der kompletten Adresse)
 +      if (memcmp(tempAddr, SENSOR_ADDR_INNEN, 8) == 0) {
 +        Serial.println(" → INNEN (Ti)");
 +        sensorInnenOK = true;
 +        sensors.setResolution(tempAddr, 12);  // 12-bit Auflösung
 +
 +      } else if (memcmp(tempAddr, SENSOR_ADDR_AUSSEN, 8) == 0) {
 +        Serial.println(" → AUSSEN (Ta)");
 +        sensorAussenOK = true;
 +        sensors.setResolution(tempAddr, 12);  // 12-bit Auflösung
 +
 +      } else {
 +        Serial.println(" → UNBEKANNT (Adresse nicht konfiguriert)");
 +      }
 +    }
 +  }
 +
 +  Serial.println();
 +  if (sensorInnenOK && sensorAussenOK) {
 +    Serial.println("✓ Beide Sensoren (Innen + Außen) gefunden und konfiguriert");
 +  } else {
 +    if (!sensorInnenOK)  Serial.println("✗ FEHLER: Innen-Sensor nicht gefunden!");
 +    if (!sensorAussenOK) Serial.println("✗ FEHLER: Außen-Sensor nicht gefunden!");
 +    Serial.println("   → Sensor-Adressen im Code prüfen!");
 +  }
 +  Serial.println();
 +}
 +
 +/*** 10) WLAN-Helfer ***********************************************************************/
 +
 +// Mehrstufiger WLAN-Boot: Funk hart zurücksetzen, 3 Versuche mit Backoff
 +bool wifiBootConnect() {
 +  WiFi.persistent(false);
 +  WiFi.mode(WIFI_STA);
 +  WiFi.setAutoConnect(true);
 +  WiFi.setAutoReconnect(true);
 +  WiFi.setOutputPower(20.5);
 +  WiFi.setSleepMode(WIFI_NONE_SLEEP);
 +
 +  for (uint8_t attempt = 1; attempt <= WIFI_BOOT_TRIES; ++attempt) {
 +    Serial.printf("[BOOT] WLAN-Versuch %u/%u\n", attempt, WIFI_BOOT_TRIES);
 +
 +    // Funkzustand hart resetten
 +    WiFi.disconnect(true);
 +    delay(200);
 +    WiFi.forceSleepBegin(); delay(200);
 +    WiFi.forceSleepWake();  delay(200);
 +
 +    // Statische IP setzen & verbinden
 +    WiFi.config(ip, gateway, subnet, dns);
 +    WiFi.begin(ssid, password);
 +
 +    if (ensureWifi(20000)) {
 +      WiFiClient poke; poke.connect(gateway, 80); poke.stop();  // ARP im Router "anticken"
 +      return true;
 +    }
 +
 +    // Backoff (1.0–3.0 s) vor nächstem Versuch – verhindert Synchronsturm
 +    int backoff = 1000 + random(0, 2000);
 +    Serial.printf("[BOOT] fehlgeschlagen → Backoff %d ms\n", backoff);
 +    delay(backoff);
 +  }
 +  return false;
 +}
 +
 +// Reconnect im Betrieb; merkt Offline-Startzeit für Watchdog
 +bool ensureWifi(uint32_t timeoutMs) {
 +  if (WiFi.status() == WL_CONNECTED) return true;
 +  Serial.print("[WIFI] Verbinde");
 +  uint32_t start = millis();
 +  while (WiFi.status() != WL_CONNECTED && millis() - start < timeoutMs) {
 +    delay(250); Serial.print(".");
 +  }
 +  if (WiFi.status() == WL_CONNECTED) {
 +    Serial.printf("\n[WIFI] OK: %s (RSSI:%d)\n",
 +                  WiFi.localIP().toString().c_str(), WiFi.RSSI());
 +    g_wifiDownSince = 0;
 +    return true;
 +  }
 +  Serial.println("\n[WIFI] Timeout");
 +  if (!g_wifiDownSince) g_wifiDownSince = millis();
 +  return false;
 +}
 +
 +// Sanfte Netzwerk-Kur: Radio kurz aus/an + frische Assoziation mit statischer IP
 +bool netSoftReset(uint32_t connectTimeoutMs) {
 +  Serial.println("[NET] Soft-Reset: disconnect + radio cycle");
 +  WiFi.disconnect(true);
 +  delay(200);
 +  WiFi.forceSleepBegin();
 +  delay(200);
 +  WiFi.forceSleepWake();
 +  delay(200);
 +  WiFi.config(ip, gateway, subnet, dns);
 +  WiFi.begin(ssid, password);
 +  return ensureWifi(connectTimeoutMs);
 +}
 +
 +// Kurzer TCP-Preflight: baut eine nackte Verbindung zum Server auf und schließt sie wieder.
 +bool tcpPreflight(uint16_t port, uint16_t graceMs) {
 +  WiFiClient poke;
 +  if (!poke.connect(SERVER_IP, port)) return false;
 +  delay(graceMs);
 +  poke.stop();
 +  return true;
 +}
 +
 +/*** 11) Upload an EmonCMS *****************************************************************/
 +void sendData(float tempInnen, float tempAussen) {
 +  if (WiFi.status() != WL_CONNECTED) {
 +    Serial.println("[HTTP] offline – Upload übersprungen");
 +    g_lastSendFailed = true;
 +    return;
 +  }
 +
 +  // POST zu /emoncms/input/post.json?node=...&apikey=...
 +  String path  = "/emoncms/input/post.json";
 +  String query = "?node=" + String(CLIENT) + "&apikey=" + String(apikey);
 +
 +  // JSON mit BEIDEN Temperaturen (eine Nachkommastelle):
 +  // Ti_C = Temperatur Innen, Ta_C = Temperatur Außen
 +  String body = "json={\"Ti_C\":" + String(tempInnen, 1) +
 +                ",\"Ta_C\":"      + String(tempAussen, 1) + "}";
 +
 +  // Preflight nach Fehlschlag
 +  if (g_lastSendFailed) {
 +    bool ok = tcpPreflight(httpPort, 80);
 +    Serial.printf("[NET] Preflight %s\n", ok ? "OK" : "FAIL");
 +  }
 +
 +  String hostStr = SERVER_IP.toString();
 +
 +  auto doPostOnce = [&](int attempt) -> int {
 +    if (WiFi.status() != WL_CONNECTED) {
 +      ensureWifi(8000);
 +      if (WiFi.status() != WL_CONNECTED) {
 +        Serial.printf("[HTTP] WLAN down (Try %d) – skip\n", attempt);
 +        return -10000;
 +      }
 +    }
 +
 +    WiFiClient client;
 +    client.setNoDelay(true);
 +    client.setTimeout(15000);
 +
 +    HTTPClient http;
 +    http.setTimeout(15000);
 +    http.setReuse(false);
 +
 +    if (!http.begin(client, hostStr, httpPort, path + query, false)) {
 +      Serial.println("[HTTP] begin() failed");
 +      return -2;
 +    }
 +
 +    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
 +    http.addHeader("Connection", "close");
 +
 +    int code = http.POST(body);
 +    String err = http.errorToString(code);
 +    String payload = (code > 0) ? http.getString() : String();
 +    http.end();
 +
 +    Serial.printf("[HTTP] Try %d → code=%d (%s)\n", attempt, code, err.c_str());
 +    if (code == 200) {
 +      if (payload.length()) { Serial.print("[HTTP] resp: "); Serial.println(payload); }
 +      Serial.printf("Sent: Ti=%.1f°C Ta=%.1f°C\n", tempInnen, tempAussen);
 +      g_uploadedOnce   = true;
 +      g_lastSendFailed = false;  // Erfolg → Flag löschen
 +    }
 +    return code;
 +  };
 +
 +  // 3 normale Versuche
 +  int code = -9999;
 +  for (int attempt = 1; attempt <= 3; ++attempt) {
 +    code = doPostOnce(attempt);
 +    if (code == 200) return;
 +    delay(250 * attempt);
 +  }
 +
 +  // Soft-Reset + 4. Versuch
 +  if (netSoftReset(15000)) {
 +    code = doPostOnce(4);
 +    if (code == 200) return;
 +  }
 +
 +  // Zweiter Soft-Reset + 5. Versuch
 +  Serial.println("[NET] Soft-Reset #2 …");
 +  if (netSoftReset(15000)) {
 +    code = doPostOnce(5);
 +    if (code == 200) return;
 +  }
 +
 +  g_lastSendFailed = true;  // alles fehlgeschlagen
 +  Serial.printf("[HTTP] FAILED after retries | Node=%s | Ti=%.1f Ta=%.1f\n",
 +                CLIENT, tempInnen, tempAussen);
 +}
 +
 +/********************************************************************************************
 + *  HINWEISE
 + ********************************************************************************************
 + *  SENSOR-ADRESSEN ERMITTELN:
 + *  1. Sketch mit den Beispieladressen hochladen
 + *  2. Serieller Monitor (115200 Baud) zeigt beim Start alle Adressen am Bus, z. B.
 + *       Sensor #0: 28:04:EE:71:00:00:00:9D → UNBEKANNT (Adresse nicht konfiguriert)
 + *  3. Eintragen als:
 + *       DeviceAddress SENSOR_ADDR_INNEN  = {0x28, 0x04, 0xEE, 0x71, 0x00, 0x00, 0x00, 0x9D};
 + *
 + *  EMONCMS: Es erscheinen die Inputs Ti_C und Ta_C. Für jeden einen Feed anlegen
 + *  (PHPFina, Interval 10 s bei Bereich A bzw. 20 s bei Bereich B/C).
 + *
 + *  FEHLERSUCHE:
 + *  "UNBEKANNT (Adresse nicht konfiguriert)": Adressen im Code stimmen nicht mit den echten überein
 + *  "Lesefehler!": Wackelkontakt, Sensor während der Messung getrennt oder defekt
 + *  "Weniger als 2 Sensoren gefunden": Verkabelung und Pull-Up-Widerstand prüfen,
 + *                                     beide Sensoren angeschlossen?
 + ********************************************************************************************/
 </code> </code>
  
QR-Code
QR-Code www-messdatenerfassung-sas:sensor_code (erstellt für aktuelle Seite)