Update: 2023-09-24

Holzheizung SL 18K mit Pufferspeicher und Esp8266 Nodemcu Komforterweiterung

Die sieben DS18B20 sind Sternförmig verdrahtet.

HMI_LCD

Die LCD Anzeige im Heizungsraum.

HMI_LCD

Die Anzeige als Chart.

Download Projekt

Heizung_3.5.ino

// ****************************************************************
// Sketch Esp8266 Heizung Modular(Tab)
// created: Jens Fleischer, 2020-02-11
// last mod: Jens Fleischer, 2023-04-08
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 3.1.0 - 3.1.2
// Getestet auf: Nodemcu
/******************************************************************
  Copyright (c) 2020 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Der WebServer Tab ist der Haupt Tab mit "setup" und "loop".
// #include <FS.h> #include <ESP8266WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP8266 Webservers ist erforderlich.
// "server.onNotFound()" darf nicht im Setup des ESP8266 Webserver stehen.
// Inklusive Arduino OTA-Updates (Erfordert freien Flash-Speicher)
/**************************************************************************************/

#include <ESP8266WebServer.h>
#include <ArduinoOTA.h>
#include <LittleFS.h>
#include <DallasTemperature.h>          // https://github.com/milesburton/Arduino-Temperature-Control-Library Version 3.9.1
#include <map>

ESP8266WebServer server(80);

constexpr uint8_t OPERAND {128};
constexpr uint16_t BRIGHTNESS = 850;
bool minmax {true};                     // Merker für Min oder Max // Beim neu aufspielen minmax einstellen. minmax Speicher leer true - Speicher voll false
bool pushStatusAddWood {false};         // Status für Push Nachricht Holz nachlegen
bool releaseAddWood {false};            // Globale Freigabe Holz nachlegen
bool lcdStatus {false};                 // LCD Beleuchtung An/Aus
std::map<uint8_t, String>mixingTime;    // Anzahl und Uhrzeit für Speicher gemischt

//#define DEBUGGING                     // Einkommentieren für die Serielle Ausgabe

#ifdef DEBUGGING
//#define TEMPDEBUG                     // Einkommentieren für die Serielle Temperaturausgabe
#define DEBUG_B(...) Serial.begin(__VA_ARGS__)
#define DEBUG_P(...) Serial.println(__VA_ARGS__)
#define DEBUG_F(...) Serial.printf(__VA_ARGS__)
#else
#define DEBUG_B(...)
#define DEBUG_P(...)
#define DEBUG_F(...)
#endif

struct Ds18b20 {
  DeviceAddress address;
  int16_t currentMin;
  int16_t currentMax;
  int16_t alwaysMin;
  int16_t alwaysMax;
  int16_t current;                      // Aktuelle Temperatur
  char minTime[9];                      // Zeitpunkt für minimale Temperatur
  char maxTime[9];                      // Zeitpunkt für maximale Temperatur
  char alwaysMinTime[20];               // Zeitpunkt für Langzeit Min Temperatur
  char alwaysMaxTime[20];               // Zeitpunkt für Langzeit Max Temperatur
};

struct Collection {
  Ds18b20 KES;                          // Heizkessel
  Ds18b20 HVL;                          // Haus Vorlauf
  Ds18b20 HRL;                          // Haus Rücklauf
  Ds18b20 SWW;                          // Pufferspeicher Warmwasser
  Ds18b20 SVL;                          // Pufferspeicher Vorlauf
  Ds18b20 SRL;                          // Pufferspeicher Rücklauf
  Ds18b20 ATE;                          // Aussentemperatur
};

struct Collection sensors { {{0x28, 0xFF, 0x2E, 0x17, 0x81, 0x16, 0x04, 0x42}, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, 0, "", "", "", ""},
  {{0x28, 0xFF, 0xEA, 0x8C, 0x30, 0x17, 0x04, 0xD9}, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, 0, "", "", "", ""},
  {{0x28, 0xff, 0xd7, 0xfb, 0x24, 0x17, 0x03, 0xc8}, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, 0, "", "", "", ""},
  {{0x28, 0xFF, 0x29, 0x17, 0x81, 0x16, 0x04, 0x13}, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, 0, "", "", "", ""},
  {{0x28, 0xFF, 0x03, 0x18, 0x81, 0x16, 0x04, 0xCC}, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, 0, "", "", "", ""},
  {{0x28, 0xFF, 0xF6, 0x1E, 0x81, 0x16, 0x04, 0xB1}, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, 0, "", "", "", ""},
  {{0x28, 0xff, 0xa3, 0xdc, 0x24, 0x17, 0x03, 0x4c}, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, 0, "", "", "", ""},
};

struct timevalues {
  char minmaxTime[9];
  char fileName[11];
  char timeOfDay[9];
  char timeStamp[20];
} lt;

String sketchName() {                      // Dateiname für den Admin Tab
  char file[sizeof(__FILE__)] = __FILE__;
  char * pos = strrchr(file, '.'); *pos = '\0';
  return file;
}

float convert(int16_t &temp) {
  return static_cast<float>(temp) / OPERAND;
}

void setup() {
  DEBUG_B(115200);
  delay(100);
  DEBUG_F("\n\nSketchname: %s\nBuild: %s\t\tIDE: %d.%d.%d\n%s\n\n",
          (__FILE__), (__TIMESTAMP__), ARDUINO / 10000, ARDUINO % 10000 / 100, ARDUINO % 100 / 10 ? ARDUINO % 100 : ARDUINO % 10, ESP.getFullVersion().c_str());
  setupFS();
  connectWifi();
  admin();
  initLcd();
  setupTime();
  tempsetup();
  setupAPI();
  setupButton();
  delay(250);
  localTime();
  toRead();
  ArduinoOTA.onStart([]() {
    toSave();                              // Min/Max Werte speichern bevor OTA Update
  });
  ArduinoOTA.begin();
  server.begin();
  server.enableCORS(true);                 // Sendet den "CORS" Header an den Client.
}

void loop() {
  ArduinoOTA.handle();
  server.handleClient();
  if (millis() < 0x2FFF || millis() > 0xFFFFF0FF) runtime();
  if (static auto once {0}; millis() > 54e4 && !once++) connectWifi();          // Bei Stromausfall Wifi Verbindung einmalig nach 9 Minuten neu starten.
  localTime();
  static uint32_t previousMillis[] {0, 0};
  uint32_t currentMillis {millis()};
  if (currentMillis - previousMillis[0] >= 250) {
    queryTemp();
    previousMillis[0] = currentMillis;
  }
  currentMillis = millis();
  if (currentMillis - previousMillis[1] >= 100) {
    process();
    previousMillis[1] = currentMillis;
  }
  getButton();
  thingspeak();
}

Admin.ino


// ****************************************************************
// Sketch Esp8266 Admin IPv6 Modular(Tab)
// created: Jens Fleischer, 2020-01-26
// last mod: Jens Fleischer, 2021-06-09
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 2.6.1 - 3.1.2
// Geprüft: von 1MB bis 16MB Flash
// Getestet auf: Nodemcu, Wemos D1 Mini Pro, Sonoff Switch, Sonoff Dual
/******************************************************************
  Copyright (c) 2020 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Diese Version von Admin sollte als Tab eingebunden werden.
// #include <LittleFS.h> #include <ESP8266WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP8266 Webservers ist erforderlich.
// Die LittleFS.ino muss im ESP8266 Webserver enthalten sein
// Funktion "admin();" muss im setup() nach setupFS() und dem Verbindungsaufbau aufgerufen werden.
// Die Funktion "runtime();" muss mindestens zweimal innerhalb 49 Tage aufgerufen werden.
// Entweder durch den Client(Webseite) oder zur Sicherheit im "loop();"
// In der IDE unter Werkzeuge auf lwip Variant: ;"v2 IPv6 Lower Memory;" einstellen.
/**************************************************************************************/

#include <AddrList.h>

const char* const PROGMEM flashChipMode[] = {"QIO", "QOUT", "DIO", "DOUT", "Unbekannt"};

void admin() {                          // Funktionsaufruf "admin();" muss im Setup eingebunden werden
  File file = LittleFS.open(existFolder("Config") + "/config.json", "r");
  if (file) {
    String newhostname = file.readStringUntil('\n');
    if (newhostname != "") {
      WiFi.hostname(newhostname.substring(1, newhostname.length() - 1));
      file.close();
      ArduinoOTA.setHostname(WiFi.hostname().c_str());
    }
  }
  server.on("/admin/renew", handlerenew);
  server.on("/admin/once", handleonce);
  server.on("/reconnect", []() {
    server.send(304, "message/http");
    WiFi.reconnect();
  });
  server.on("/restart", []() {
    server.send(304, "message/http");
    toSave();                           //Wenn Werte vor dem Neustart gespeichert werden sollen
    ESP.restart();
  });
}

//Es kann entweder die Spannung am ADC-Pin oder die Modulversorgungsspannung (VCC) ausgegeben werden.

void handlerenew() {    // Um die am ADC-Pin anliegende externe Spannung zu lesen, verwende analogRead (A0)
  server.send(200, "application/json", "[\"" + runtime() + "\",\"" + WiFi.RSSI() + "\",\"" + analogRead(A0) + "\"]");     // Json als Array
}

/*
  ADC_MODE(ADC_VCC);
  void handlerenew() {   // Zum Lesen der Modulversorgungsspannung (VCC), verwende ESP.getVcc()
  server.send(200, "application/json", "[\"" + runtime() + "\",\"" + WiFi.RSSI() + "\",\"" + ESP.getVcc() / 1024.0 + " V" + "\"]");
  }
*/

void handleonce() {
  String ipv6Local, ipv6Global;
#if LWIP_IPV6
  for (auto &a : addrList) {
    if (a.isV6()) a.isLocal() ? ipv6Local = a.toString() : ipv6Global = a.toString();
  }
#endif
  if (server.arg(0) != "") {
    WiFi.hostname(server.arg(0));
    File f = LittleFS.open(existFolder("Config") + "/config.json", "w");                    // Datei zum schreiben öffnen
    f.printf("\"%s\"\n", WiFi.hostname().c_str());
    f.close();
  }
  String temp = "{\"File\":\"" + sketchName() + "\", \"Build\":\"" + __DATE__ + " " + __TIME__ + "\", \"SketchSize\":\"" + formatBytes(ESP.getSketchSize()) +
                "\", \"SketchSpace\":\"" + formatBytes(ESP.getFreeSketchSpace()) + "\", \"LocalIP\":\"" +  WiFi.localIP().toString() + "\", \"IPv6l\":\"" + ipv6Local +
                "\", \"IPv6g\":\"" +  ipv6Global + "\", \"Hostname\":\"" + WiFi.hostname() + "\", \"SSID\":\"" + WiFi.SSID() + "\", \"GatewayIP\":\"" +  WiFi.gatewayIP().toString() +
                "\", \"Channel\":\"" +  WiFi.channel() + "\", \"MacAddress\":\"" +  WiFi.macAddress() + "\", \"SubnetMask\":\"" +  WiFi.subnetMask().toString() +
                "\", \"BSSID\":\"" +  WiFi.BSSIDstr() + "\", \"ClientIP\":\"" + server.client().remoteIP().toString() + "\", \"DnsIP\":\"" + WiFi.dnsIP().toString() +
                "\", \"ResetReason\":\"" + ESP.getResetReason() + "\", \"CpuFreqMHz\":\"" + F_CPU / 1000000 + "\", \"FreeHeap\":\"" + formatBytes(ESP.getFreeHeap()) +
                "\", \"HeapFrag\":\"" + ESP.getHeapFragmentation() + "\", \"ChipSize\":\"" +  formatBytes(ESP.getFlashChipSize()) +
                "\", \"ChipSpeed\":\"" + ESP.getFlashChipSpeed() / 1000000 + "\", \"ChipMode\":\"" + flashChipMode[ESP.getFlashChipMode()] +
                "\", \"IdeVersion\":\"" + ARDUINO + "\", \"CoreVersion\":\"" + ESP.getCoreVersion() + "\", \"SdkVersion\":\"" + ESP.getSdkVersion() + "\"}";
  server.send(200, "application/json", temp);     // Json als Objekt
}

String runtime() {
  static uint8_t rolloverCounter;
  static uint32_t previousMillis;
  uint32_t currentMillis {millis()};
  if (currentMillis < previousMillis) rolloverCounter++;       // prüft millis() auf Überlauf
  previousMillis = currentMillis;
  uint32_t sec {(0xFFFFFFFF / 1000) * rolloverCounter + (currentMillis / 1000)};
  char buf[20];
  snprintf(buf, sizeof(buf), "%*.d %.*s %02d:%02d:%02d", sec < 86400 ? 0 : 1, sec / 86400, sec < 86400 ? 0 : sec >= 172800 ? 4 : 3, "Tage", sec / 3600 % 24, sec / 60 % 60, sec % 60);
  return buf;
}

Connect.ino

// ****************************************************************
// Sketch Esp8266 Connect Modular(Tab) mit optischer Anzeige
// created: Jens Fleischer, 2018-04-08
// last mod: Jens Fleischer, 2023-01-16
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 3.0.0 - 3.1.2
// Getestet auf: Nodemcu, Wemos D1 Mini Pro, Sonoff Dual
/******************************************************************
  Copyright (c) 2018 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Diese Version von Connect sollte als Tab eingebunden werden.
// #include <ESP8266WebServer.h> oder #include <ESP8266WiFi.h> muss im Haupttab aufgerufen werden
// Die Funktion "connectWifi();" muss im Setup eingebunden werden.
/**************************************************************************************/

//#define CONFIG                                        // Einkommentieren wenn der ESP dem Router die IP mitteilen soll.
#define NO_SLEEP                                        // Auskommentieren wenn der Nodemcu den deep sleep Modus nutzt.

const char* ssid = "Netzwerkname";                      // << kann bis zu 32 Zeichen haben
const char* password = "PasswortvomNetzwerk";           // << mindestens 8 Zeichen jedoch nicht länger als 64 Zeichen

#ifdef CONFIG
IPAddress staticIP(192, 168, 178, 99);                  // Statische IP des NodeMCU ESP8266
IPAddress gateway(192, 168, 178, 1);                    // IP-Adresse des Router
IPAddress subnet(255, 255, 255, 0);                     // Subnetzmaske des Netzwerkes
IPAddress dns(192, 168, 178, 1);                        // DNS Server
#endif

void connectWifi() {                                    // Funktionsaufruf "connectWifi();" muss im Setup eingebunden werden.
  byte i {0};
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
#ifdef CONFIG
  WiFi.config(staticIP, gateway, subnet, dns);
#endif
  while (WiFi.status() != WL_CONNECTED) {
#ifdef NO_SLEEP
    pinMode(LED_BUILTIN, OUTPUT);                       // OnBoardLed Nodemcu, Wemos D1 Mini Pro
    digitalWrite(LED_BUILTIN, 0);
#endif
    delay(500);
    digitalWrite(LED_BUILTIN, 1);
    delay(500);
    DEBUG_F(" %d sek\n", i);
    if (++i > 9) {
      DEBUG_P("\nVerbindung zum AP fehlgeschlagen !\n\n");
      ESP.restart();
    }
  }
  DEBUG_P("\nVerbunden mit: " + WiFi.SSID());
  DEBUG_P("Esp8266 IP: " + WiFi.localIP().toString());
}

Daten_API.ino

// ****************************************************************
// Sketch Esp8266 Daten_API Modular(Tab)
// created: Jens Fleischer, 2020-02-11
// last mod: Jens Fleischer, 2023-04-08
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 2.6.0 - 3.1.2
// Getestet auf: Nodemcu
/******************************************************************
  Copyright (c) 2020 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Diese Version von Connect sollte als Tab eingebunden werden.
// #include <ESP8266WebServer.h> muss im Haupttab aufgerufen werden
// Die Funktion "setupAPI();" muss im Setup eingebunden werden.
/**************************************************************************************/

void setupAPI() {                     // Funktionsaufruf "setupAPI();" muss im Setup eingebunden werden.
  server.on("/heater/current", handleCurrent);
  server.on("/heater/detail", handledetail);
  server.on("/test", test);
}

const String buildJson(const char* name, Ds18b20 &device) {
  String temp = "\"],\"";
  temp += name;
  temp += "\":[\"";
  temp += device.minTime;
  temp += "\",\"";
  temp += device.maxTime;
  temp += "\",\"";
  temp += device.alwaysMinTime;
  temp += "\",\"";
  temp += device.alwaysMaxTime;
  temp += "\",\"";
  temp += device.currentMin;
  temp += "\",\"";
  temp += device.currentMax;
  temp += "\",\"";
  temp += device.alwaysMin;
  temp += "\",\"";
  temp += device.alwaysMax;
  return temp;
}

void handledetail() {
  String temp = "{\"status\":[\"";
  temp += minmax;
  temp += "\",\"";
  temp += lt.minmaxTime;
  temp += "\",\"";
  temp += pushStatusAddWood;
  temp += "\",\"";
  temp += lcdStatus;
  temp += "\",\"";
  for (const auto& [key, value] : mixingTime) {
    temp += static_cast<String>(key) + " x gemischt um " + value + "<br>";
  }
  temp += buildJson((char*)"KES", sensors.KES);
  temp += buildJson((char*)"HVL", sensors.HVL);
  temp += buildJson((char*)"HRL", sensors.HRL);
  temp += buildJson((char*)"SWW", sensors.SWW);
  temp += buildJson((char*)"SVL", sensors.SVL);
  temp += buildJson((char*)"SRL", sensors.SRL);
  temp += buildJson((char*)"ATE", sensors.ATE);
  temp += "\"]}";
  DEBUG_P("Json: " + temp);
  serverResponse(temp);
}

void handleCurrent() {
  String temp = "{\"SWW\":\"";
  temp += sensors.SWW.current;
  temp += "\", \"SVL\":\"";
  temp += sensors.SVL.current;
  temp += "\", \"SRL\":\"";
  temp += sensors.SRL.current;
  temp += "\", \"KES\":\"";
  temp += sensors.KES.current;
  temp += "\", \"HVL\":\"";
  temp += sensors.HVL.current;
  temp += "\", \"HRL\":\"";
  temp += sensors.HRL.current;
  temp += "\"}";
  serverResponse(temp);
}

void test() {
  String temp = "{\"pushStatusAddWood\":\"";
  temp += pushStatusAddWood;
  temp += "\", \"minmax\":\"";
  temp += minmax;
  temp += "\", \"releaseAddWood\":\"";
  temp += releaseAddWood;
  temp += "\"}";
  serverResponse(temp);
}

void serverResponse(String &temp) {
  server.sendHeader("Cache-Control", "no-cache, no-store");
  server.send(200, "application/json", temp);
}

HttpClient.ino

// ****************************************************************
// Sketch HttpClient Modular(Tab)
// created: Jens Fleischer, 2018-06-29
// last mod: Jens Fleischer, 2020-10-31
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: ESP8266
// D3 = GPIO0  Anschluss Taster vom GPIO0 auf GND
// D4 = GPIO2  Anschluss Taster vom GPIO2 auf GND
// Software: Esp8266 Arduino Core 2.5.2 - 3.1.2
// Getestet auf: Nodemcu, Wemos D1 Mini Pro
/******************************************************************
  Copyright (c) 2018 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Diese Version von HttpClient sollte als Tab eingebunden werden.
// #include <ESP8266WebServer.h> oder #include <ESP8266WiFi.h> muss im Haupttab aufgerufen werden
// Zum schalten muss die Funktion "getbutton();" im loop(); aufgerufen werden.
/**************************************************************************************/

#include <ESP8266HTTPClient.h>

constexpr uint8_t BUTTON[] {D4, D3};                // Pin für Taster einstellen

void setupButton() {
  for (auto &pin : BUTTON)  pinMode(pin, INPUT_PULLUP);
}

void getButton() {
  bool currentStatus[2] {0, 0};
  static bool previousStatus[2] {0, 0};
  static uint32_t debounceMillis;
  uint32_t currentMillis {millis()};
  if (currentMillis - debounceMillis >= 50) {       // 50 ms Taster Entprellzeit
    debounceMillis = currentMillis;
    for (auto i = 0; i < 2; i++) {
      currentStatus[i] = !digitalRead(BUTTON[i]);
      if (!currentStatus[i] && currentStatus[i] != previousStatus[i]) {
        httpClientLight(i);
      }
      previousStatus [i] = currentStatus[i];
    }
  }
}

void httpClientLight(const byte &message) {
  const char* URL {"http://192.168.178.38/timer?tog="};        // URL des ESP... für Aussenbeleuchtung
  if (WiFi.status() == WL_CONNECTED) {
    WiFiClient client;
    HTTPClient http;
    DEBUG_F(PSTR("\nFunktion: %s meldet in Zeile: %d -> HttpClient Begin\n"), __PRETTY_FUNCTION__, __LINE__);
    DEBUG_P((String)URL + message);
    http.begin(client, (String)URL + message);
    int16_t httpCode = http.GET();
    if (httpCode > 0) {
      DEBUG_P("HTTP / 1.1 " + static_cast<String>(httpCode));
      if (httpCode == HTTP_CODE_OK) {
        String payload = http.getString();
        DEBUG_P("Antwort: " + payload);
        bool smallSize = payload.substring(2, payload.length()).toInt();
        bool largeSize = payload.substring(6, payload.length()).toInt();
        showStatusLight(smallSize, largeSize);
      }
    }
    else {
      DEBUG_P("Fehler: " + http.errorToString(httpCode));
    }
    http.end();
  }
}

LCD.ino

// ****************************************************************
// Sketch Esp8266 LCD Modular(Tab)
// created: Jens Fleischer, 2020-02-11
// last mod: Jens Fleischer, 2023-01-23
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266, LCD MODUL 4X20
// Software: Esp8266 Arduino Core 3.0.0 - 3.1.2
// Getestet auf: Nodemcu
/******************************************************************
  Copyright (c) 2020 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/

#include <LiquidCrystal_I2C.h>                             // https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library

LiquidCrystal_I2C lcd(0x3F, 20, 4);


void initLcd() {
  lcd.begin();
  lcd.backlight();
  server.on("/lcd", lcdBacklight);
  server.on("/clear", []() {
    server.send(304, "message/http");
    lcd.clear();
  });
}

void lcdBacklight() {                                      // LCD Beleuchtung An/Aus
  lcdStatus = !lcdStatus;
  handledetail();
}

void showTime() {
  static bool previousStatus {true};
  lcd.setCursor(12, 3);
  lcd.printf("%s", lt.timeOfDay);                          // Uhrzeit auf LCD ausgeben
  if (lcdStatus != previousStatus) {
    previousStatus = lcdStatus;
    lcd.setBacklight(lcdStatus);                           // LCD Hintergrundbeleuchtung schalten.
  }
}

void showTemperatures() {                                  // aktuelle Temperaturen auf LCD ausgeben
  lcd.setCursor(0, 0);
  lcd.printf("WW %.2f%cC", convert(sensors.SWW.current), 0xDF);
  lcd.setCursor(0, 1);
  lcd.printf("VL %.2f%cC", convert(sensors.SVL.current), 0xDF);
  lcd.setCursor(0, 2);
  lcd.printf("RL %.2f%cC", convert(sensors.SRL.current), 0xDF);
  lcd.setCursor(0, 3);
  if (sensors.KES.current < 12800) {
    lcd.printf("KE %.2f%cC", convert(sensors.KES.current), 0xDF);
  }
  else {
    lcd.printf("KE %.1f%cC", convert(sensors.KES.current), 0xDF);
  }
  lcd.setCursor(11, 0);
  lcd.printf("HV %.1f%cC", convert(sensors.HVL.current), 0xDF);
  lcd.setCursor(11, 2);
  lcd.printf("AT %4.1f%cC", convert(sensors.ATE.current), 0xDF);
}

void showStatusAddWood(int16_t &threshold) {
  lcd.setCursor(11, 1);
  if (pushStatusAddWood && threshold > 9664) {
    if (static uint8_t logState, viewsCount; !(++viewsCount, viewsCount %= 4)) {
      (logState = !logState) ? lcd.printf("FN %4.1f%cC", convert(threshold), 0xDF) : lcd.printf("HR %4.1f%cC", convert(sensors.HRL.current), 0xDF);
    }
  }
  else {
    lcd.printf("HR %4.1f%cC", convert(sensors.HRL.current), 0xDF);
  }
}

void showStatusLight(bool &smallSize, bool &largeSize) {   // Anzeige Aussenbeleuchtung auf LCD ausgeben
  lcd.setCursor(10, 0);
  smallSize ? lcd.printf("%c", (char)255) : lcd.printf(" ") ;
  lcd.setCursor(11, 2);
  smallSize ? lcd.printf("10 ON") : lcd.printf("     ") ;
  DEBUG_P(smallSize ? "10 Watt an" : "10 Watt aus");
  lcd.setCursor(17, 2);
  largeSize ? lcd.printf("30 ON") : lcd.printf("     ") ;
  DEBUG_P(largeSize ? "30 Watt an" : "30 Watt aus");
}

LittleFS.ino

// ****************************************************************
// Sketch Esp8266 Filesystem Manager spezifisch sortiert Modular(Tab)
// created: Jens Fleischer, 2020-06-08
// last mod: Jens Fleischer, 2023-01-14
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 3.1.0 - 3.1.2
// Geprüft: von 1MB bis 2MB Flash
// Getestet auf: Nodemcu
/******************************************************************
  Copyright (c) 2020 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Diese Version von LittleFS sollte als Tab eingebunden werden.
// #include <LittleFS.h> #include <ESP8266WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP8266 Webservers ist erforderlich.
// "server.onNotFound()" darf nicht im Setup des ESP8266 Webserver stehen.
// Die Funktion "setupFS();" muss im Setup aufgerufen werden.
/**************************************************************************************/

#include <list>
#include <tuple>

const char WARNING[] PROGMEM = R"(<h2>Der Sketch wurde mit "FS:none" kompilliert!)";
const char HELPER[] PROGMEM = R"(<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="[]" multiple><button>Upload</button></form>Lade die fs.html hoch.)";

void setupFS() {                                                                       // Funktionsaufruf "setupFS();" muss im Setup eingebunden werden
  LittleFS.begin();
  server.on("/format", formatFS);
  server.on("/upload", HTTP_POST, sendResponce, handleUpload);
  server.onNotFound([]() {
    if (!handleFile(server.urlDecode(server.uri())))
      server.send(404, "text/plain", "FileNotFound");
  });
}

bool handleList() {                                                                    // Senden aller Daten an den Client
  FSInfo fs_info;  LittleFS.info(fs_info);                                             // Füllt FSInfo Struktur mit Informationen über das Dateisystem
  Dir dir = LittleFS.openDir("/");
  using namespace std;
  using records = tuple<String, String, int>;
  list<records> dirList;
  while (dir.next()) {                                                                 // Ordner und Dateien zur Liste hinzufügen
    if (dir.isDirectory()) {
      uint8_t ran {0};
      Dir fold = LittleFS.openDir(dir.fileName());
      while (fold.next())  {
        ran++;
        dirList.emplace_back(dir.fileName(), fold.fileName(), fold.fileSize());
      }
      if (!ran) dirList.emplace_back(dir.fileName(), "", 0);
    }
    else {
      dirList.emplace_back("", dir.fileName(), dir.fileSize());
    }
  }
  dirList.sort([](const records & f, const records & l) {                              // Dateien sortieren
    if (server.arg(0) == "1") {
      return get<2>(f) > get<2>(l);
    } else {
      for (uint8_t i = 0; i < 31; i++) {
        if (tolower(get<1>(f)[i]) < tolower(get<1>(l)[i])) return true;
        else if (tolower(get<1>(f)[i]) > tolower(get<1>(l)[i])) return false;
      }
      return false;
    }
  });
  dirList.sort([](const records & f, const records & l) {                              // Ordner sortieren
    if (get<0>(f)[0] != 0x00 || get<0>(l)[0] != 0x00) {
      for (uint8_t i = 0; i < 31; i++) {
        if (tolower(get<0>(f)[i]) < tolower(get<0>(l)[i])) return true;
        else if (tolower(get<0>(f)[i]) > tolower(get<0>(l)[i])) return false;
      }
    }
    return false;
  });
  String temp = "[";
  for (auto& t : dirList) {
    if (temp != "[") temp += ',';
    temp += "{\"folder\":\"" + get<0>(t) + "\",\"name\":\"" + get<1>(t) + "\",\"size\":\"" + formatBytes(get<2>(t)) + "\"}";
  }
  temp += ",{\"usedBytes\":\"" + formatBytes(fs_info.usedBytes) +                      // Berechnet den verwendeten Speicherplatz
          "\",\"totalBytes\":\"" + formatBytes(fs_info.totalBytes) +                   // Zeigt die Größe des Speichers
          "\",\"freeBytes\":\"" + (fs_info.totalBytes - fs_info.usedBytes) + "\"}]";   // Berechnet den freien Speicherplatz
  server.send(200, "application/json", temp);
  return true;
}

void deleteRecursive(const String &path) {
  if (LittleFS.remove(path)) {
    LittleFS.open(path.substring(0, path.lastIndexOf('/')) + "/", "w");
    return;
  }
  Dir dir = LittleFS.openDir(path);
  while (dir.next()) {
    deleteRecursive(path + '/' + dir.fileName());
  }
  LittleFS.rmdir(path);
}

bool handleFile(String &&path) {
  if (server.hasArg("new")) {
    String folderName {server.arg("new")};
    for (auto& c : {
           34, 37, 38, 47, 58, 59, 92
         }) for (auto& e : folderName) if (e == c) e = 95;    // Ersetzen der nicht erlaubten Zeichen
    LittleFS.mkdir(folderName);
  }
  if (server.hasArg("sort")) return handleList();
  if (server.hasArg("delete")) {
    deleteRecursive(server.arg("delete"));
    sendResponce();
    return true;
  }
  if (!LittleFS.exists("fs.html")) server.send(200, "text/html", LittleFS.begin() ? HELPER : WARNING);     // ermöglicht das hochladen der fs.html
  if (path.endsWith("/")) path += "index.html";
  return LittleFS.exists(path) ? ({File f = LittleFS.open(path, "r"); server.streamFile(f, mime::getContentType(path)); f.close(); true;}) : false;
}

void handleUpload() {                                                                  // Dateien ins Filesystem schreiben
  static File fsUploadFile;
  HTTPUpload& upload = server.upload();
  if (upload.status == UPLOAD_FILE_START) {
    if (upload.filename.length() > 31) {  // Dateinamen kürzen
      upload.filename = upload.filename.substring(upload.filename.length() - 31, upload.filename.length());
    }
    printf(PSTR("handleFileUpload Name: /%s\n"), upload.filename.c_str());
    fsUploadFile = LittleFS.open(server.arg(0) + "/" + server.urlDecode(upload.filename), "w");
  } else if (upload.status == UPLOAD_FILE_WRITE) {
    printf(PSTR("handleFileUpload Data: %u\n"), upload.currentSize);
    fsUploadFile.write(upload.buf, upload.currentSize);
  } else if (upload.status == UPLOAD_FILE_END) {
    printf(PSTR("handleFileUpload Size: %u\n"), upload.totalSize);
    fsUploadFile.close();
  }
}

void formatFS() {                                                                      // Formatiert das Filesystem
  LittleFS.format();
  sendResponce();
}

void sendResponce() {
  server.sendHeader("Location", "fs.html");
  server.send(303, "message/http");
}

const String formatBytes(size_t const& bytes) {                                        // lesbare Anzeige der Speichergrößen
  return bytes < 1024 ? static_cast<String>(bytes) + " Byte" : bytes < 1048576 ? static_cast<String>(bytes / 1024.0) + " KB" : static_cast<String>(bytes / 1048576.0) + " MB";
}

const String existFolder(const char* foldername) {
  if (!LittleFS.exists(foldername)) LittleFS.mkdir(foldername);
  return foldername;
}

void lightDataRecorder() {
  static bool previouslogState {false};
  bool logState = analogRead(A0) >= BRIGHTNESS;
  if (logState != previouslogState) {
    lcdStatus = logState;                                                              // Status der Hintergrundbeleuchtung ändern..
    DEBUG_P(PSTR("====== LIGHT DATA LOGGING ======="));
    if (File f = LittleFS.open(existFolder("Visits") + lt.fileName + "txt", "a")) {
      logState ? f.printf("Licht an %s", lt.timeStamp) : f.printf(" aus %s\n", lt.timeOfDay);
      f.close();
    }
  }
  previouslogState = logState;
}

void archiveHeatUp() {
  if (File f = LittleFS.open(existFolder("Flow") + "/anheizen.txt", "a")) {            // Datei zum schreiben öffnen
    f.printf("%s SWW %.2f SVL %.2f Max SVL %.2f SRL %.2f Max SRL %.2f HVL %.2f\n",
             lt.timeStamp, convert(sensors.SWW.current), convert(sensors.SVL.current), convert(sensors.SVL.currentMax),
             convert(sensors.SRL.current), convert(sensors.SRL.currentMax), convert(sensors.HVL.current));
    f.close();
  }
}

void archiveAddWood(int16_t &threshold) {
  if (File f = LittleFS.open(existFolder("Flow") + "/nachlegen.txt", "a")) {           // Datei zum schreiben öffnen
    f.printf("%s SRL %.2f KES %.2f S %.2f\n",
             lt.timeStamp, convert(sensors.SRL.current), convert(sensors.KES.current), convert(threshold));
    f.close();
  }
}
void archiveProcess(char record[]) {
  if (File f = LittleFS.open(existFolder("Flow") + "/ablauf.txt", "a")) {
    f.printf("%s %s\n", lt.timeStamp, record);
    f.close();
  }
}

bool toRead() {                                                                        // Einlesen aller Daten falls die Datei im Filesystem vorhanden ist.
  if (File f = LittleFS.open(existFolder("Data") + lt.fileName + "dat", "r")) {
    DEBUG_P(PSTR("====== READ DATA LOGGING ======="));
    f.read(reinterpret_cast<byte*>(&sensors), sizeof(sensors));                        // Deserialisierung
    f.close();
    return true;
  }
  return false;
}

bool toSave() {
  if (File f = LittleFS.open(existFolder("Data") + lt.fileName + "dat", "w")) {        // Datei zum schreiben öffnen
    DEBUG_P(PSTR("====== SAVE DATA LOGGING ======="));
    f.write(reinterpret_cast<byte*>(&sensors), sizeof(sensors));
    f.close();
    return true;
  }
  return false;
}

Localtime.ino

// ****************************************************************
// Sketch Esp8266 Lokalzeit Modular(Tab)
// created: Jens Fleischer, 2018-07-10
// last mod: Jens Fleischer, 2021-01-09
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 2.6.0 - 3.1.2
// Getestet auf: Nodemcu, Wemos D1 Mini Pro, Sonoff Switch, Sonoff Dual
/******************************************************************
  Copyright (c) 2018 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Diese Version von Lokalzeit sollte als Tab eingebunden werden.
// #include <ESP8266WebServer.h> oder #include <ESP8266WiFi.h> muss im Haupttab aufgerufen werden.
// Funktion "setupTime();" muss im setup() nach dem Verbindungsaufbau aufgerufen werden.
/**************************************************************************************/

#include <time.h>

struct tm tm;

const char* const PROGMEM monthShort[] = {"Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"};

void setupTime() {                            // Es ist kein NTP Server erforderlich wenn der Timestamp vom DHCP-Server(FritzBox) geholt werden kann.
  setTZ("CET-1CEST,M3.5.0/02,M10.5.0/03");    // Zeitzone einstellen https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
}

uint32_t sntp_update_delay_MS_rfc_not_less_than_15000() {
  return 12 * 36e5;                                                      // SNTP Update Intervall einstellen
}

void fileName() {
  snprintf(lt.fileName, sizeof(lt.fileName), "/%u_%s.", 1900 + tm.tm_year, monthShort[tm.tm_mon]);
}

void localTime() {
  static uint8_t lastmon {CHAR_MAX};
  static time_t previous;
  time_t now = time(&now);
  if (now != previous) {
    previous = now;
    localtime_r(&now, &tm);
    strftime (lt.timeOfDay, sizeof(lt.timeOfDay), "%T", &tm);            // http://www.cplusplus.com/reference/ctime/strftime/
    strftime (lt.timeStamp, sizeof(lt.timeStamp), "%T %d.%m.%Y", &tm);   // http://www.cplusplus.com/reference/ctime/strftime/
    showTime();                                                          // Uhrzeit auf LCD ausgeben
    if (tm.tm_mon != lastmon) {
      lastmon = tm.tm_mon;
      fileName();
    }
  }
}

Push.ino

// ****************************************************************
// Sketch Esp8266 PushBullet Modular(Tab)
// created: Jens Fleischer, 2018-06-26
// last mod: Jens Fleischer, 2021-01-09
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 2.7.0 - 3.1.2
// Getestet auf: Nodemcu, Wemos D1 Mini Pro, Sonoff Switch, Sonoff Dual
/******************************************************************
  Copyright (c) 2018 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/
// Diese Version von PushBullet sollte als Tab eingebunden werden.
// #include <ESP8266WebServer.h> oder #include <ESP8266WiFi.h> muss im Haupttab aufgerufen werden
// Die Übergabe der Nachricht an "pushbullet(string oder String-Object);" erfolgt als char oder String.
// Die Funktion "Connect();" muss im Setup eingebunden sein.
/**************************************************************************************/

#include <WiFiClientSecure.h>     // für PushBullet

WiFiClientSecure secureClient;

bool pushbullet(const char* message) {                                     // Push Nachricht senden
  constexpr uint16_t TIMEOUT {2000};                                       // Zeit für Wartezeit auf Antwort in Millisekunden einstellen
  const char* PushBulletAPIKEY = {"Key vom Pushbullet-Konto"};             // Konto Schlüssel einfügen http://pushbullet.com
  const char* HOST {"api.pushbullet.com"};
  uint32_t broadcastingTime {millis()};
  secureClient.setInsecure();
  if (!secureClient.connect(HOST, 443)) {
    DEBUG_P("Pushbullet Verbindung fehlgeschlagen !");
    return false;
  }
  else {
    String messagebody = "{\"type\": \"note\", \"title\": \"Push vom ESP\", \"body\": \"" + (String)message + " " + lt.timeStamp + "\"}";
    secureClient.printf(PSTR("POST /v2/pushes HTTP/1.1\r\nHost: %s\r\nAuthorization: Bearer %s\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n%s\r\n"),
                        HOST, PushBulletAPIKEY, messagebody.length(), messagebody.c_str());
    DEBUG_P("Message gesendet");
  }

  while (!secureClient.available()) {
    if (millis() - broadcastingTime > TIMEOUT) {
      DEBUG_P("Pushbullet Client Timeout !");
      secureClient.stop();
      return false;
    }
  }

  while (secureClient.available()) {               //Emfängt Antwort
    DEBUG_F("Pushbullet Antwort nach: %4ld ms\n", millis() - broadcastingTime); // zeigt die Zeit bis zur Antwort --> passe den Timeout entsprechend an
    String line = secureClient.readStringUntil('\n');
    DEBUG_F("Funktion: %s meldet in Zeile: %d Responce: %s\n", __PRETTY_FUNCTION__, __LINE__, line.c_str());
    if (line.startsWith("HTTP/1.1 200 OK")) {
      secureClient.stop();
      return true;
    }
  }
  return false;
}

Temperatur.ino

// ****************************************************************
// Sketch Esp8266 Temperatur Modular(Tab)
// created: Jens Fleischer, 2020-02-11
// last mod: Jens Fleischer, 2023-04-08
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266, DS18b20
// Software: Esp8266 Arduino Core 2.7.0 - 3.1.2
// Getestet auf: Nodemcu
// ****************************************************************
// Hardware: Esp8266, DS18B20, 4k7 Ohm Widerstand
// D6 = GPIO12  Anschluss DS18B20
// 4k7 Ohm Widerstand von VCC auf GPIO12
// Getestet auf: Nodemcu
/******************************************************************
  Copyright (c) 2020 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/

constexpr byte ONE_WIRE_PIN = D6;     // Pin für Ds18b20 einstellen

OneWire oneWire(ONE_WIRE_PIN);
DallasTemperature MaximDs18b20(&oneWire);

void tempsetup() {
  MaximDs18b20.begin();
  MaximDs18b20.setWaitForConversion(false);
  MaximDs18b20.requestTemperatures();
  server.on("/minmax", []() {
    minmax ? archiveProcess((char*)"Max return durch Benutzer") : archiveProcess((char*)"Min return durch Benutzer");
    minmax ? setMaxGroup() : setMinGroup();                                    // setzt MIN MAX Werte zurück
    minmax = !minmax;
    handledetail();
  });
  server.on("/statusAddWood", []() {                                           // Sperrt die Push Nachricht zum Holz nachlegen
    pushStatusAddWood ? archiveProcess((char*)"Push nachlegen false durch Benutzer") : archiveProcess((char*)"Push nachlegen true durch Benutzer");
    pushStatusAddWood = !pushStatusAddWood;
    releaseAddWood = false;
    handledetail();
  });
}

void measure(Ds18b20 &device) {
  int16_t raw = MaximDs18b20.getTemp(device.address);
  if (raw != DEVICE_DISCONNECTED_RAW) {
    device.current = raw;
    if (device.current < device.currentMin) {                                  //Min Wert ermitteln
      device.currentMin = device.current;
      strcpy(device.minTime, lt.timeOfDay);
    }
    if (device.current > device.currentMax) {                                  //Max Wert ermitteln
      device.currentMax = device.current;
      strcpy(device.maxTime, lt.timeOfDay);
    }
    if (device.currentMin < device.alwaysMin) {                                //Langzeit Min Werte ermitteln
      device.alwaysMin = device.currentMin;
      strcpy(device.alwaysMinTime, lt.timeStamp);
    }
    if (device.currentMax > device.alwaysMax) {                                //Langzeit Max Werte ermitteln
      device.alwaysMax = device.currentMax;
      strcpy(device.alwaysMaxTime, lt.timeStamp);
    }
  }
  MaximDs18b20.requestTemperaturesByAddress(device.address);
}

void queryTemp() {
  static uint8_t passCounter;
  switch (++passCounter) {
    case 1:
      measure(sensors.KES);
      break;
    case 2:
      measure(sensors.SWW);
      break;
    case 3:
      measure(sensors.SVL);
      break;
    case 4:
      measure(sensors.SRL);
      break;
    case 5:
      measure(sensors.HVL);
      break;
    case 6:
      measure(sensors.ATE);
      break;
    case 7:
      measure(sensors.HRL);
      break;
    default:
      showTemperatures();                                                      // Temperatur auf LCD ausgeben
      passCounter = 0;
#ifdef TEMPDEBUG
      debugTemp();
#endif
  }
}

void getKlima(uint16_t &klimaComp) {
  static uint8_t previousDay {UCHAR_MAX};
  if (tm.tm_hour == 2 && tm.tm_wday != previousDay) {
    previousDay = tm.tm_wday;
    switch (sensors.ATE.current) {
      case 15 ... 44:
        klimaComp = 0;
        break;
      case 0 ... 14:
        klimaComp = OPERAND;
        break;
      default:
        klimaComp = OPERAND * 2;
    }
    toSave();             // Min Max Werte speichern
  }
}

void process() {
  static bool pushStatusHeated {false};
  static uint16_t klimaComp {0};
  static int16_t threshold;
  static uint8_t passCounter;
  switch (++passCounter) {
    case 1:
      sendHeatUp(pushStatusHeated);
      break;
    case 2:
      heated(pushStatusHeated, klimaComp);
      break;
    case 3:
      sendAddWood(threshold, klimaComp);
      break;
    case 4:
      heaterOff(pushStatusHeated, klimaComp);
      break;
    case 5:
      storageMixed();
      break;
    case 6:
      showStatusAddWood(threshold);
      break;
    default:
      lightDataRecorder();
      getKlima(klimaComp);
      passCounter = 0;
  }
}

void setMin(Ds18b20 &device) {                                                 // Min Werte zurücksetzen
  device.currentMin = device.current;
  strcpy(device.minTime, lt.timeOfDay);
}

void setMinGroup() {
  setMin(sensors.KES);
  setMin(sensors.HVL);
  setMin(sensors.HRL);
  setMin(sensors.SWW);
  setMin(sensors.SVL);
  setMin(sensors.SRL);
  strcpy(lt.minmaxTime, lt.timeOfDay);
}

void setMax(Ds18b20 &device) {                                                 // Max Werte zurücksetzen
  device.currentMax = device.current;
  strcpy(device.maxTime, lt.timeOfDay);
}

void setMaxGroup() {
  setMax(sensors.KES);
  setMax(sensors.HVL);
  setMax(sensors.HRL);
  setMax(sensors.SWW);
  setMax(sensors.SVL);
  setMax(sensors.SRL);
  strcpy(lt.minmaxTime, lt.timeOfDay);
}

/*-------- Erkennung ob angeheizt code ----------*/
void heated(bool &pushStatusHeated, uint16_t &klimaComp) {
  if (minmax && (sensors.KES.current > sensors.KES.currentMin + 5 * OPERAND - klimaComp)) {           //Erkennt das angeheitzt wurde //klimaCompensation -> 0 warm 1 kalt 2 sehr kalt
    setMaxGroup();                     //Max Werte zurücksetzen
    archiveProcess((char*)"Push anheizen false - Push nachlegen true - Ofen angeheizt - Max return");
    minmax = !minmax;
    pushStatusAddWood = true;                                                  // Gibt die Benachrichtigung für nachlegen frei, wenn angeheizt wurde.
    pushStatusHeated = true;                                                   // Sperrt die Benachrichtigung für anheizen.
  }
}

/*-------- Push Nachlegen Kessel code ----------*/
void sendAddWood(int16_t &threshold, uint16_t &klimaComp) {
  // Rücklauf unter 81°
  // Kessel fällt auf 75,5° + z Grad
  // Merken wenn Rücklauf über 81° freigabe löschen

  if (pushStatusAddWood && (sensors.SRL.current < 80 * OPERAND + klimaComp / 2)) { // klimaCompensation -> 0 warm 1 kalt 2 sehr kalt
    if (sensors.SRL.current < 61 * OPERAND) threshold = 9664;                                       // entspricht 75.5C
    else if (sensors.SRL.current < 70 * OPERAND) threshold = 9792;                                  // entspricht 76.5C
    else if (sensors.SRL.current < 74 * OPERAND) threshold = 9920;                                  // entspricht 77.5C
    else if (sensors.SRL.current < 76 * OPERAND) threshold = sensors.SRL.current + 512;             // entspricht SRL + 4C
    else if (sensors.SRL.current >= 76 * OPERAND) threshold = sensors.SRL.current + 640;            // entspricht SRL + 5C
    if (releaseAddWood && (sensors.KES.current <= threshold)) {
      releaseAddWood = !releaseAddWood;
      if (analogRead(A0) < BRIGHTNESS) {                                       //nur wenn Licht aus
        if (pushbullet("Holz nachlegen")) {                                    //Push Nachricht wenn Kesseltemperatur auf z gefallen
          archiveAddWood(threshold);
          archiveProcess((char*)"Push nachlegen gesendet");
        }
        else {
          archiveProcess((char*)"Push nachlegen Senden fehlgeschlagen");
        }
      }
    }
    if (!releaseAddWood && (sensors.KES.current > threshold + 1 * OPERAND)) {
      releaseAddWood = !releaseAddWood;
    }
  }
  if ((releaseAddWood && (sensors.SRL.current >= 80 * OPERAND + klimaComp)) || (releaseAddWood && (sensors.SRL.current + 15 * OPERAND) < sensors.SRL.currentMax)) {        // sperrt Nachlegen bis zum nächsten Anheizen
    archiveProcess((char*)"Push nachlegen false");
    releaseAddWood = false;
    pushStatusAddWood = false;
  }
}

/*-------- Push Anheizen Kessel code ----------*/
void sendHeatUp(bool &pushStatusHeated) {
  if (!pushStatusHeated && sensors.SVL.current >= sensors.SWW.current - 1 * OPERAND && sensors.SWW.current < 73 * OPERAND) {       // 17.12.18 von 2 auf 1
    if (analogRead(A0) < BRIGHTNESS && sensors.SRL.currentMax > (sensors.SRL.current + 15 * OPERAND)) {       //nur wenn Licht aus
      if (pushbullet("Anheizen")) {
        pushStatusHeated = true;                                 // wird von Ofen aus freigegeben
        archiveHeatUp();
        archiveProcess((char*)"Push Anheizen gesendet");
      }
      else {
        static bool secound {0};
        if (secound) {
          secound = false;
          pushStatusHeated = true;
        }
        else {
          secound = true;
        }
        archiveProcess((char*)"Push Anheizen Senden fehlgeschlagen");
      }
    }
  }
}

/*-------- Erkennung Ofen aus code ----------*/
void heaterOff(bool &pushStatusHeated, uint16_t &klimaComp) {
  if (!minmax && sensors.SVL.currentMax > (sensors.SVL.current + 7 * OPERAND - klimaComp)) {       // klimaCompensation -> 0 warm 1 kalt 2 sehr kalt
    setMinGroup();                                                                   // Min Werte zurücksetzen
    archiveProcess((char*)"Push anheizen true - Ofen aus - Min return");
    minmax = !minmax;
    pushStatusHeated = false;                                                        // gibt Push anheizen frei
  }
}

/*-------- Abfrage Speicher gemischt Funktion ----------*/
void storageMixed() {
  static unsigned long startzeit1, startzeit2;
  static int16_t previousValue = INT16_MAX;
  static uint8_t counter {0};
  static bool flag {true};                   // Hilfsmerker für Speicher mischen
  if (minmax) {                              // minmax (true)kommt von Ofen aus, wird durch angeheizt (false), gibt Speicher gemischt frei ist global
    if (flag) {                              // Abfrage Speicher gemischt
      if (millis() - startzeit1 >= 60000) {
        startzeit1 = millis();
        previousValue = sensors.SVL.current;
      }
      if (sensors.SVL.current > previousValue + OPERAND) {
        counter ++;
        flag = false;
        mixingTime[counter] = lt.timeOfDay;
        char mixing[15];
        snprintf(mixing, sizeof(mixing), "%d x gemischt", counter);
        archiveProcess(mixing);
      }
    }
    else {
      if (millis() - startzeit2 >= 240000) {
        startzeit2 = millis();
        previousValue = sensors.SVL.current;
      }
      if (sensors.SVL.current < previousValue - OPERAND / 2) {
        flag = true;
        previousValue = INT16_MAX;
      }
    }
  }
  else {                 // minmax (false)kommt von angeheizt, wird durch Ofen aus (true), gibt Speicher gemischt frei ist global
    counter = 0;
    mixingTime.clear();
  }
}

#ifdef TEMPDEBUG

void printDevice(Ds18b20 &device) {
  DEBUG_F(" Akt: %.2f\n", convert(device.current));
  DEBUG_F(" Min: %.2f\n", convert(device.alwaysMin));
  DEBUG_F(" Min: %s\n", device.alwaysMinTime);
  DEBUG_F(" Max: %.2f\n", convert(device.alwaysMax));
  DEBUG_F(" Max: %s\n", device.alwaysMaxTime);
}

void debugTemp() {
  DEBUG_P("KES");
  printDevice(sensors.KES);
  DEBUG_P("HVL");
  printDevice(sensors.HVL);
  DEBUG_P("HRL");
  printDevice(sensors.HRL);
  DEBUG_P("SWW");
  printDevice(sensors.SWW);
  DEBUG_P("SVL");
  printDevice(sensors.SVL);
  DEBUG_P("SRL");
  printDevice(sensors.SRL);
  DEBUG_P("ATE");
  printDevice(sensors.ATE);
}

#endif

Thingspeak.ino

// ****************************************************************
// Sketch Thingspeak Modul(Tab) nicht blockierend
// created: Jens Fleischer, 2018-04-08
// last mod: Jens Fleischer, 2021-01-09
// ****************************************************************
// Hardware: Esp8266
// Software: Esp8266 Arduino Core 2.4.2 - 3.1.2
// Getestet auf: Nodemcu
/******************************************************************
  Copyright (c) 2018 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/

WiFiClient client;

bool thingspeak() {                                                // Funktionsaufruf "thingspeak();" muss in loop eingebunden sein.
  constexpr uint16_t HTTPPORT {80};
  constexpr uint32_t INTERVAL {1000UL * 16};                       // Sendeinterval in Sekunden einstellen.
  constexpr uint16_t TIMEOUT {1600};                               // Zeitraum für Fehlerrückgabe in Millisekunden einstellen.
  const char* HOST {"184.106.153.149"};
  const char* TS_KEY = {"Geheimer ApiKey"};                        // trage deinen ApiKey von https://thingspeak.com
  static bool error {false}, response {true};
  static unsigned long previousMillis {0};                         // Sendebeginn einstellen (" 0 - interval" = sofort, "0" = nach Ablauf von Interval)

  /*************** Thinkspeak senden *********************/

  uint32_t currentMillis {millis()};
  if (currentMillis - previousMillis >= INTERVAL || error) {       // Senden im Interval und erneut im Fehlerfall.
    error = false;                // oder bei erfolgreicher Antwort
    previousMillis = currentMillis;
    if (!client.connect(HOST, HTTPPORT)) {
      DEBUG_P("Thingspeak Verbindung fehlgeschlagen !");
      error = true;
      return false;
    }
    else {
      char buf[146];
      snprintf(buf, sizeof(buf), "&field1=%.2f&field2=%.2f&field3=%.2f&field4=%.2f&field5=%.2f&field6=%.2f&field7=%.2f&field8=%.2f",
               convert(sensors.SWW.current), convert(sensors.SVL.current), convert(sensors.KES.current), convert(sensors.SRL.current),
               convert(sensors.SWW.current), convert(sensors.SVL.current), convert(sensors.KES.current), convert(sensors.SRL.current));
      DEBUG_P(buf);
      client.printf("POST /update?key=%s%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", TS_KEY, buf, HOST);
      response = false;
      return true;
    }
  }

  /************** Thinkspeak auf Anwort prüfen *****************/

  if (!response) {
    if (millis() - previousMillis > TIMEOUT) {
      DEBUG_P("Thingspeak Client Timeout !");
      error = true;
      client.stop();
      return false;
    }
    if (client.available()) {               // einlesen der Antwort
      String line = client.readStringUntil('\n');
      line.trim();
      DEBUG_F("Funktion: %s meldet in Zeile: %d Responce: %s\n", __PRETTY_FUNCTION__, __LINE__, line.c_str());
      if (line == "HTTP/1.1 200 OK") {    // wenn Antwort mit "HTTP/1.1 200 OK" beginnt war das Senden erfolgreich
        client.stop();
        response = true;
      }
    }
  }
  return true;
}

admin.html

<!DOCTYPE HTML> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
  <head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="style.css">
	<title>ESP8266 Admin</title>
	<script>
	  addEventListener('load', () => {
		renew(), once();
		let output = document.querySelector('#note');
		let btn = document.querySelectorAll('button');
		let span = document.querySelectorAll('#right span'); 
		btn[0].addEventListener('click', () => {
		  location = '/fs.html';
		});
		btn[1].addEventListener('click', () => {
		  location = '/';
		});
		btn[2].addEventListener('click', check.bind(this, document.querySelector('input')));
		btn[3].addEventListener('click', re.bind(this, 'reconnect'));
		btn[4].addEventListener('click', () => {
		  if (confirm('Bist du sicher!')) re('restart');
		});
		async function once(val = '',arg) {
		  try {
			let resp = await fetch('/admin/once', { method: 'POST', body: val});
			let obj = await resp.json();
			output.innerHTML = '';
			output.classList.remove('note');
			document.querySelector('form').reset();
			if (val.length == 0) myIv = setInterval(renew, 1000);
			if (arg == 'reconnect') re(arg);
			span[3].innerHTML = obj['File'];
			span[4].innerHTML = obj['Build'];
			span[5].innerHTML = obj['SketchSize'];
			span[6].innerHTML = obj['SketchSpace'];
			span[7].innerHTML = obj['LocalIP'];
			span[8].innerHTML = obj['IPv6l'] ? obj['IPv6l'] : 'inaktiv';
			span[9].innerHTML = obj['IPv6g'] ? obj['IPv6g'] : 'inaktiv';
			span[10].innerHTML = obj['Hostname'];
			span[11].innerHTML = obj['SSID'];
			span[12].innerHTML = obj['GatewayIP'];
			span[13].innerHTML = obj['Channel'];
			span[14].innerHTML = obj['MacAddress'];
			span[15].innerHTML = obj['SubnetMask'];
			span[16].innerHTML = obj['BSSID'];
			span[17].innerHTML = obj['ClientIP'];
			span[18].innerHTML = obj['DnsIP'];
			span[19].innerHTML = obj['ResetReason'];
			span[20].innerHTML = obj['CpuFreqMHz'] + " MHz";
			span[21].innerHTML = obj['FreeHeap'];
			span[22].innerHTML = obj['HeapFrag'] + "%";
			span[23].innerHTML = obj['ChipSize'];
			span[24].innerHTML = obj['ChipSpeed'] + " MHz";
			span[25].innerHTML = obj['ChipMode'];
			span[26].innerHTML = obj['IdeVersion'].replace(/(\d)(\d)(\d)(\d)/,obj['IdeVersion'][3]!=0 ? '$1.$3.$4' : '$1.$3.');
			span[27].innerHTML = obj['CoreVersion'].replace(/_/g,'.');
			span[28].innerHTML = obj['SdkVersion'];
			Object.keys(obj).forEach(val => {
				if (obj[val].length > 25) document.querySelectorAll(`[data-${val}]`).forEach(el => {el.classList.add('ip')});
			});
		  } catch(err) {
			re();
		  }
		}
		async function renew() {
		  const resp = await fetch('admin/renew');
		  const array = await resp.json();
		  array.forEach((v, i) => {span[i].innerHTML = v});
		}
		function check(inObj) {
		  !inObj.checkValidity() ? (output.innerHTML = inObj.validationMessage, output.classList.add('note')) : (once(inObj.value, 'reconnect'));
		}
		function re(arg = '') {
		  clearInterval(myIv);
		  fetch(arg);
		  output.classList.add('note');
		  if (arg == 'restart') {
			output.innerHTML = 'Der Server wird neu gestartet. Die Daten werden in 15 Sekunden neu geladen.';
			setTimeout(once, 15000);
		  } 
		  else if (arg == 'reconnect'){
			output.innerHTML = 'Die WiFi Verbindung wird neu gestartet. Daten werden in 10 Sekunden neu geladen.';
			setTimeout(once, 10000);
		  }
		  else {
			output.innerHTML = 'Es ist ein Verbindungfehler aufgetreten. Es wird versucht neu zu verbinden.';
			setTimeout(once, 3000);
		  }
		}
	  });
	</script>
  </head>
  <body>
	<h1>ESP8266 Admin Page</h1>
	<main>
	  <aside id="left">
		<span>Runtime ESP:</span>
		<span>WiFi RSSI:</span>
		<span>ADC/VCC:</span>
		<span>Sketch Name:</span>
		<span>Sketch Build:</span>
		<span>SketchSize:</span>
		<span>FreeSketchSpace:</span>
		<span>IPv4 Address:</span>
		<span data-ipv6l>Link-Local:</span>
		<span data-ipv6g>IPv6:</span>
		<span>Hostname:</span>
		<span>Connected to:</span>
		<span>Gateway IP:</span>
		<span>Channel:</span>
		<span>MacAddress:</span>
		<span>SubnetMask:</span>
		<span>BSSID:</span>
		<span data-clientip>Client IP:</span>
		<span>DnsIP:</span>
		<span>Reset Ground:</span>
		<span>CPU Freq:</span>
		<span>FreeHeap:</span>
		<span>Heap Fragmentation:</span>
		<span>FlashSize:</span>
		<span>FlashSpeed:</span>
		<span>FlashMode:</span>
		<span>Arduino IDE Version:</span>
		<span>Esp Core Version:</span>
		<span>SDK Version:</span>
	  </aside>
	  <aside id="right">
		<span>0</span>
		<div>
		  <span></span>
		  dBm
		</div>
		<span>0</span>
		<span>?</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span data-ipv6l>0</span>
		<span data-ipv6g>0</span>
		<span>?</span>
		<span>?</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span data-clientip>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
		<span>0</span>
	  </aside>
	</main>
	<div>
	  <button>Filesystem</button>
	  <button>Startseite</button>
	</div>
	<div id="note"></div>
	<div>
	  <form>
		<input placeholder="neuer Hostname" pattern="([A-Za-z0-9\-]{1,32})" title="Es dürfen nur Buchstaben (a-z, A-Z), Ziffern (0-9) und Bindestriche (-) enthalten sein. Maximal 32 Zeichen" required>
		<button type="button">Name Senden</button>
	  </form>
	</div>
	<div>
	  <button>WiFi Reconnect</button>
	  <button>ESP Restart</button>
	</div>
  </body>
</html>
fs.html

<!DOCTYPE HTML> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
  <head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="style.css">
	<title>Filesystem Manager</title>
	<script>
	  document.addEventListener('DOMContentLoaded', () => {
		list(JSON.parse(localStorage.getItem('sortBy')));
		btn.addEventListener('click', () => { if (!confirm(`Wirklich formatieren? Alle Daten gehen verloren.\nDu musst anschließend fs.html wieder laden.`)) event.preventDefault()});
	  });
	  async function list(by, to = '/'){
		let resp = await fetch(`?sort=${by}`);
		let json = await resp.json();
		let myList = document.querySelector('main'), noted = '';
		myList.innerHTML = '<nav><input type="radio" id="/" name="group"><label for="/"> &#128193;</label><span id="cr">+&#128193;</nav></span><span id="si"></span>';
		document.querySelector('form').setAttribute('action', `/upload?f=${to}`);
		for (var i = 0; i < json.length - 1; i++) {
		  let dir = '', f = json[i].folder, n = json[i].name;
		  if (f != noted) {
			noted = f;
			dir = `<nav><input type="radio" id="${f}" name="group"><label for="${f}"></label> &#128193; ${f} <a href="?delete=/${f}">&#x1f5d1;&#xfe0f;</a></nav>`;
		  }
		  if (n != '') dir += `<li><a href="${f}/${n}">${n}</a><small> ${json[i].size}</small><a href="${f}/${n}"download="${n}"> Download</a> or<a href="?delete=${f}/${n}"> Delete</a>`;
		  myList.insertAdjacentHTML('beforeend', dir);
		}
		myList.insertAdjacentHTML('beforeend', `<li><b id="so">${by ? '&#9660;' : '&#9650;'} LittleFS</b> belegt ${json[i].usedBytes.replace(".00", "")} von ${json[i].totalBytes.replace(".00", "")}`);
		var free = json[i].freeBytes;
		cr.addEventListener('click', () => {
		  document.getElementById('no').classList.toggle('no');
		});
		so.addEventListener('click', () => {
		  list(by = ++by % 2, to);
		  localStorage.setItem('sortBy', JSON.stringify(by));
		});
		fs.addEventListener('change', e => {
		  for (var bytes = 0, i = 0; i < e.target.files.length; i++) bytes += e.target.files[i].size;
		  for (var output = `${bytes} Byte`, i = 0, circa = bytes / 1024; circa > 1; circa /= 1024) output = circa.toFixed(2) + [' KB', ' MB', ' GB'][i++];
		  if (bytes > free) {
			si.innerHTML = `<li><b> ${output}</b><strong> Ungenügend Speicher frei</strong></li>`;
			up.setAttribute('disabled', 'disabled');
		  }
		  else {
			si.innerHTML = `<li><b>Dateigröße:</b> ${output}</li>`;
			up.removeAttribute('disabled');
		  }
		});
		let node = document.querySelectorAll('main input');
		node.forEach(n => { if (n.id === to) n.setAttribute('checked', 'checked')});
		node.forEach(n => {
		  n.addEventListener('change', e => {
			if (e.target.checked) {
			  to = e.target.id
			  document.querySelector('form').setAttribute('action', `/upload?f=${to}`);
			}
		  });
		});
		document.querySelectorAll('[href^="?delete=/"]').forEach(node => { node.addEventListener('click', () => { if (!confirm('Sicher!')) event.preventDefault()})});
		document.querySelectorAll('main input').forEach(n => { if (n.id === to) n.setAttribute('checked', 'checked')});
	  }
	</script>
  </head>
  <body>
	<h2>ESP8266 Filesystem Manager</h2>
	<form method="post" enctype="multipart/form-data">
	  <input id="fs" type="file" name="up[]" multiple>
	  <button id="up" disabled>Upload</button>
	</form>
	<form id="no" class="no" method="post">
	  <input name="new" placeholder="Ordner Name" pattern="[^\x22\/%&\\:;]{0,31}[^\x22\/%&\\:;\s]{1}" title="Zeichen &#8220; % & / : ; \  sind nicht erlaubt." required>
	  <button>Create</button>
	</form>
	<main></main>
	<form action="/format" method="post">
	  <button id="btn">Format LittleFS</button>
	</form>
  </body>
</html>
style.css

/* For more information visit:https://fipsok.de */
body {
	font-family: sans-serif;
	background-color: #87cefa;
	display: flex;
	flex-flow: column;
	align-items: center;
}
h1,h2 {
	color: #e1e1e1;
	text-shadow: 2px 2px 2px black;
}
li {
	background-color: #feb1e2;
	list-style-type: none;
	margin-bottom: 10px;
	padding: 2px 5px 1px 0;
	box-shadow: 5px 5px 5px rgba(0,0,0,0.7);
}
li a:first-child, li b {
	background-color: #8f05a5;
	font-weight: bold;
	color: white;
	text-decoration:none;
	padding: 2px 5px;
	text-shadow: 2px 2px 1px black;
	cursor:pointer;
}
li strong {
	color: red;
}
input {
	height:35px;
	font-size:14px;
	padding-left: .3em;
}
label + a {
	text-decoration: none;
}
h1 + main {
	display: flex;
}  
aside {
	display: flex;
	flex-direction: column;
	padding: 0.2em;
}
button {
	height:40px;
	width:130px;
	font-size:16px;
	margin-top: 1em;
	box-shadow: 5px 5px 5px rgba(0,0,0,0.7);
}
div button {
	background-color: #7bff97;
}
nav {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
}
#left {
	align-items:flex-end;
	text-shadow: 0.5px 0.5px 1px #757474;
}
#cr {
	font-weight: bold;
	cursor:pointer;
	font-size: 1.5em;
}
#up {
	width: auto; 
}
.note {
	background-color: #fecdee;
	padding: 0.5em;
	margin-top: 1em;
	text-align: center;
	max-width: 320px;
	border-radius: 0.5em;
}
.no {
	display: none;
}
form [title] {
	background-color: skyblue;
	font-size: 1em;
	width: 120px;
}
form:nth-of-type(2) {
	margin-bottom: 1em;
}
[value*=Format] {
	margin-top: 1em;
	box-shadow: 5px 5px 5px rgba(0,0,0,0.7);
}
[name="group"] {
	display: none;
}
[name="group"] + label {
	font-size: 1.5em;
	margin-right: 5px;
}
[name="group"] + label::before {
	content: "\002610";
}	
[name="group"]:checked + label::before {
	content: '\002611\0027A5';
}
@media only screen and (max-width: 500px) {
	.ip {
		right: 6em;
		position: relative;
	}
	aside {
		max-width: 50vw;
	}
}
3 Kommentare
Helmut ❘ 06.10.2020
Hallo Fips ,

nicht unmittelbar gesehen , der Compiler hat gemeckert
im Tab Push

Zeile 33

Fehlermeldung :

"erwartet ',' oder ';' vor 'c....'"


zum LCD Tab

"..... Pin Standard ESP8266 I2C "

wieder was gelernt !

aber mit Leerfeld kommt Fehler

Keine passende Funktion für den Aufruf von
'LiquidCrystal_......"

Ich benutze das NodeMCU Amiga ESP8266 mod 12F
als Bord "Generic ESP 8266 Module in der Arduino IDE.

Gruß Helmut

Antwort:
Das durch die Passwortersetzung auf dem Webserver unterschlagene Semikolon habe ich doch gefixt, lade bitte die Zip Datei oder die Seite neu, dann passt das wieder.

"lcd.begin();" klappt ohne Fehlermeldung wenn du die im Head des LCD Tab aufgeführte Library von Lucas Maziero verwendest.

Unverständlich für mich, warum nutzt man einen Nodemcu und stellt die IDE auf nicht näher spezifiziertes Board?

Gruß Fips







Helmut ❘ 06.10.2020

Wenn man mit über 60 Jahren von Bascom auf C bzw.
Arduino Programmierung umsteigen und ergänzen will
ist das mit den Termini nicht so einfach.

Mit Ports sind die Pins gemeint.
Die Umbenennung sollte kein Problem sein, nur als Hinweis
in deinen TAB's wird es unterschiedlich gehandhabt.
Aber nicht das Problem !!

Wenn C++ für mich auch noch viele Geheimnisse in sich
birgt , versuche ich im Web mehr Klarheit zu erhalten.

Bei den Lücken in Englisch hilft Google.

Zum Projekt Heizungsüberwachung :
Erster Start ok
Uplouds ok
Aufruf Admin ok
Aufruf Startseite -"FileNotFound"
Zurück ok
Server verlassen
Neustart
Anzeige FileNotFound
Button Zurück (Pfeil oben links) deaktiviert
Erneutes Flashen und Starten , wie bei Neustart

Warum ?

________________________________________________
Nur als Hinweis :

Fehlermeldungen beim erstmaligen kompilieren :

Tab HttpClient
const uint8_t BUTTON[] {D4, D3}; // Pin für Taster einstellen

Pins auf GPI umgestellt
________________________________________________________________
Tab LCD
void initLcd() {
lcd.begin();

mit 20,4 ergänzt
________________________________________________________________

Tab Push
bool pushbullet(const char* message) {

Mit ; ergänzt
________________________________________________________________

Tab Temperatur
const byte ONE_WIRE_PIN = D6

Pin auf GPI umgestellt


Gruß Helmut

Antwort:
Es gibt keine Startseite beim Projekt Heizung im Filesystem des Esp8266. Meine Startseite befindet sich Zentral für alle meine Esp auf einem Server, diese holt sich per Fetch Api die Daten von den jeweiligen Esp's.

Um die "D" Bezeichnungen der Pin's zu verwenden musst du vor dem Compilieren den richtigen Esp (bei mir NodeMcu) auswählen.

" lcd.begin();
mit 20,4 ergänzt"

Was bezweckst du damit?
Die Esp8266 haben keinen GPIO20!

An der Stelle überschreibst du die default Pin's für I2C des Esp8266.

Ausschnitt aus der verwendeten Library:
"void begin(uint8_t sda = SDA , uint8_t scl = SCL); // Int with pin default ESP8266 I2C "

"bool pushbullet(const char* message) {
Mit ; ergänzt"

Wo hast du da ein fehlendes Semikolon gesehen bzw. eingesetzt?

Gruß Fips


Helmut ✪ ✪ ✪ 05.10.2020
Hallo Fips,
erstmal - eine "Hammer Seite!" , Danke dafür.

Als "Holzheizer" wäre dein Projekt Heizung für mich
wie gemacht.
Da das Projekt noch in Arbeit ist treten natürlich noch
Fehler auf.
Die Ports mussten umbenannt werden sowie ein ";"
hinzugefügt werden.
Nach dem Verbindungsaufbau wird im Browser allerdings
"file not found" angezeigt.
Hat das mit den spiffs zu tun?
Die Zeitschaltuhr funktioniert !

Gruß Helmut

Antwort:
Nanu, wo bist du denn? Hier gibt es doch gar kein Projekt.

";" War ein Fehler der automatischen Passwortersetzung auf dem Server. Ist behoben.

Welche Ports?

Das Projekt Heizung ist bereits von Spiffs auf LittleFS Filesystem umgestellt.

Gruß Fips


Kommentar eintragen

*