Update: 2023-09-24
Esp32 Zeitschaltuhr
Singel Zeitschaltuhr mit NTP Zeitsynchronisation
Automatischer Wechsel zwischen Sommer und Normalzeit
Betriebsstundenzähler für den Angeschlossenen Verbraucher
Verbrauchsanzeige in Kilowattstunden
Eins vorweg, ich übernehme keinerlei Verantwortung falls ihr diesen Sketch nutzt um mittels Relais oder SSR Netzspannung zu schalten.
Ich stelle bewusst keine Schaltpläne dazu zur Verfügung. Wendet euch dafür an eine Elektrofachkraft mit entsprechender Ausbildung.
Getestet habe ich mit den, bei Arduino-Jüngern, beliebten blauen Relais Modulen, einem Mosfet "IRF3708" und einem Solid State Relais "Fotek SSR-40 DA".

Highlight
Der Sketch Zeitschaltuhr ist für LOW und HIGH aktive Relais, Solid State Relais oder Mosfet geeignet.
Dies muss vor dem Hochladen einmalig im Sketch, im Tab Schaltuhr.ino, eingestellt werden.
Es lassen sich bis zu 100 Ein-/Aus-Schaltzeiten für ein angeschlossenes Gerät einstellen.
Dies kann vor dem Hochladen im Sketch Tab Schaltuhr.ino und in der index.html, eingestellt werden.
Rechts neben dem Button zum manuellen Ein-/Ausschalten des Ausgangs befindet sich die optische Schaltzustandsanzeige.

Eine Betriebsstundenanzeige der Angeschlossenen Geräte erfolgt durch Klick/Touch in den oberen Teil.
Gib den Verbrauch deines Gerätes in Watt in die Maske ein. Der Betriebstundenzähler lässt sich per Klick/Touch auf den Button zurücksetzen.


Funktionen
Die einzelnen Schaltzeiten können mittels Schaltfläche ON/OFF aktiviert oder deaktiviert werden.
Ein-und Ausschaltzeiten werden in einer Datei im Spiffs des Esp.. gespeichert.
Eingegebene Zeitperioden werden durch Klick/Touch des Speichern Buttons zum ESP.. gesendet.
Ein erfolgreiches speichern der Schaltzeiten auf dem Esp32 Webserver wird im Webinterface für 5 Sekunden signalisiert.
Alle Schaltzeiten können gleichzeitig aktiviert/deaktiviert werden.

Anleitung zur Inbetriebnahme
Zuerst im "Connect" Tab deine Wlan Zugangsdaten eingeben, anschliesend im "Schaltuhr" Tab die Variable "aktiv" auf HIGH oder LOW setzen.
"const auto aktiv = HIGH;" Je nachdem welche Komponente du an den Ausgängen betreiben möchtest.
Du kannst die Anzahl der Schaltzeiten pro Relais an deinen Bedarf anpassen. Diese müssen im "Schaltuhr" Tab und in der "index.html" übereinstimmen.
Eventuell den NTP Zeitserver und die Zeitzone, für deinen Standort, in der "Lokalzeit.ino" ändern. Anschließend den Sketch hochladen.
Im Seriellen Monitor wird die IP des ESP.. angezeigt. "deineIP/spiffs.html" Kopiere diese URL in die Adresszeile deines Browsers und verbinde dich mit deinem Esp32.
Falls sich im Spiffs (Speicher) des Esp32 noch keine "spiffs.html" befindet wird ein kleiner Helfer zu deinem Browser gesendet.
Mit diesem kannst du die
"spiffs.html"
und die
"style32.css" hochladen.
Jetzt wird der Spiffs Manager angezeigt, mit dem du noch die
"admin.html" , das
Favicon und die
"index.html"
in den Speicher deines Esp... uploaden musst. Klick/Touch nun auf die index.html um zur Zeitschaltuhr zu kommen.
Optional kann auch noch ein mechanischer Taster zum manuellen Schalten angeschlossen werden.
Wer möchte kann sich unten den Code ansehen und selbst in Dateien kopieren oder aber das Ganze als Archiv downloaden.
Zeitschaltuhrsingel32.ino
// ****************************************************************
// Sketch Esp32 Webserver Zeitschaltuhr Modular(Tab)
// created: Jens Fleischer, 2018-07-06
// last mod: Jens Fleischer, 2020-03-26
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
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.
*******************************************************************/
// Der WebServermodular stellt den Haupt Tab dar.
// #include "SPIFFS.h" #include <WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP32 Webservers ist erforderlich.
// "server.onNotFound()" darf nicht im Setup des ESP32 Webserver stehen.
// Inklusive Arduino OTA-Updates
/**************************************************************************************/
#include <WebServer.h>
#include <ArduinoOTA.h>
#include <SPIFFS.h>
#define DEBUGGING // Einkommentieren für die Serielle Ausgabe
#ifdef DEBUGGING
#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
WebServer server(80);
void setup() {
DEBUG_B(115200);
DEBUG_F("\nSketchname: %s\nBuild: %s\t\tIDE: %d.%d.%d\n\n",
__FILE__, __TIMESTAMP__, ARDUINO / 10000, ARDUINO % 10000 / 100, ARDUINO % 100 / 10 ? ARDUINO % 100 : ARDUINO % 10);
spiffs();
admin();
Connect();
setupTime();
setupTimerSwitch();
setupHobbsMeter();
ArduinoOTA.onStart([]() {
save(); //Einkommentieren wenn Werte vor dem Update gesichert werden sollen
});
ArduinoOTA.begin();
server.begin();
DEBUG_P("HTTP Server gestartet\n\n");
}
void loop() {
ArduinoOTA.handle();
server.handleClient();
if (millis() < 0x2FFF || millis() > 0xFFFFF0FF) runtime(); // Auskommentieren falls du den Admin Tab nicht nutzen möchtest
localTime(); // Funktionsaufruf Uhrzeit aktualisieren
timerSwitch(); // Funktionsaufruf Zeitschaltuhr
}
Admin.ino
// ****************************************************************
// Sketch Esp32 Admin Modular(Tab)
// created: Jens Fleischer, 2018-07-05
// last mod: Jens Fleischer, 2020-05-01
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Geprüft: bei 4MB Flash
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
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 Admin sollte als Tab eingebunden werden.
// #include "SPIFFS.h" #include <WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP32 Webservers ist erforderlich.
// Die Spiffs.ino muss im ESP32 Webserver enthalten sein
// Funktion "admin();" muss im setup() nach spiffs() aber vor 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();"
/**************************************************************************************/
#include <rom/rtc.h>
const char* const PROGMEM flashChipMode[] = {"QIO", "QOUT", "DIO", "DOUT", "Unbekannt"};
const char* const PROGMEM resetReason[] = {"ERR", "Power on", "Unknown", "Software", "Watch dog", "Deep Sleep", "SLC module", "Timer Group 0", "Timer Group 1",
"RTC Watch dog", "Instrusion", "Time Group CPU", "Software CPU", "RTC Watch dog CPU", "Extern CPU", "Voltage not stable", "RTC Watch dog RTC"
};
void admin() { // Funktionsaufruf "admin();" muss im Setup eingebunden werden
WiFi.mode(WIFI_STA);
File file = SPIFFS.open("/config.json");
if (file) {
String Hostname = file.readStringUntil('\n');
if (Hostname != "") {
WiFi.setHostname(Hostname.substring(2, Hostname.length() - 2).c_str());
ArduinoOTA.setHostname(WiFi.getHostname());
}
}
file.close();
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");
save(); //Einkommentieren wenn Werte vor dem Neustart gesichert werden sollen
ESP.restart();
});
}
void handlerenew() {
server.send(200, "application/json", R"([")" + runtime() + R"(",")" + temperatureRead() + R"(",")" + WiFi.RSSI() + R"("])"); // Json als Array
}
void handleonce() {
if (server.arg(0) != "") {
WiFi.setHostname(server.arg(0).c_str());
File file = SPIFFS.open("/config.json", FILE_WRITE);
file.printf("[\"%s\"]", WiFi.getHostname());
file.close();
}
String fname = String(__FILE__).substring( 3, String(__FILE__).lastIndexOf ('\\'));
String temp = R"({"File":")" + fname.substring(fname.lastIndexOf ('\\') + 1, fname.length()) + R"(", "Build":")" + (String)__DATE__ + " " + (String)__TIME__ +
R"(", "LocalIP":")" + WiFi.localIP().toString() + R"(", "Hostname":")" + WiFi.getHostname() + R"(", "SSID":")" + WiFi.SSID() +
R"(", "GatewayIP":")" + WiFi.gatewayIP().toString() + R"(", "Channel":")" + WiFi.channel() + R"(", "MacAddress":")" + WiFi.macAddress() +
R"(", "SubnetMask":")" + WiFi.subnetMask().toString() + R"(", "BSSID":")" + WiFi.BSSIDstr() + R"(", "ClientIP":")" + server.client().remoteIP().toString() +
R"(", "DnsIP":")" + WiFi.dnsIP().toString() + R"(", "Reset1":")" + resetReason[rtc_get_reset_reason(0)] +
R"(", "Reset2":")" + resetReason[rtc_get_reset_reason(1)] + R"(", "CpuFreqMHz":")" + ESP.getCpuFreqMHz() +
R"(", "FreeHeap":")" + formatBytes(ESP.getFreeHeap()) + R"(", "ChipSize":")" + formatBytes(ESP.getFlashChipSize()) +
R"(", "ChipSpeed":")" + ESP.getFlashChipSpeed() / 1000000 + R"(", "ChipMode":")" + flashChipMode[ESP.getFlashChipMode()] +
R"(", "IdeVersion":")" + ARDUINO + R"(", "SdkVersion":")" + ESP.getSdkVersion() + R"("})";
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 Überlauf
DEBUG_P("Millis Überlauf");
}
previousMillis = currentMillis;
uint32_t sec {(0xFFFFFFFF / 1000) * rolloverCounter + (currentMillis / 1000)};
char buf[20];
snprintf(buf, sizeof(buf), "%d Tag%s %02d:%02d:%02d", sec / 86400, sec < 86400 || sec >= 172800 ? "e" : "", sec / 3600 % 24, sec / 60 % 60, sec % 60);
return buf;
}
Connect.ino
// ****************************************************************
// Sketch Esp32 Connect Modular(Tab) mit optischer Anzeige
// created: Jens Fleischer, 2018-07-06
// last mod: Jens Fleischer, 2020-03-26
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
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 <WebServer.h> muss im Haupttab aufgerufen werden
// Die Funktionalität des ESP32 Webservers ist erforderlich.
// Die Funktion "Connect();" muss im Setup eingebunden werden.
/**************************************************************************************/
const char* ssid = "Netzwerkname"; // << kann bis zu 32 Zeichen haben
const char* password = "PasswortvomNetzwerk"; // << mindestens 8 Zeichen jedoch nicht länger als 64 Zeichen
void Connect() { // Funktionsaufruf "Connect();" muss im Setup nach "spiffs();" eingebunden werden
pinMode(LED_BUILTIN, OUTPUT); // OnBoardLed ESP32 Dev Module
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
digitalWrite(LED_BUILTIN, 1);
delay(250);
digitalWrite(LED_BUILTIN, 0);
delay(250);
DEBUG_F(".");
if (millis() > 10000) {
DEBUG_P("\nVerbindung zum AP fehlgeschlagen\n\n");
ESP.restart();
}
}
DEBUG_P("\nVerbunden mit: " + WiFi.SSID());
DEBUG_P("Esp32 IP: " + WiFi.localIP().toString() + "\n");
}
Lokalzeit.ino
// ****************************************************************
// Sketch Esp32 Lokalzeit Modular(Tab)
// created: Jens Fleischer, 2018-07-15
// last mod: Jens Fleischer, 2020-03-27
// ****************************************************************
// Hardware: Esp32
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
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 <WebServer.h> oder #include <WiFi.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 ntpServer[] = {"fritz.box", "de.pool.ntp.org", "at.pool.ntp.org", "ch.pool.ntp.org", "ptbtime1.ptb.de", "europe.pool.ntp.org"};
bool getTime() { // Zeitzone einstellen https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
configTzTime("CET-1CEST,M3.5.0/02,M10.5.0/03", ntpServer[1]); // deinen NTP Server einstellen (von 0 - 5 aus obiger Liste)
if (!getLocalTime(&tm)) return false;
return true;
}
void setupTime() {
if (!getTime()) {
DEBUG_P("Zeit konnte nicht geholt werden\n");
} else {
getLocalTime(&tm);
}
server.on("/zeit", []() {
server.send(200, "application/json", "\"" + (String)localTime() + "\"");
});
}
char* localTime() {
static char buf[9]; // je nach Format von "strftime" eventuell die Größe anpassen
static time_t lastsec;
getLocalTime(&tm, 50);
if (tm.tm_sec != lastsec) {
lastsec = tm.tm_sec;
strftime (buf, sizeof(buf), "%T", &tm); // http://www.cplusplus.com/reference/ctime/strftime/
time_t now;
if (!(time(&now) % 86400)) getTime(); // einmal am Tag die Zeit vom NTP Server holen o. jede Stunde "% 3600" aller zwei "% 7200"
}
return buf;
}
In diesem Tab einstellen ob dein Device LOW oder HIGH aktiv geschaltet wird.
Schaltuhr.ino
// ****************************************************************
// Sketch Esp32 Schaltuhr Modular(Tab)
// created: Jens Fleischer, 2020-09-05
// last mod: Jens Fleischer, 2020-09-05
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32, Relais Modul o. Mosfet IRF3708 o. Fotek SSR-40 DA
// für Relais Modul
// GND an GND
// IN an T7 = GPIO 27
// VCC an VIN -> je nach verwendeten Esp.. möglich
// Jumper JD-VCC VCC
// alternativ ext. 5V Netzteil verwenden
//
// für Mosfet IRF3708
// Source an GND
// Mosfet Gate an T7 = GPIO 27
//
// für 3V Solid State Relais
// GND an GND
// SSR Input + an T7 = GPIO 27
//
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
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 Zeitschaltuhr sollte als Tab eingebunden werden.
// #include <SPIFFS.h> #include <WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP32 Webservers ist erforderlich.
// Der Lokalzeit Tab ist zum ausführen der Zeitschaltuhr einzubinden.
// Die Funktion "setupTimerSwitch();" muss im Setup aufgerufen werden.
// Zum schalten muss die Funktion "timerSwitch();" im loop(); aufgerufen werden.
/**************************************************************************************/
#include <EEPROM.h>
const auto aktiv = LOW; // LOW für LOW aktive Relais oder HIGH für HIGH aktive (zB. SSR, Mosfet) einstellen
const uint8_t relPin = T7; // Pin für Relais einstellen
const auto count = 25; // Anzahl Schaltzeiten (analog Html Dokument) einstellen 1 bis 100
char switchTime[count * 2][6];
byte switchActive[count], wday[count];
bool fixed, relState {!aktiv};
void setupTimerSwitch() {
digitalWrite(relPin, !aktiv);
pinMode(relPin, OUTPUT);
EEPROM.begin(4);
EEPROM.get(1, fixed);
fixed = fixed > 0 ? 1 : 0;
DEBUG_F("Zeitschaltuhr Automatik %saktiviert\n", fixed ? "de" : "");
if (SPIFFS.exists("/swtime.dat")) { // Einlesen aller Daten falls die Datei im Spiffs vorhanden ist.
File f = SPIFFS.open("/swtime.dat");
f.read(switchActive, sizeof(switchActive));
f.read(wday, sizeof(wday));
while (f.read() != '\n');
for (auto& elem : switchTime) f.readBytesUntil('\n', elem, sizeof(elem));
f.close();
} else { // Sollte die Datei nicht existieren
for (auto i = 0; i < count; i++) {
switchActive[i] = 1; // werden alle Schaltzeiten
wday[i] = ~wday[i]; // und alle Wochentage aktiviert.
}
}
server.on("/timer", HTTP_POST, []() {
if (server.args() == 1) {
switchActive[server.argName(0).toInt()] = server.arg(0).toInt();
printer();
String temp = "\"";
for (auto& elem : switchActive) temp += elem;
temp += "\"";
server.send(200, "application/json", temp);
}
if (server.hasArg("sTime")) {
byte i {0};
char str[count * 14];
strcpy (str, server.arg("sTime").c_str());
char* ptr = strtok(str, ",");
while (ptr != NULL) {
strcpy (switchTime[i++], ptr);
ptr = strtok(NULL, ",");
}
if (server.arg("sDay")) {
i = 0;
strcpy (str, server.arg("sDay").c_str());
char* ptr = strtok(str, ",");
while (ptr != NULL) {
wday[i++] = atoi(ptr);
ptr = strtok(NULL, ",");
}
printer();
}
}
String temp = "[";
for (auto& elem : switchTime) {
if (temp != "[") temp += ',';
temp += (String)"\"" + elem + "\"";
}
temp += ",\"";
for (auto& elem : switchActive) {
temp += elem;
}
for (auto& elem : wday) {
temp += "\",\"";
temp += elem;
}
temp += "\"]";
server.send(200, "application/json", temp);
});
server.on("/timer", HTTP_GET, []() {
if (server.hasArg("tog") && server.arg(0) == "tog") relState = !relState; // Relais1 Status manuell ändern
if (server.hasArg("tog") && server.arg(0) == "fix") { // alle Schalzeiten deaktivieren/aktivieren
fixed = !fixed;
DEBUG_F("Zeitschaltuhr Automatik %saktiviert\n", fixed ? "de" : "");
EEPROM.put(1, fixed);
EEPROM.commit();
}
server.send(200, "application/json", (String)"[\"" + (relState == aktiv) + "\",\"" + localTime() + "\",\"" + fixed + "\"]");
});
}
void printer() {
File file = SPIFFS.open("/swtime.dat", "w");
if (file) {
file.write(switchActive, sizeof(switchActive));
file.write(wday, sizeof(wday));
for (auto& elem : switchTime) file.printf("\n%s", elem);
file.close();
}
}
void timerSwitch() {
static uint8_t lastmin {60}, lastState {aktiv};
hobbsMeter(relState); // Funktionsaufruf Betriebsstundenzähler mit Relais Status
button(relState); // Funktionsaufruf Manueller Taster mit Relais Status
if (tm.tm_min != lastmin && !fixed) {
lastmin = tm.tm_min;
char buf[6];
sprintf(buf, "%.2d:%.2d", tm.tm_hour, tm.tm_min);
for (auto i = 0; i < count * 2; i++) {
if (switchActive[i / 2] && !strcmp(switchTime[i], buf)) {
if (wday[i / 2] & (1 << (tm.tm_wday ? tm.tm_wday - 1 : 6))) relState = i & 1 ? !aktiv : aktiv; // Relais Status nach Zeit ändern
}
}
}
if (relState != lastState) { // Relais schalten wenn sich der Status geändert hat
lastState = relState;
digitalWrite(relPin, relState);
DEBUG_F("Relais a%s\n", digitalRead(relPin) == aktiv ? "n" : "us");
}
}
Spiffs.ino
// ****************************************************************
// Sketch Esp32 Datei Manager spezifisch sortiert Modular(Tab)
// created: Jens Fleischer, 2020-03-26
// last mod: Jens Fleischer, 2020-09-05
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Geprüft: mit 4MB Flash
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
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 Spiffs sollte als Tab eingebunden werden.
// #include <SPIFFS.h> #include <WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP32 Webservers ist erforderlich.
// "server.onNotFound()" darf nicht im Setup des ESP32 Webserver stehen.
// Die Funktion "spiffs();" muss im Setup aufgerufen werden.
/**************************************************************************************/
#include <detail/RequestHandlersImpl.h>
#include <list>
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 spiffs.html hoch.)";
void spiffs() { // Funktionsaufruf "spiffs();" muss im Setup eingebunden werden
SPIFFS.begin(true);
server.on("/list", handleList);
server.on("/format", formatSpiffs);
server.on("/upload", HTTP_POST, sendResponce, handleFileUpload);
server.onNotFound([]() {
if (!handleFile(server.urlDecode(server.uri())))
server.send(404, "text/plain", "FileNotFound");
});
}
void handleList() { // Senden aller Daten an den Client
File root = SPIFFS.open("/");
typedef std::pair<String, int> prop;
std::list<prop> dirList; // Liste anlegen
while (File f = root.openNextFile()) dirList.emplace_back(f.name(), f.size()); // Liste füllen
dirList.sort([](const prop & f, const prop & l) { // Liste sortieren
if (server.arg(0) == "1") {
return f.second > l.second;
} else {
for (uint8_t i = 0; i < 31; i++) {
if (tolower(f.first[i]) < tolower(l.first[i])) return true;
else if (tolower(f.first[i]) > tolower(l.first[i])) return false;
}
return false;
}
});
String temp = "[";
for (auto& p : dirList) {
if (temp != "[") temp += ',';
temp += "{\"name\":\"" + p.first.substring(1) + "\",\"size\":\"" + formatBytes(p.second) + "\"}";
}
temp += R"(,{"usedBytes":")" + formatBytes(SPIFFS.usedBytes() * 1.05) + R"(",)" + // Berechnet den verwendeten Speicherplatz + 5% Sicherheitsaufschlag
R"("totalBytes":")" + formatBytes(SPIFFS.totalBytes()) + R"(","freeBytes":")" + // Zeigt die Größe des Speichers
(SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05)) + R"("}])"; // Berechnet den freien Speicherplatz + 5% Sicherheitsaufschlag
server.send(200, "application/json", temp);
}
bool handleFile(String&& path) {
if (server.hasArg("delete")) {
SPIFFS.remove(server.arg("delete")); // Datei löschen
sendResponce();
return true;
}
if (!SPIFFS.exists("/spiffs.html"))server.send(200, "text/html", HELPER); // ermöglicht das hochladen der spiffs.html
if (path.endsWith("/")) path += "index.html";
return SPIFFS.exists(path) ? ({File f = SPIFFS.open(path); server.streamFile(f, StaticRequestHandler::getContentType(path)); f.close(); true;}) : false;
}
void handleFileUpload() { // Dateien vom Rechnenknecht oder Klingelkasten ins SPIFFS schreiben
static File fsUploadFile;
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
if (upload.filename.length() > 30) {
upload.filename = upload.filename.substring(upload.filename.length() - 30, upload.filename.length()); // Dateinamen auf 30 Zeichen kürzen
}
DEBUG_F("handleFileUpload Name: /%s\n", upload.filename.c_str());
fsUploadFile = SPIFFS.open("/" + server.urlDecode(upload.filename), "w");
} else if (upload.status == UPLOAD_FILE_WRITE) {
DEBUG_F("handleFileUpload Data: %u\n", upload.currentSize);
if (fsUploadFile)
fsUploadFile.write(upload.buf, upload.currentSize);
} else if (upload.status == UPLOAD_FILE_END) {
if (fsUploadFile)
fsUploadFile.close();
DEBUG_F("handleFileUpload Size: %u\n", upload.totalSize);
}
}
void formatSpiffs() { //Formatiert den Speicher
SPIFFS.format();
sendResponce();
}
void sendResponce() {
server.sendHeader("Location", "spiffs.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";
}
bool freeSpace(uint16_t const& printsize) { // Funktion um beim speichern in Logdateien zu prüfen ob noch genügend freier Platz verfügbar ist.
DEBUG_F("Funktion: %s meldet in Zeile: %d FreeSpace: %s\n", __PRETTY_FUNCTION__, __LINE__, formatBytes(SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05)).c_str());
return (SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05) > printsize) ? true : false;
}
Stundenzaehler.ino
// ****************************************************************
// Sketch Esp32 Betriebsstundenzähler Modular(Tab)
// created: Jens Fleischer, 2020-09-05
// last mod: Jens Fleischer, 2020-09-05
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
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 Betriebsstundenzähler sollte als Tab eingebunden werden.
// #include <SPIFFS.h> #include <WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP32 Webservers ist erforderlich.
// Der Spiffs Tab ist zum ausführen des Betriebsstundenzähler einzubinden.
// Die Funktion "setupHobbsMeter();" muss im Setup aufgerufen werden.
/**************************************************************************************/
uint32_t totalmin;
uint16_t watt;
void setupHobbsMeter() {
if (SPIFFS.exists("/hobbs.txt")) {
File file = SPIFFS.open("/hobbs.txt", "r"); // Betriebstunden(minuten) beim Neustart einlesen
totalmin = file.parseInt();
watt = file.parseInt();
file.close();
}
server.on("/hobbs", HTTP_GET, []() {
uint32_t power;
if (server.argName(0) == "watt") {
watt = server.arg(0).toInt();
save();
}
if (server.argName(0) == "reset") {
totalmin = 0; // Betriebsstundenzähler zurücksetzen
save();
}
power = (watt * totalmin) / 6000;
server.send(200, "application/json", (String)"[\"" + totalmin / 60 + "," + totalmin / 6 % 10 + "\",\"" + watt + "\",\"" + power / 10 + "." + power % 10 + "\"]");
});
}
void hobbsMeter(bool &state) { // Aufrufen mit Relais Status
static uint32_t lastmin, previousMillis[] {0, 0};
uint32_t currentMillis {millis()};
if (currentMillis - previousMillis[0] >= 6e4) {
previousMillis[0] = currentMillis;
if (state == aktiv) totalmin++; // Betriebstundenzähler Relais wird um eine Minute erhöht
}
if (currentMillis - previousMillis[1] >= 864e5 && totalmin != lastmin) { // einmal am Tage Betriebsstunden in Datei schreiben wenn sich der Wert geändert hat
previousMillis[1] = currentMillis;
lastmin = totalmin;
save();
}
}
void save() {
File file = SPIFFS.open("/hobbs.txt", "w"); // Betriebstunden(minuten) speichern
if (file && freeSpace(100)) { // prüfen ob ausreichend Speicherplatz frei ist
file.printf("%u\n%d\n", totalmin, watt);
file.close();
}
}
Der Taster ist optional.
Taster.ino
// ****************************************************************
// Sketch Esp32 Zeitschaltuhr Taster Modular(Tab)
// created: Jens Fleischer, 2020-09-05
// last mod: Jens Fleischer, 2020-09-05
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32
// T9 = GPIO 32 Anschluss Taster vom T9 auf GND
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
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 Zeitschaltuhr Taster sollte als Tab eingebunden werden.
// Die Funktion "button(relState);" muss in "timerSwitch();" aufgerufen werden.
/**************************************************************************************/
const byte inputPIN {T9}; // Pin für Taster einstellen
void button(bool &state) { // Aufrufen mit Relais Status
pinMode(inputPIN, INPUT_PULLUP); // oder 10k Pullup-Widerstand von VCC zum inputPIN
static bool previousStatus {0};
static uint32_t debounceMillis;
uint32_t currentMillis {millis()};
if (currentMillis - debounceMillis >= 50) { // 50 ms Taster Entprellzeit
debounceMillis = currentMillis;
bool currentStatus = digitalRead(inputPIN);
if (!currentStatus && currentStatus != previousStatus) {
state = !state; // Relais Status toggeln
}
previousStatus = currentStatus;
}
}
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.0">
<link rel="stylesheet" href="style32.css">
<title>ESP32 Admin</title>
<SCRIPT>
window.addEventListener('load', () => {
document.querySelector('#spiff').addEventListener('click', () => {
window.location = '/spiffs.html';
});
document.querySelector('#home').addEventListener('click', () => {
window.location = '/';
});
document.querySelector('#restart').addEventListener('click', () => {
if (confirm('Bist du sicher!')) re('restart');
});
document.querySelector('#reconnect').addEventListener('click', re.bind(this, 'reconnect'));
document.querySelector('#hostbutton').addEventListener('click', check.bind(this, document.querySelector('input')));
once();
var output = document.querySelector('#note');
function once(arg1, arg2) {
fetch('/admin/once', {
method: 'POST',
body: arg1
}).then( resp => {
return resp.json();
}).then( obj => {
output.innerHTML = '';
output.classList.remove('note');
document.querySelector('form').reset();
if (arg1 == undefined) wait = window.setInterval(renew, 1000);
if (arg2 == 'reconnect') re(arg2);
document.querySelector('#file').innerHTML = obj['File'];
document.querySelector('#build').innerHTML = obj['Build'];
document.querySelector('#local').innerHTML = obj['LocalIP'];
document.querySelector('#host').innerHTML = obj['Hostname'];
document.querySelector('#ssid').innerHTML = obj['SSID'];
document.querySelector('#gateway').innerHTML = obj['GatewayIP'];
document.querySelector('#kanal').innerHTML = obj['Channel'];
document.querySelector('#mac').innerHTML = obj['MacAddress'];
document.querySelector('#subnet').innerHTML = obj['SubnetMask'];
document.querySelector('#bss').innerHTML = obj['BSSID'];
document.querySelector('#client').innerHTML = obj['ClientIP'];
document.querySelector('#dns').innerHTML = obj['DnsIP'];
document.querySelector('#reset1').innerHTML = obj['Reset1'];
document.querySelector('#reset2').innerHTML = obj['Reset2'];
document.querySelector('#cpufreq').innerHTML = obj['CpuFreqMHz'];
document.querySelector('#freeheap').innerHTML = obj['FreeHeap'];
document.querySelector('#csize').innerHTML = obj['ChipSize'];
document.querySelector('#cspeed').innerHTML = obj['ChipSpeed'];
document.querySelector('#cmode').innerHTML = obj['ChipMode'];
document.querySelector('#ide').innerHTML = obj['IdeVersion'].replace(/(\d)(\d)(\d)(\d)/,obj['IdeVersion'][3]!=0 ? '$1.$3.$4' : '$1.$3.');
document.querySelector('#sdk').innerHTML = obj['SdkVersion'];
}).catch(function (err) {
re();
});
}
function renew() {
fetch('admin/renew').then( resp => {
return resp.json();
}).then( array => {
document.querySelector('#runtime').innerHTML = array[0];
document.querySelector('#temp').innerHTML = array[1];
document.querySelector('#rssi').innerHTML = array[2];
});
}
function check(inObj) {
!inObj.checkValidity() ? (output.innerHTML = inObj.validationMessage, output.classList.add('note')) : (once(inObj.value, 'reconnect'));
}
function re(arg) {
window.clearInterval(wait);
fetch(arg);
output.classList.add('note');
if (arg == 'restart') {
output.innerHTML = 'Der Server wird neu gestartet. Die Daten werden in 10 Sekunden neu geladen.';
setTimeout(once, 10000);
} else if (arg == 'reconnect') {
output.innerHTML = 'Die WiFi Verbindung wird neu gestartet. Daten werden in 5 Sekunden neu geladen.';
setTimeout(once, 5000);
} else {
output.innerHTML = 'Es ist ein Verbindungfehler aufgetreten. Es wird versucht neu zu verbinden.';
setTimeout(once, 2000);
}
}
});
</SCRIPT>
</head>
<body>
<h1>ESP32 Admin Page</h1>
<main>
<section id="left">
<span>Runtime ESP:</span>
<span>WiFi RSSI:</span>
<span>CPU Temperatur:</span>
<span>Sketch Name:</span>
<span>Sketch Build:</span>
<span>IP address:</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>Client IP:</span>
<span>DnsIP:</span>
<span>Reset CPU 1:</span>
<span>Reset CPU 2:</span>
<span>CPU Freq:</span>
<span>FreeHeap:</span>
<span>FlashSize:</span>
<span>FlashSpeed:</span>
<span>FlashMode:</span>
<span>Arduino IDE Version:</span>
<span>SDK-Version:</span>
</section>
<section>
<data id="runtime">00:00:00</data>
<div>
<data id="rssi"></data>
dBm
</div>
<div>
<data id="temp"></data>
°C
</div>
<data id="file">?</data>
<data id="build">0</data>
<data id="local">0</data>
<data id="host">?</data>
<data id="ssid">?</data>
<data id="gateway">0</data>
<data id="kanal">0</data>
<data id="mac">0</data>
<data id="subnet">0</data>
<data id="bss">0</data>
<data id="client">0</data>
<data id="dns">0</data>
<data id="reset1">0</data>
<data id="reset2">0</data>
<div>
<data id="cpufreq"></data>
MHz
</div>
<data id="freeheap">0</data>
<data id="csize">0</data>
<div>
<data id="cspeed"></data>
MHz
</div>
<data id="cmode">0</data>
<data id="ide">0</data>
<data id="sdk">0</data>
</section>
</main>
<div>
<button class="button" id="spiff">Spiffs</button>
<button class="button" id="home">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 class="button" type="button" id="hostbutton">Name Senden</button>
</form>
</div>
<div>
<button class="button" id="reconnect">WiFi Reconnect</button>
<button class="button" id="restart">ESP Restart</button>
</div>
</body>
</html>
In der "index.html" die Anzahl der Schaltzeiten (analog Sketch) einstellen. (1 bis 100).
index.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="style32.css">
<title>Timer</title>
<style>
body {
margin: 0;
}
main {
padding: 0 2em;
box-shadow: 5px 3px 10px #4e4d4d;
background-color: #333;
color: #15dfdf;
max-width: 400px;
}
div {
display: flex;
align-items: center;
}
span {
padding: 0.5em;
}
input {
background-color: inherit;
font-size: 3em;
color: inherit;
border: solid #555;
height: auto;
width: auto;
font-weight: bold;
}
time {
text-shadow: 1px 1px 1px #777;
font-size: 1.4em;
font-weight: bold;
}
button {
background-color: #333;
color: #33cccc;
cursor: pointer;
border: outset #999;
width: 100%;
height: 2.5em;
font-size: 1em;
margin: 0;
}
button:only-of-type {
width: 25%;
}
svg {
width: 3.5em;
}
label {
font-style: italic;
color: #777;
}
input:checked+label {
color: #15dfdf;
}
div+span {
margin-left: 1em;
display: flex;
justify-content: space-evenly
}
#top {
justify-content: space-evenly;
position: sticky;
top: 0;
cursor: pointer;
background-color: #333;
z-index: 2;
}
#below {
position: sticky;
bottom: 0;
padding-bottom: .6em;
background-color: #333;
}
.no {
display: none;
}
#view {
position: sticky;
flex-direction: column;
top: 4.4em;
background-color: #333;
border: solid #555;
margin-bottom: .5em;
padding: .5em;
z-index: 1;
}
#reset {
cursor: pointer;
border: solid #555;
}
[name^=bu] {
width: 2em;
cursor: pointer;
}
.greyed {
color: #777 !important;
}
#watt {
font-size: 1em;
width: 2.6em;
text-align: end;
}
#kwh {
cursor: default;
border: solid #f00;
margin: .5em;
padding: .3em;
}
.edit, .noedit {
display: flex;
justify-content: center;
position: absolute;
min-width: 93%;
background-color: red;
color: #fff;
padding: 1em;
border-radius: 0.5em;
}
.edit:after {
content: 'Eingabe gespeichert';
}
.noedit:after {
content: 'Nur 4 Ziffern 0 - 9 eingeben';
}
#tip {
position: sticky;
justify-content: center;
top: 50%;
z-index: 9;
height: 0;
}
@media only screen and (max-width: 600px) {
input {
font-size: 2.4em;
}
}
</style>
<script>
var count = 25; <!--Anzahl Schaltzeiten(analog Sketch) einstellen 1 bis 100 -->
var d = document, fixed;
d.addEventListener('DOMContentLoaded', () => {
dom(), send();
btn = d.querySelectorAll(`button`);
btn[1].addEventListener('click', () => {
let formData = new FormData();
let arr = [];
formData.append('sTime', Array.from(d.querySelectorAll('input[type=time]')).map(x => x.value != 0 ? x.value : 0));
for (var i = 0; i < count; i++) {
let x = 0;
d.querySelectorAll(`input[name=c${i}]`).forEach((el, i) => { if (el.checked) x = x | (1 << i) });
arr.push(x);
}
formData.append(`sDay`, arr);
send(formData);
});
btn[0].addEventListener('click', () => {
event.stopPropagation();
renew('tog');
});
btn[2].addEventListener('click', renew.bind(this, 'fix'));
d.querySelector('#top').addEventListener('click', () => {
if (!d.getElementById('view').classList.toggle('no')) hours();
});
d.querySelector('#reset').addEventListener('click', () => {
if (confirm('Bist du sicher!')) hours(event);
});
d.querySelector('input').addEventListener('blur', () => {
let inObj = d.querySelector('input');
!inObj.checkValidity() ? out('noedit') : hours(parseInt(inObj.value));
});
for (var i = 0; i < count;) d.querySelector(`[name=bu${i++}]`).addEventListener('click', setActive);
}, renew(), setInterval(renew, 1000));
function dom() {
var buf = '';
for (var i = 0; i < count; i++) {
buf += `<div id=ak${i}><span name=bu${i}></span><input type="time" id="sz${i * 2}" value=><span> -- </span><input type="time" id="sz${i * 2 + 1}" value=></div><span id="t${i}">`;
['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'].forEach(v => {
buf += `<input type="checkbox" name="c${i}"><label>${v} </label>`;
});
buf += '</span>';
}
buf += '<div id="below"><button>⏰ Zeiten Speichern</button><button>-</button></div>';
d.querySelector('main').insertAdjacentHTML('beforeend', buf);
}
function setActive() {
let formData = new FormData();
formData.append(this.parentNode.id.substr(2, 5), this.textContent == 'ON' ? '0' : '1');
send(formData);
}
function out(arg) {
let el = d.querySelector('#err').classList;
el.add(arg);
setTimeout(() => {
el.remove(arg);
}, 5e3);
}
function send(arg) {
fetch('/timer', {
method: 'post',
body: arg
}).then(resp => {
if (resp.ok && arg && arg.has('sTime')) out('edit');
return resp.json();
}).then(array => {
if (array.length > count) {
array.forEach((v, i) => {
if (i < count * 2) d.getElementById(`sz${i}`).value = v;
if (i == count * 2) setTimeout(getActive(v),100);
if (i > count * 2) {
let el = d.getElementsByName(`c${i - count * 2 - 1}`);
for (let k in el) {
v & (1 << k) ? el[k].checked = true : el.checked = false;
}
}
});
}
else {
getActive(array);
}
});
}
function getActive(arg) {
for (var i = 0; i < count; i++) {
if (arg.length > 0) d.querySelector(`[name=bu${i}]`).textContent = (arg[i] % 2 ? 'ON' : 'OFF');
let el = d.getElementById(`ak${i}`).classList;
fixed == 0 ? arg[i] % 2 ? el.remove('greyed') : el.add('greyed') : el.add('greyed');
el = d.getElementById(`t${i}`).childNodes;
fixed == 0 ? el.forEach(v => { arg[i] % 2 ? v.classList.remove('greyed') : v.classList.add('greyed') }) : el.forEach(v => { v.classList.add('greyed') });
}
}
function renew(arg) {
fetch(`timer?tog=${arg}`).then(resp => {
return resp.json();
}).then(array => {
d.querySelector('polygon').style.fill = array[0] == 0 ? '#eee' : '#ff0';
btn[0].innerHTML = array[0] == 0 ? '✋ ON' : '✋ OFF';
d.querySelector('time').innerHTML = array[1];
btn[2].innerHTML = array[2] == 0 ? '✖ Auto inaktiv' : '⏳ Auto aktiv';
fixed = array[2];
if (arg == 'fix') fixed == 1 ? getActive(0) : send();
});
}
function hours(arg) {
fetch(`hobbs?${event.target.id}=${arg}`).then(resp => {
if (resp.ok && arg) out('edit');
return resp.json();
}).then(array => {
d.querySelector('strong').innerHTML = array[0] + ' h';
d.getElementById('watt').value = array[1];
d.getElementById('kwh').innerHTML = array[2] + ' kWh';
});
}
</script>
</head>
<body>
<main>
<div id="top">
<button>-</button>
<svg viewBox="0 0 12 15">
<polygon points="10.421,6.754 6.498,6.75 12.058,2.357 9.734,2.357 1.687,8.436 5.584,8.436 0,14.02"></polygon>
</svg>
<time>00:00:00</time>
</div>
<div id="tip"><span id="err"></span></div>
<div id="view" class="no">
<span>Betriebsstunden <strong></strong></span>
<span>Verbrauch bei <input id="watt" pattern="[0-9]{1,4}"> Watt <span id="kwh"></span></span>
<span id="reset">❌ Zähler zurücksetzen</span>
</div>
</main>
</body>
</html>
spiffs.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="style32.css">
<title>Esp32 Datei Manager</title>
<script>
var to = JSON.parse(localStorage.getItem('sortBy'));
document.addEventListener('DOMContentLoaded', () => {
list(to);
fs.addEventListener('change', () => {
for (var bytes = 0, j = 0; j < event.target.files.length; j++) bytes += event.target.files[j].size;
for (var out = `${bytes} Byte`, i = 0, circa = bytes / 1024; circa > 1; circa /= 1024) out = circa.toFixed(2) + [' KB', ' MB', ' GB'][i++];
if (bytes > free) {
si.innerHTML = `<li><b> ${out}</b><strong> Ungenügend Speicher frei</strong></li>`;
up.setAttribute('disabled', 'disabled');
}
else {
si.innerHTML = `<li><b>Dateigröße:</b> ${out}</li>`;
up.removeAttribute('disabled');
}
});
btn.addEventListener('click', () => {
if (!confirm(`Wirklich formatieren? Alle Daten gehen verloren.\nDu musst anschließend spiffs.html wieder laden.`)) event.preventDefault();
});
});
function list(arg){
let myList = document.querySelector('main');
fetch('list?sort='+arg).then( (response) => {
return response.json();
}).then((json) => {
myList.innerHTML = '';
for (var i = 0; i < json.length - 1; i++) {
let dir = `<li><a href ="${json[i].name}">${json[i].name}</a><small> ${json[i].size}</small><a href ="${json[i].name}"download="${json[i].name}"> Download </a>`;
if (json[i].name != 'spiffs.html') dir += `or <a href ="?delete=/${json[i].name}">Delete </a>`;
myList.insertAdjacentHTML('beforeend', dir);
}
myList.insertAdjacentHTML('beforeend', `<li><b id="so">${to ? '▼' : '▲'} SPIFFS</b> belegt ${json[i].usedBytes} von ${json[i].totalBytes}`);
free = json[i].freeBytes;
so.addEventListener('click', () => {
list(to=++to%2);
localStorage.setItem('sortBy', JSON.stringify(to));
});
});
}
</script>
</head>
<body>
<h2>ESP32 Datei Manager</h2>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input id="fs" type="file" name="upload[]" multiple>
<input id="up" type="submit" value="Upload" disabled>
</form>
<div>
<span id="si"></span>
<main></main>
</div>
<form action="/format" method="POST"><input id="btn" type="submit" value="Format SPIFFS"></form>
</body>
</html>
style32.css
/*For more information visit: https://fipsok.de*/
body {
font-family: sans-serif;
background-color: #a9a9a9;
display: flex;
flex-flow: column;
align-items: center;
}
h1,h2 {
color: #e1e1e1;
text-shadow: 2px 2px 2px black;
}
li {
background-color: #7cfc00;
list-style-type: none;
margin-bottom: 10px;
padding-right: 5px;
border-top: 3px solid #7cfc00;
border-bottom: 3px solid #7cfc00;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.7);
}
li a:first-child, li b {
background-color: #ff4500;
font-weight: bold;
color: white;
text-decoration: none;
padding: 0 5px;
border-top: 3.2px solid #ff4500;
border-bottom: 3.6px solid #ff4500;
text-shadow: 2px 2px 1px black;
cursor:pointer;
}
li strong {
color: red;
}
input {
height: 35px;
font-size: 13px;
}
h1+main {
display: flex;
}
section {
display: flex;
flex-direction: column;
padding: 0.2em;
}
#left {
align-items: flex-end;
text-shadow: 1px 1px 2px #757474;
}
.note {
background-color: salmon;
padding: 0.5em;
margin-top: 1em;
text-align: center;
max-width: 320px;
border-radius: 0.5em;
}
.button {
width: 130px;
height: 40px;
font-size: 16px;
margin-top: 1em;
cursor: pointer;
background-color: #adff2f;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.7);
}
[type=submit] {
height: 40px;
min-width: 70px;
}
[value^=Format] {
background-color: #ddd;
}
[title] {
background-color: silver;
font-size: 16px;
width: 125px;
}
alles gut geschrieben, leider bekomme ich in der Zeitschaltuhr Single (alte Version) weder Automatik eingeschaltet noch verändertet Zeiten gespeichert. Was mache ich falsch?
Im Notfall sende ich dir mein Programm gerne zu, zum Prüfen.
Gruß
Rene
Antwort:
Bitte beachte: Esp32 Arduino Core 1.0.0 - 1.0.4
Gruß Fips
grosses Lob fuer Deine Arbeit. Da hast Du viel Zeit und Muehe investiert. Ich habe noch selten ein aehnlich vollstaendiges, umfangreiches und durchdachtes Projekt gesehen.
Daumen hoch!
VG Rob
Antwort:
Danke für dein Feedback!
Gruß Fips
Was ich nicht ganz checke, ist die Option, wie ich den Verbraucher von wo anders zusätzlich schalten kann.
Ich habe ein Gewächshaus mit Absaugung. Nun möchte ich mit der Zeitschaltuhr diese Absaugung steuern.
Zusätzlich möchte ich die Luftfeuchtigkeit überwachen. Wenn diese einen bestimmten Wert überschreitet, möchte ich die Absaugung so lange im Dauerbetrieb lassen, bis der gewünschte Wert wieder unterschritten wird. Dann soll die Zeitschaltuhr wieder übernehmen.
Das müsste doch mit dem bereits integrierten Schalter funktionieren?
Gruß Joachim
Antwort:
Die "Taster.ino" ist für einen mechanischen Taster geschrieben. Eine anstehende Schaltzeit würde den Zustand ändern wenn die Schaltuhr nicht vorher im Webinterface auf Inaktiv gestellt wird.
Das heißt, die Variablen "relState" und "fixed" müssen für die Zeit der Lüftung geändert werden.
Gruß Fips