Im zweiten Teil wollen wir nun eine neue Software erstellen, sodass wir Bewässerungsprogramme über den Browser konfigurieren können. Zum Beispiel, die Bewässerung soll täglich um 8 Uhr morgens für 15 Minuten und um 19 Uhr für 20 Minuten eingeschaltet werden.
Benötigte Hardware
Wir verwenden dieselbe Hardware wie im ersten Teil.
Bibliothek WebConfig
Um uns die Programmierarbeit zu erleichtern, verwenden wir die Software-Bibliothek WebConfig, die über die Arduino Bibliotheksverwaltung installiert werden kann. Es sollte in jedem Fall die neueste Version eingesetzt werden. Mindestens die Version 1.3 ist notwendig, da diese neue Funktionen enthält, die im Sketch verwendet werden.
Die Klasse WebConfig stellt intern bis zu 20 frei definierbare Konfigurationsparameter bereit. Die Werte dieser Parameter können im SPIFFS gespeichert und von dort gelesen werden. Über ein HTML-Formular können die Werte der Parameter verändert werden. Zur Definition wird eine JSON Zeichenkette verwendet, die eine Liste von Objekten darstellt. Jedes Objekt in dieser Liste beschreibt einen Parameter.
[{
"name":"",
"label":"",
"type":0,
"default":""
"min":0,
"max":0,
"options":[
{"v":"","l":""}
]
}]
name | String | Gibt den Namen des Parameters an. Mit diesem Namen wird der eingestellte Wert im Konfigurationsfile gespeichert. Über diesen Namen kann auch auf den Wert zugegriffen werden |
label | String | Definiert die Beschriftung des Eingabeelements im HTML Formular |
type | Integer | Typ des Eingabeelements folgende Elemente sind möglich
|
default | String | Vorgabewert |
min | Integer | (optional) Minimalwert für Nummerneingaben |
max | Integer | (optional) Maximalwert für Nummerneingaben |
options | Liste von Objekten | (optional) Liste der möglichen Optionen für Mehrfachauswahl. Jeder Eintrag besteht aus einem Objekt mit den Eigenschaften v für den Wert und l für die Beschriftung. |
Anhand dieser Konfiguration erzeugt eine Funktion der WebConfig Klasse ein HTML-Formular zum Ändern und Speichern der Parameter. Die Darstellung auf der HTML Seite ist in der Breite auf 320 Pixel begrenzt, damit die Seite auf einem Smartphone gut lesbar ist.
Es gibt zwei Formulartypen. Eines dient besonders zum Einstellen von Zugangsdaten. Dieses Formular hat zwei Knöpfe, einer mit der Beschriftung „SAVE“ und einer mit der Beschriftung „RESTART“. Beide Knöpfe sorgen dafür, dass die Konfiguration im SPI-Flash-Filesystem, kurz SPIFFS, gespeichert wird.
Der „RESTART“-Knopf löst zusätzlich einen Restart des Mikrocontrollers aus. Über eine Callback Funktion, die beim Klicken auf den „SAVE“-Knopf ausgelöst wird, kann auf diesen Umstand reagiert werden. In unserem Sketch werden wir diese Callback-Funktion nutzen, um von der Konfigurationsseite wieder auf die Hauptseite zurückzuschalten.
Die zweite Variante ist ein Formular mit drei Knöpfen „DONE“, „CANCEL“ und „DELETE“. Dieses Formular besitzt keine automatische Speicherung. In unserem Sketch werden wir dieses Formular zum Einstellen der Schaltzeiten benützen. Auch hier können Callback-Funktionen registriert werden, die aufgerufen werden, wenn auf einen Knopf geklickt wurde.
Bibliothek NTPClient
Da wir eine genaue Uhrzeit zum Ein- und Ausschalten des Ventils benötigen, bedienen wir uns eines Zeitservers; das sind Server im Internet, die über das Network-Time-Protokoll (NTP) die genaue Uhrzeit und das aktuelle Datum liefern. Auch diese Bibliothek kann über die Bibliotheksverwaltung installiert werden.
Bibliothek ArduinoJson
Java-Script-Object-Notation (JSON) ist ein Format, in dem Arrays und Objekte als String dargestellt werden können. Dieses Format wird auch in der WebConfig Bibliothek benutzt, um die Formulare zu beschreiben. In unserem Sketch werden wir das JSON Format benutzen, um die Liste der Schaltzeiten als Array von Objekten im SPI-Filesystem zu speichern. Auch diese Bibliothek kann über die Bibliotheksverwaltung installiert werden. Nähere Details zum JSON Format findet man im Internet.
Die Software
//Includes für den Webserver und DNS #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> //Includes für den Zeit Client #include <NTPClient.h> #include <WiFiUdp.h> //Include für den Servo #include <Servo.h> //Include für die Konfiguration #include <WebConfig.h> //Includes für die Speicherung der //Einstellungen im SPI Flash Filesystem #include <ArduinoJson.h> #include <FS.h> #define SERVO 13 //pin für den Servo #define MAXEVENTS 10 //maximale Anzahl der Ereignisse #define EVENTFILE "/ereignisse.json" //Filename zur Sicherung //Instanz für Servo Servo servo1; //Instanzen von WebConfig WebConfig conf; //WLAN Zugangsdaten WebConfig eventconf; //Ereignisse konfigurieren //Instanz für Webserver ESP8266WebServer server(80); //Instanz für NTP Client WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP,"europe.pool.ntp.org"); //Datenstruktur zur Ereignis-Definition typedef struct event { String name; //Name des Ereignis uint16_t time; //Zeitpunkt in Minuten uint16_t duration; //Dauer in Minuten uint8_t value; //Wert zum Einstellen uint8_t status; //Status 0=frei, 1=bereit //2= Ereignis aktiv }; //Globale Variable für die Ereignisse event events[MAXEVENTS]; //Verbindungszustand ins WLAN bool connected = false; //Zeitstempel der letzten Überprüfung uint32_t last; //aktuelle Durchflussmenge in % uint16_t durchfluss = 0; //aktueller Winkel für den Servo float winkel = 0; //Konfigurationswerte für Netzwerkanmeldung //und Sommerzeit String param_wlan = "[" "{" "'name':'ssid'," "'label':'SSID des WLAN'," "'type':"+String(INPUTTEXT)+"," "'default':''" "}," "{" "'name':'pwd'," "'label':'WLAN Passwort'," "'type':"+String(INPUTPASSWORD)+"," "'default':''" "}," "{" "'name':'sommerzeit'," "'label':'Sommerzeit'," "'type':"+String(INPUTCHECKBOX)+"," "'default':'1'" "}]"; //Formular zum Konfigurieren //der Ereignisse String param_event = "[" "{" "'name':'name'," "'label':'NAME'," "'type':"+String(INPUTTEXT)+"," "'default':''" "}," "{" "'name':'time'," "'label':'Zeit'," "'type':"+String(INPUTTIME)+"," "'default':'00:00'" "}," "{" "'name':'duration'," "'label':'Dauer'," "'type':"+String(INPUTNUMBER)+"," "'default':'1'," "'min':'1'," "'max':'300'" "}," "{" "'name':'value'," "'label':'Durchflussmenge'," "'type':"+String(INPUTNUMBER)+"," "'default':'100'," "'min':'10'," "'max':'100'" "}]"; //HTML Templates //Erster Teil der Webseite const char HTML1[] = "<!DOCTYPE HTML>\n" "<html lang='de'>\n" "<head>\n" "<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>\n" "<meta name='viewport' content='width=380' />\n" "<title>Ventilsteuerung</title>\n" "<style>\n" "body {\n" " background-color: #d2f3eb;\n" " font-family: Arial, Helvetica, Sans-Serif;\n" " Color: #000000;\n" " font-size:12pt;\n" "}\n" ".titel {\n" "font-size:18pt;\n" "font-weight:bold;\n" "text-align:center;\n" "width:100%;\n" "padding:20px;\n" "}\n" ".zeile {\n" " width:100%;\n" " padding:5px;\n" " text-align: center;\n" "}\n" "button {\n" "font-size:14pt;\n" "width:150px;\n" "border-radius:10px;\n" "}\n" "</style>\n" "</head>\n" "<body>\n" "<div id='main_div' style='margin-left:15px;margin-right:15px;'>\n" "<div class='titel'>Durchflussmenge %i %%</div>\n" "<form action='./' method='get'>\n" " <div class='zeile'>0%%<input type='range' min='0' max='100' value='%i' name='durchfluss' onchange='form.submit()'>100%%</div>\n" "</form><br>\n"; //Endteil der Webseite const char HTML2 [] = "<br><form action='./event' method='get' >\n" " <div class='zeile'><button type='submit'>Neues Ereignis</button></div>\n" "</form>\n" "<form action='./config' method='get' >\n" " <div class='zeile'><button type='submit'>Konfiguration</button></div>\n" "</form>\n" "</div>\n" "</body>\n" "</html>\n"; //Template für die Ereignisanzeige const char HTMLEV [] = "<div class='zeile'><a href='./event?ev=%s'>%s %s Uhr %3i Minuten mit %3i %%</a></div>\n"; //Falls möglich am lokalen WLAN anmelden boolean initWiFi() { boolean connected = false; //Stationsmodus WiFi.mode(WIFI_STA); //wenn eine SSID konfiguriert wurde versuchen wir uns anzumelden if (strlen(conf.getValue("ssid")) != 0) { Serial.print("Verbindung zu "); Serial.print(conf.getValue("ssid")); Serial.println(" herstellen"); //Verbindungsaufbau starten WiFi.begin(conf.getValue("ssid"),conf.getValue("pwd")); uint8_t cnt = 0; //10 Sekunden auf erfolgreiche Verbindung warten while ((WiFi.status() != WL_CONNECTED) && (cnt<20)){ delay(500); Serial.print("."); cnt++; } Serial.println(); //Wenn die Verbindung erfolgreich war, wird die IP-Adresse angezeigt if (WiFi.status() == WL_CONNECTED) { Serial.print("IP-Adresse = "); Serial.println(WiFi.localIP()); connected = true; } } if (!connected) { //keine Verbindung, wir starten einen Access Point //damit wir auf die Konfiguration zugreifen können WiFi.mode(WIFI_AP); WiFi.softAP(conf.getApName(),"",1); } return connected; } //wandelt Minuten in einen String mit Format hh:mm String timeToString(uint16_t minutes) { char buf[10]; uint8_t h = minutes / 60; uint8_t m = minutes % 60; sprintf(buf,"%02d:%02d",h,m); return String(buf); } //wandelt Zeit im Format hh:mm in Minuten um uint16_t minutesFromString(String time) { uint16_t h = time.substring(0,2).toInt(); uint16_t m = time.substring(3,5).toInt(); return h * 60 + m; } //sucht einen freien Ereigniseintrag //returniert den Index oder -1 wenn kein //freier Eintrag existiert int16_t findFree() { uint8_t i = 0; while ((i<MAXEVENTS) && (events[i].status != 0)) i++; if (i<MAXEVENTS) { return i; } else { return -1; } } //sucht einen Ereigniseintrag mit Namen //returniert den Index oder -1 wenn //der Name nicht gefunden wurde int16_t findEvent(String name) { uint8_t i = 0; while ((i<MAXEVENTS) && (events[i].name != name)) i++; if (i<MAXEVENTS) { return i; } else { return -1; } } //zeigt die Hauptseite am Webserver an void handleRoot() { char buf[2000]; char n[40]; char t[10]; //Aktualisiert den Servo zur manuellen Einstellung if (server.hasArg("durchfluss")) { durchfluss = server.arg("durchfluss").toInt(); sendServo(durchfluss); } //Vorbereitung der Antwort server.setContentLength(CONTENT_LENGTH_UNKNOWN); //Im Template zum ertsen Teil %i mit der aktuellen //Durchflussmenge ersetzen sprintf(buf,HTML1,durchfluss,durchfluss); //Und an den Browser senden server.send(200, "text/html", buf); //Nun folgt die Anzeige der konfigurierten Ereignisse for (uint16_t i = 0;i<MAXEVENTS;i++) { if (events[i].status != 0) { strlcpy(n,events[i].name.c_str(),40); strlcpy(t,timeToString(events[i].time).c_str(),10); //Variablen im Template durch die Werte des //jeweiligen Ereignis ersetzen sprintf(buf,HTMLEV,n,n,t,events[i].duration,events[i].value); //Daten an den Browser senden server.sendContent(buf); } } //und schließlich das Ende des HTML-Dokuments senden server.sendContent(HTML2); } //Anfrage für die Konfigurationsseite //bearbeiten void handleConfig() { //die Anfrage an die Konfigurations Instanz //weitergeben conf.handleFormRequest(&server); } //Anfrage für das Formular zur Ereignis //Eingabe bearbeiten void handleEvent() { int16_t i = -1; //Eintrag mit dem im Argument "ev" angegebenen //Namen suchen if (server.hasArg("ev")) { i = findEvent(server.arg("ev")); } if (i>=0) { //Wurde ein Eintrag gefunden, werden die Formularwerte //auf die Werte des Eintrags gesetzt eventconf.setValue("name",events[i].name); eventconf.setValue("time",timeToString(events[i].time)); eventconf.setValue("duration",String(events[i].duration)); eventconf.setValue("value",String(events[i].value)); } else { //Wurde kein Eintrag gefunden, wird das Formular auf //die Default-Werte gesetzt eventconf.setValue("name",""); eventconf.setValue("time","00:00"); eventconf.setValue("duration","1"); eventconf.setValue("value","100"); } //Anfrage an die Ereigniskonfiguration //weitergeben eventconf.handleFormRequest(&server); } //Servo einstellen void sendServo(uint16_t durchfluss) { //um das Ventil zu öffnen ist ein Winkel //von 90 Grad erforderlich float winkel = durchfluss * 90 / 100; //diese Korrektur berücksichtigt, dass der Servo //einen gesmt Drehwinkel von 210 Grad und //nicht 180 Grad hat winkel = winkel * 180 / 210; //Servo einschalten die Parameter min=540 //und max=2400 bestimmen die minimale und die maximale //Impulsdauer. Durch vermindern des Wertes min kann eine //Feinjustierung vorgenommen werden, wenn das Ventil //nicht ganz schließt servo1.attach(SERVO,500,2400); servo1.write(winkel); delay(1000); //Nach einer Sekunde den Servo wieder abschalten servo1.detach(); } //Das Konfigurationsformular wurde mit dem //Button SAVE beendet void onSave(String res) { //die Hauptseite wird angezeigt handleRoot(); } //Das Formular zur Ereigniseingabe wurde //mit dem Button DONE beendet void saveEvent(String data) { Serial.println(data); String t; int16_t i; //wurde das Formular für ein bestimmtes // Ereignis aufgerufen, suchen wir den //Index für dieses Ereignis. //Ansonsten wird ein freier Eintrag gesucht if (server.hasArg("ev")) { i = findEvent(server.arg("ev")); } else { i = findFree(); } if (i >= 0) { //wenn wir einen Eintrag gefunden haben, //füllen wir dessen Daten mit den //Werten aus dem Formular events[i].name=eventconf.getString("name"); t=eventconf.getString("time"); events[i].time = minutesFromString(t); events[i].duration = eventconf.getInt("duration"); events[i].value = eventconf.getInt("value"); events[i].status=1; } //die geänderten Werte werden im Filesystem gespeichert writeEvents(EVENTFILE); //die Hauptseite wird wieder angezeigt handleRoot(); } //Das Formular zur Ereigniseingabe wurde //mit dem Button CANCEL beendet void cancelEvent() { //die Hauptseite wird wieder angezeigt handleRoot(); } //Das Formular zur Ereigniseingabe wurde //mit dem Button DELETE beendet void deleteEvent(String name) { int16_t i; //der Index des bearbeiteten Eintrags //wird gesucht und falls gefunden durch //das Setzen des Status auf 0 gelöscht if (server.hasArg("ev")) { i = findEvent(server.arg("ev")); if (i >= 0) events[i].status = 0; Serial.print("Delete "); Serial.println(server.arg("ev")); //die Änderungen werden gespeichert writeEvents(EVENTFILE); } //die Hauptseite wird wieder angezeigt handleRoot(); } //alle Einträge mit Status <> 0 werden gespeichert //Zum Speichern der Daten wird das JSON Format //verwendet void writeEvents(String filename) { //File zum Schreiben öffnen File f = SPIFFS.open(filename,"w"); Serial.println("Ereignisse speichern"); //Ein JSON Dokument anlegen DynamicJsonDocument doc(2000); //wir benötigen hier ein Array JsonArray array = doc.to<JsonArray>(); //Nun iterieren wir über alle Ereignisse for (uint16_t i = 0;i<MAXEVENTS;i++) { if (events[i].status != 0) { //ist der Status <> 0, fügen wir ein neues //Objekt zum Array hinzu und füllen es mit //den Daten aus dem Eintrag JsonObject ev = array.createNestedObject(); Serial.println(events[i].name); ev["name"] = events[i].name; ev["time"] = events[i].time; ev["duration"] = events[i].duration; ev["value"] = events[i].value; } } //Zum Schluss wird das JSON Dokument in einen //String serialisiert und ins File geschrieben serializeJson(array, f); f.close(); } //Die gespeicherten Ereignisse werden eingelesen void readEvents(String filename) { uint16_t i; char n[40]; //Zuerst werden alle Einträge gelöscht for (i = 0; i<MAXEVENTS; i++) events[i].status = 0; //wenn ein File mit dem gegebenen Namen existiert //wird es zum Lesen geöffnet if (SPIFFS.exists(filename)) { File f = SPIFFS.open(filename,"r"); if (f) { Serial.println("Ereignisse einlesen"); //wir benötigen wieder ein JSON Dokument DynamicJsonDocument doc(2000); //in diese Dokument werden die Daten aus dem //File eingelesen und in ein Array von //Objekten umgewandelt deserializeJson(doc,f); JsonArray evs = doc.as<JsonArray>(); i=0; //wir iterieren über alle Objekte in diesem Array for (JsonVariant v : evs) { if (i<MAXEVENTS) { //ist die Liste der Einträge noch nicht voll //wird der nächste Eintrag mit den Daten //aus dem JSON Objekt gefüllt und der //Status auf 1 gesetzt JsonObject ev = v.as<JsonObject>(); if (ev.containsKey("name")) { strlcpy(n,ev["name"],15); Serial.println(n); events[i].name = String(n); } if (ev.containsKey("time")) events[i].time = ev["time"]; if (ev.containsKey("duration")) events[i].duration = ev["duration"]; if (ev.containsKey("value")) events[i].value = ev["value"]; events[i].status=1; i++; } } f.close(); } } } //es wird geprüft ob im Augenblick ein Ereignis aktiv ist //und falls nötig wird der Servo geändert //Diese Funktion wird in regelmäßigen Abständen //in der Hauptschleife aufgerufen void checkEvents() { uint16_t m; uint8_t s; Serial.println(timeClient.getFormattedTime()); //vom Time Client holen wir die aktuelle Uhrzeit //in Minuten m = timeClient.getHours()*60+timeClient.getMinutes(); //Wir iterieren über alle Ereigniseinträge for (uint16_t i = 0; i<MAXEVENTS; i++) { //Nur Einträge mit Status <> 0 werden verarbeitet if (events[i].status != 0) { s=1; if((events[i].time <= m) && ((events[i].time+events[i].duration) > m)) s=2; if (events[i].status != s) { if (s==2) { durchfluss=events[i].value; } else { durchfluss = 0; } Serial.printf("Status geändert auf %i Durchfluss %i%%\n",s,durchfluss); sendServo(durchfluss); events[i].status = s; } } } } void setup() { // put your setup code here, to run once: Serial.begin(74880); Serial.println(); bool initok = false; initok = SPIFFS.begin(); if (!(initok)) // Format SPIFFS { Serial.println("Format SPIFFS"); SPIFFS.format(); initok = SPIFFS.begin(); } Dir dir = SPIFFS.openDir(""); while (dir.next()) { Serial.print(dir.fileName()); Serial.print(" "); File f = dir.openFile("r"); Serial.println(f.size()); } //Die JSON Zeichenkette für das Konfigurations-Formular //wird gesetzt conf.setDescription(param_wlan); //Falls vorhanden wird die Konfiguration aus dewm SPIFFS geladen conf.readConfig(); //Die JSON Zeichenkette für das Konfigurations-Formular //wird gesetzt eventconf.setDescription(param_event); //freies Formular definieren eventconf.setButtons(BTN_DONE+BTN_CANCEL+BTN_DELETE); //Falls vorhanden werden die Ereignisse aus dem SPIFFS geladen readEvents(EVENTFILE); //Die Netzwerk-Verbindung wird hergestellt connected = initWiFi(); conf.registerOnSave(onSave); eventconf.registerOnDone(saveEvent); eventconf.registerOnCancel(cancelEvent); eventconf.registerOnDelete(deleteEvent); //readEvents(EVENTFILE); //Webserver vorbereiten und starten server.on("/", handleRoot); server.on("/config", handleConfig); server.on("/event",handleEvent); server.begin(); Serial.println("WebServer gestartet"); char dns[30]; sprintf(dns,"%s.local",conf.getApName()); uint16_t offset = conf.getBool("sommerzeit")?7200:3600; timeClient.setTimeOffset(offset); if (connected) timeClient.begin(); if (MDNS.begin(dns)) { Serial.println("MDNS responder gestartet"); } //Servo nach dem Einschalten auf 0% sendServo(durchfluss); last = millis(); } void loop() { if (connected) timeClient.update(); server.handleClient(); MDNS.update(); if ((millis()-last) > 10000) { checkEvents(); last = millis(); } }
Vor dem Hochladen des Sketches muss noch sichergestellt werden, dass im Flash Speicher Platz für das SPI Filesystem reserviert wurde. Das erfolgt in der Arduino-IDE im Menü Werkzeuge.
1MB ist in unserem Fall ausreichend, dann bleiben noch 3MB für das Programm.
Viel Spaß beim Basteln.
4 comments
Wolfgang Rothhaupt
Hallo,
habe das Projekt ausprobiert funktioniert mir Wemos mini ohne Probleme,
Wird der Sketch in einem NodeMCU esp8266 (esp12-e) modul geladen kommt keine Fehlermeldung aber es wird nicht mit dem WLAN verbunden an was kann das liegen?
Kann man den Beitrag als pdf irgendwo finden?
Viele Grüße
W. Rothhaupt
Christoph
zum Lernen ist das Projekt ganz in Ordnung, wer aber wirklich eine Bewässerungslösung selber bauen möchte, der sollte sich das hier nicht antun.
Ich bin auf eine Open-Source Lösung mit dem Raspberry gekommen.
Es muss nur eine Lochrasterplatine mit einem 74HC595 aufgebaut werden und von AZ_Delivery zB ein 8x Relaisboard daran angeschlossen werden. Als Raspberry kann ein günstiger Zero-W genommen werden. Die Bewässerung erfolgt über Niedervoltventile. Oder wie bei mir aus ausgeschlachteten Waschmaschinenventile. Alle diese sind im ausgeschalteten Zustand verschlossen, was kein Risiko bei Stromausfall oder Defekt bedeutet!
Ein Relais lasse ich parallel zu allen anderen eingeschaltet, hiermit wird die Pumpe angesteuert, geht auch direkt am Wasserhahn, wer das möchte.
Die komplette Software ist via GIT erhältlich:
https://openthings.freshdesk.com/support/solutions/articles/5000631599-installing-and-updating-the-unified-firmware
Der Source-Code ist für Raspi und ESP8266 gleichermassen geschrieben.
Der HW-Schaltplan beinhaltet eine Echtzeituhr und einen AD-Wandler – beide können weggelassen werden, wie auch die Treiber für die Niedervoltrelais:
https://github.com/OpenSprinkler/OpenSprinkler-Hardware/tree/master/OSPi/1.50
Die komplette Bedienungsanleitung:
https://openthings.freshdesk.com/support/solutions/articles/5000716364-opensprinkler-user-manuals
Wer zB Feuchtigkeitssensoren für den Boden oder einen Regensensor verwenden möchte, der kann auch den AD-Wandler aus dem Schaltplan übernehmen.
Die Bedienung via WebSeite ist sehr übersichtlich und auch gut via Tablet oder Handy nutzbar. Sie unterstütz verschiedene Beregnungsszenarien und berücksichtig für den jeweiligen Ort die Wetterprognose.
Einfacher und trotzdem selbst gemacht, finde ich, geht es nicht.
Trotzdem finde ich diese Reihe hier ein gelungenes Trainingsprojekt, da es HW- und SW-Themen nutzvoll miteinander verbindet.
Gerald
Hallo Manfred
Danke für den Hinweis. Ich habe die WebConfig Bibliothek korrigiert. Die neue Version ist 1.3.1
Manfred
Hallo Gerald,
Abbruch mit der Fehlermeldung:bei mir lässt sich der Sketch nicht compilieren.
Configuration —> ESP8266 + WebConfig 1.3.0.
/home/ms/Arduino/libraries/WebConfig/src/WebConfig.cpp:25:32: fatal error: ESP8266Webserver.h: No such file or directory
#include
Ursache ist die Datei “WebConfig.cpp” in Zeile 25:
hier steht:
#include
muss geändert werden in (Server groß geschrieben):
#include
Wenn’s hilfreich war, auf die Seite posten, ansonsten verwerfen.
MfG
Manfred