From 2348fc59c74d2078b5aeac62f4480cde44843bc8 Mon Sep 17 00:00:00 2001 From: toptah Date: Mon, 9 Mar 2026 20:05:35 +0100 Subject: [PATCH] =?UTF-8?q?Hinzuf=C3=BCgen=20WebServer=20und=20AP=20zur=20?= =?UTF-8?q?SSID=20Konfiguration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/copilot-instructions.md | 50 +++++++++ compile.log | Bin 0 -> 255874 bytes platformio.ini | 1 + src/Admin.ino | 101 +++++++++++++++++ src/Config.ino | 1 + src/Connect.ino | 185 ++++++++++++++++++++++++++++++++ src/KeyPatch.ino | 44 +++++++- src/LittleFS.ino | 173 +++++++++++++++++++++++++++++ src/Webserver.ino | 54 ++++++++++ src/html/admin.html | 172 +++++++++++++++++++++++++++++ src/html/fs.html | 77 +++++++++++++ src/style.css | 120 +++++++++++++++++++++ 12 files changed, 975 insertions(+), 3 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 compile.log create mode 100644 src/Admin.ino create mode 100644 src/Connect.ino create mode 100644 src/LittleFS.ino create mode 100644 src/Webserver.ino create mode 100644 src/html/admin.html create mode 100644 src/html/fs.html create mode 100644 src/style.css diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..ac9ef9b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,50 @@ +--- +# Workspace instructions for the KeyPatch ESP8266 project + +This project is a PlatformIO/Arduino sketch for an ESP8266 (Wemos D1 mini) that +implements a captive-portal Wi‑Fi configurator, filesystem manager, and NeoPixel +handler. The code is organised into multiple `.ino` tabs under `src/` and uses +several libraries (LittleFS, ESP8266WebServer, DNSServer, Adafruit NeoPixel, PCF8575, +etc.). + +## Building and flashing + +- Build with PlatformIO via the task or from the command line: + + ```powershell + & "C:\Users\User\.platformio\penv\Scripts\platformio.exe" run + ``` + + or `pio run`/`platformio run` from a shell that has the `platformio` command. +- Upload to the board using `platformio run -t upload` or through the built-in VS Code task. +- If you see `exit code 1` from the upload task, the problem is generally a connection issue to the + device, not a compilation error; the project compiles cleanly with the current sources. + +## Code conventions + +- All Arduino headers are included in the main tab (`KeyPatch.ino`). Helper tabs + such as `Connect.ino`, `LittleFS.ino`, etc. depend on those includes being present. +- `setup()` must call `connectWifi()` and `setupFS()` as indicated in the comments. +- Serial debugging is used heavily; the baud rate is 115200 by default. +- When the soft‑AP named `EspConfig` is started, the IP address is printed to the + serial monitor (e.g. `AP-IP-Adresse: 172.217.28.1`). + +## Common tasks + +1. Change Wi‑Fi parameters in `Connect.ino` or use the captive portal. +2. Upload `fs.html` via the web interface to manage LittleFS content. +3. Edit hardware configurations under `hardware/` (FreeCAD files). + +## Troubleshooting + +- Compilation errors are rare; if you encounter them, run the verbose build + (`platformio run -v`) and inspect `compile.log` for `error:` messages. +- LittleFS operations require `LittleFS.begin()` to succeed; the helper tab + `LittleFS.ino` includes debug prints. + +## Personalisation + +This file is intended to help any contributor or future self understand the +project layout, build commands, and where to look for the various features. +Feel free to update it with new instructions as the sketch evolves. +--- diff --git a/compile.log b/compile.log new file mode 100644 index 0000000000000000000000000000000000000000..aed9db5f72e9b8cdd8c69126ce91051d4226c567 GIT binary patch literal 255874 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 V=o37}ary/#include #include müssen im Haupttab aufgerufen werden +// Die Funktionalität des ESP8266 Webservers ist erforderlich. +// Die Spiffs.ino muss im ESP8266 Webserver enthalten sein +// Funktion "admin();" muss im setup() nach setupFS()/spiffs() 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();" +/**************************************************************************************/ + +//#define LittleFS SPIFFS // Einkommentieren wenn SPIFFS als Filesystem genutzt wird + +const char* const PROGMEM flashChipMode[] = {"QIO", "QOUT", "DIO", "DOUT", "Unbekannt"}; + +void admin() { // Funktionsaufruf "admin();" muss im Setup eingebunden werden + File file = LittleFS.open("/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"); + //save(); //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() { + if (server.arg(0) != "") { + WiFi.hostname(server.arg(0)); + File f = LittleFS.open("/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() + + "\", \"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; +} diff --git a/src/Config.ino b/src/Config.ino index 690cfe5..6f6541b 100644 --- a/src/Config.ino +++ b/src/Config.ino @@ -5,4 +5,5 @@ #define NEOPIXEL_PIN D6 #define BRIGHTNESS 100 #define BLINK_INTERVAL 500 // Blink interval in milliseconds (0.5 seconds) +#define SERIAL_SPEED 115200 // Serial Baudrate for ESP8266 diff --git a/src/Connect.ino b/src/Connect.ino new file mode 100644 index 0000000..f95a38a --- /dev/null +++ b/src/Connect.ino @@ -0,0 +1,185 @@ + +// **************************************************************** +// Sketch Esp8266 Login Manager mit Captive Portal und optischer Anzeige +// created: Jens Fleischer, 2021-01-05 +// last mod: Jens Fleischer, 2021-11-29 +// For more information visit: https://fipsok.de +// **************************************************************** +// Hardware: Esp8266 +// Software: Esp8266 Arduino Core 2.6.3 / 2.7.4 / 3.0.2 +// Getestet auf: Nodemcu, Wemos D1 Mini Pro +/****************************************************************** + Copyright (c) 2021 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 Login Manager sollte als Tab eingebunden werden. +// #include #include müssen im Haupttab aufgerufen werden +// Die Funktionalität des ESP8266 Webservers und des LittleFS Tab ist erforderlich. +// Die Funktion "connectWifi();" muss im Setup eingebunden werden. +// Die Oneboard LED blinkt beim Verbindungsaufbau zum Netzwerk und leuchtet im AP Modus dauerhaft. +// Die Zugangsdaten werden nicht menschenlesbar im Dateisystem gespeichert. +/**************************************************************************************/ + +/** + Folgendes muss im Webserver Tab vor dem "setup()" eingefügt werden. + + #include + const byte DNS_PORT = 53; + DNSServer dnsServer; + + Der DNS Server muss im loop aufgerufen werden. + + void loop() { + dnsServer.processNextRequest(); + reStation(); + } +*/ + +//#define CONFIG // Einkommentieren wenn der ESP dem Router die IP mitteilen soll. + +#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 + +const char HTML[] PROGMEM = R"( + + + + + + Login Manager + + +

Zugangsdaten

+
+

+ +

+

+ +

+
+ + + +)"; +const char JSON[] PROGMEM = R"("

Die Zugangsdaten wurden übertragen. Eine Verbindung zum Netzwerk wird hergestellt.

")"; + +char ssid[33] {" "}; +char password[65]; +constexpr char key {129}; + +void connectWifi() { + IPAddress apIP(172, 217, 28, 1); + IPAddress netMsk(255, 255, 255, 0); + File file = LittleFS.open("/wifi.dat", "r"); + if (file) { + file.read(reinterpret_cast(&ssid), sizeof(ssid)); + file.read(reinterpret_cast(&password), sizeof(password)); + file.close(); + for (auto &c : ssid) c ^= key; // Dechiffrierung SSID + for (auto &c : password) c ^= key; // Dechiffrierung Passwort + } + WiFi.disconnect(); + WiFi.persistent(false); // Auskommentieren wenn Netzwerkname und Passwort in den Flash geschrieben werden sollen. + WiFi.mode(WIFI_STA); // Station-Modus + WiFi.begin(ssid, password); +#ifdef CONFIG + WiFi.config(staticIP, gateway, subnet, dns); +#endif + uint8_t i {0}; + while (WiFi.status() != WL_CONNECTED) { + pinMode(LED_BUILTIN, OUTPUT); // OnBoardLed Nodemcu, Wemos D1 Mini Pro + digitalWrite(LED_BUILTIN, 0); // Led blinkt während des Verbindungsaufbaus + delay(500); + digitalWrite(LED_BUILTIN, 1); + delay(500); + Serial.printf(" %i sek\n", ++i); + if (WiFi.status() == WL_NO_SSID_AVAIL || i > 29) { // Ist die SSID nicht erreichbar, wird ein eigenes Netzwerk erstellt. + digitalWrite(LED_BUILTIN, 0); // Dauerleuchten der Led zeigt den AP Modus an. + WiFi.disconnect(); + WiFi.mode(WIFI_AP); // Soft-Access-Point-Modus + Serial.println(PSTR("\nVerbindung zum Router fehlgeschlagen !\nStarte Soft AP")); + WiFi.softAPConfig(apIP, apIP, netMsk); + if (WiFi.softAP("EspConfig")) { + Serial.println(PSTR("Verbinde dich mit dem Netzwerk \"EspConfig\".\n")); + } + break; + } + } + if (WiFi.status() == WL_CONNECTED) { + Serial.printf(PSTR("\nVerbunden mit: %s\nEsp8266 IP: %s\n"), WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); + } + dnsServer.start(DNS_PORT, "*", apIP); + server.on("/wifisave", HTTP_POST, handleWifiSave); + server.onNotFound(handleRoot); +} + +void handleWifiSave() { + if (server.hasArg("ssid") && server.hasArg("passwort")) { + strcpy(ssid, server.arg(0).c_str()); + strcpy(password, server.arg(1).c_str()); + for (auto &c : ssid) c ^= key; // Chiffrierung SSID + for (auto &c : password) c ^= key; // Chiffrierung Passwort + File file = LittleFS.open("/wifi.dat", "w"); + file.write(reinterpret_cast(&ssid), sizeof(ssid)); + file.write(reinterpret_cast(&password), sizeof(password)); + file.close(); + server.send(200, "application/json", JSON); + delay(500); + connectWifi(); + } +} + +void handleRoot() { + if (WiFi.status() != WL_CONNECTED) { // Besteht keine Verbindung zur Station wird das Formular gesendet. + server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + server.sendHeader("Pragma", "no-cache"); + server.sendHeader("Expires", "-1"); + server.send(200, "text/html", HTML); + } + else { + if (!handleFile(server.urlDecode(server.uri()))) { + if (server.urlDecode(server.uri()).endsWith("/")) sendResponce(); + server.send(404, "text/plain", "FileNotFound"); + } + } +} + +void reStation() { // Der Funktionsaufruf "reStation();" sollte im "loop" stehen. + static unsigned long previousMillis; // Nach Stromausfall startet der Esp.. schneller als der Router. + constexpr unsigned long INTERVAL (3e5); // Im AP Modus aller 5 Minuten prüfen ob der Router verfügbar ist. + if (millis() - previousMillis >= INTERVAL) { + previousMillis += INTERVAL; + if (WiFi.status() != WL_CONNECTED) connectWifi(); + } +} diff --git a/src/KeyPatch.ino b/src/KeyPatch.ino index fe61a28..8c2be68 100644 --- a/src/KeyPatch.ino +++ b/src/KeyPatch.ino @@ -1,9 +1,24 @@ #include #include #include +#include +#include +#include #include "Config.ino" +#include -#define AMOUNTOFPORTS 8 // currently max 16 ports are supported +// Globale Deklarationen für alle Tabs +ESP8266WebServer server(80); +const byte DNS_PORT = 53; +DNSServer dnsServer; + +String sketchName() { // Dateiname für den Admin Tab ab EspCoreVersion 2.6.0 + char file[sizeof(__FILE__)] = __FILE__; + char * pos = strrchr(file, '.'); *pos = '\0'; + return file; +} + +#define AMOUNTOFPORTS 16 // currently max 16 ports are supported #define EXPANDER1ADDRESS 0x21 // address of expander 1 PCF8575 expander1(EXPANDER1ADDRESS); @@ -14,8 +29,12 @@ byte number=0; extern Adafruit_NeoPixel* pixels; void setup() { - Serial.begin(9600); - Serial.println("Starte 16-Port IO Erweiterung..."); + Serial.begin(SERIAL_SPEED); + delay(100); + Serial.printf("\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()); + + Serial.printf("\nKeyPatch ESP8266 WebServer\n"); //Set UP 12C Communication Wire.begin(); for (byte adress=8; adress<120; adress++) @@ -57,10 +76,29 @@ void setup() { }; pixels->show(); +// SetUp WebServer + setupFS(); // setupFS(); oder spiffs(); je nach Dateisystem + connectWifi(); + admin(); + ArduinoOTA.onStart([]() { + //save(); // Wenn Werte vor dem Neustart gespeichert werden sollen + }); + ArduinoOTA.begin(); + server.begin(); + + + Serial.println("Setup End..."); } void loop() { + ArduinoOTA.handle(); + server.handleClient(); + if (millis() < 0x2FFF || millis() > 0xFFFFF0FF) { // Die Funktion "runtime()" wird nur für den Admin Tab gebraucht. + runtime(); // Auskommentieren falls du den Admin Tab nicht nutzen möchtest. + } + dnsServer.processNextRequest(); + reStation(); // Erst 16 Ports aus PCF8575 einlesen for (uint8_t i = 0; i < AMOUNTOFPORTS; i++) { portStates[i] = expander1.read(i); diff --git a/src/LittleFS.ino b/src/LittleFS.ino new file mode 100644 index 0000000..e106855 --- /dev/null +++ b/src/LittleFS.ino @@ -0,0 +1,173 @@ + +// **************************************************************** +// Sketch Esp8266 Filesystem Manager spezifisch sortiert Modular(Tab) +// created: Jens Fleischer, 2020-06-08 +// last mod: Jens Fleischer, 2020-09-02 +// For more information visit: https://fipsok.de +// **************************************************************** +// Hardware: Esp8266 +// Software: Esp8266 Arduino Core 2.6.0 - 2.7.4 +// 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 #include 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 +#include + +const char WARNING[] PROGMEM = R"(

Der Sketch wurde mit "FS:none" kompilliert!)"; +const char HELPER[] PROGMEM = R"(
+
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; + typedef tuple records; + list 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"; + if (path == "/spiffs.html") sendResponce(); // Vorrübergehend für den Admin Tab + return LittleFS.exists(path) ? ({File f = LittleFS.open(path, "r"); server.streamFile(f, 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(bytes) + " Byte" : bytes < 1048576 ? static_cast(bytes / 1024.0) + " KB" : static_cast(bytes / 1048576.0) + " MB"; +} + +const String getContentType(const String & path) { // ermittelt den MIME-Type + using namespace mime; + char buff[sizeof(mimeTable[0].mimeType)]; + for (size_t i = 0; i < maxType - 1; i++) { + strcpy_P(buff, mimeTable[i].endsWith); + if (path.endsWith(buff)) { + strcpy_P(buff, mimeTable[i].mimeType); + return static_cast(buff); + } + } + strcpy_P(buff, mimeTable[maxType - 1].mimeType); + return static_cast(buff); +} diff --git a/src/Webserver.ino b/src/Webserver.ino new file mode 100644 index 0000000..8526588 --- /dev/null +++ b/src/Webserver.ino @@ -0,0 +1,54 @@ +// **************************************************************** +// Sketch Esp8266 Webserver Modular(Tab) +// created: Jens Fleischer, 2018-05-16 +// last mod: Jens Fleischer, 2020-12-28 +// For more information visit: https://fipsok.de +// **************************************************************** +// Hardware: Esp8266 +// Software: Esp8266 Arduino Core 2.4.2 - 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. +*******************************************************************/ +// Der WebServer Tab ist der Haupt Tab mit "setup" und "loop". +// #include bzw. #include und #include +// müssen im Haupttab aufgerufen werden. +// Ein Connect Tab ist erforderlich. +// Inklusive Arduino OTA-Updates (Erfordert freien Flash-Speicher) +/**************************************************************************************/ + +#include +#include // https://arduino-esp8266.readthedocs.io/en/latest/ota_updates/readme.html +#include // Library für Dateisystem LittleFS +#include +//#include // Library für Dateisystem Spiffs einkommentieren wenn erforderlich + +// ESP8266WebServer server(80); // Jetzt in KeyPatch.ino deklariert +// DNSServer dnsServer; // Jetzt in KeyPatch.ino deklariert +// const byte DNS_PORT = 53; // Jetzt in KeyPatch.ino deklariert + + +void setupWebserver() { + setupFS(); // Filesystem setup + + ArduinoOTA.onStart([]() { + // Hier können Werte vor dem Neustart gespeichert werden + }); + ArduinoOTA.begin(); +} + +void handleWebserver() { + ArduinoOTA.handle(); + server.handleClient(); + dnsServer.processNextRequest(); + reStation(); +} diff --git a/src/html/admin.html b/src/html/admin.html new file mode 100644 index 0000000..e8eb51f --- /dev/null +++ b/src/html/admin.html @@ -0,0 +1,172 @@ + + + + + + + + ESP8266 Admin + + + +

ESP8266 Admin Page

+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+ + +
+ + diff --git a/src/html/fs.html b/src/html/fs.html new file mode 100644 index 0000000..6aa6629 --- /dev/null +++ b/src/html/fs.html @@ -0,0 +1,77 @@ + + + + + + + + Filesystem Manager + + + +

ESP8266 Filesystem Manager

+
+ + +
+
+ + +
+
+
+ +
+ + diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..45838e8 --- /dev/null +++ b/src/style.css @@ -0,0 +1,120 @@ + +/* 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; + } +}