ESP8266 und Progmem für Webseite

Hallo Community,

ich habe angefangen mich mit einem Webserver und Progmem auf einem Nodemcu auseinander zusetzen.

Allerdings stehe ich nun vor einem großem Rätsel. Bisher habe ich meine Sketche mit ESP8266 Core 2.5.0 kompiliert. Da hat folgender Sketch funktioniert (Ich habe die CSS Teile und den Inhalt der Webseite rausgenommen wegen der Länge):

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>

ESP8266WebServer server(80); // Port für Webserver definieren

const static char html_kopf[] PROGMEM = R"=====(
<html lang='de'><head><title>WortUhr Konfiguration</title><meta name='viewport' content='width=device-width, initial-scale=1'>
<style>
</style>
</head><body>
<h1>Testwebseite</h1>
)=====";

const static char html_ende[] PROGMEM = R"=====(
</body></html>
)=====";

// Webseite die Aufgerufen wird
void handleInfo() {

  String buff;
  buff += html_kopf;

  buff += "<h2>Informationen</h2>";
  char webserver_uhrzeit[10];
  sprintf(webserver_uhrzeit, "%02u:%02u", 12, 6);
  buff += "Es ist <b> xx:xx:xx Uhr</b>

";

  buff += "Restzeit bis NTP Abruf: <b>0.00 Std.</b>
";
  buff += "Letzter NTP Abruf: <b> xx </b>

";

  buff += html_ende;

  server.send(200, "text/html", buff);

}

void setup(void) {
  Serial.begin(115200);


  WiFiManager wifiManager;
  wifiManager.autoConnect("Konfiguiere-Mich");

  server.on("/", handleInfo);         // Webseite bearbeiten
  server.begin();

}

void loop(void) {
  server.handleClient();

}

Da ich einen neuen Laptop bekommen habe, habe ich auch die Arduino IDE neuinstalliert und mir den neuen ESP8266 Core 2.5.2 installiert. Der oben gezeigte Code endet nun in einem ständigen Reboot, sobald ich versuche den Webserver zu öffnen.

Ich bekomme den Code erst wieder zum laufen, wenn ich PROGMEM bei den Konstanten entferne.

Kann mir jemand helfen, warum es mit der 2.5.2 nicht mehr geht aber mit der 2.5.0? Was muss ich machen, damit es wieder geht - würde den Code gerne auslagern, da die Webseite doch recht groß geworden ist und ich somit wenigstens den CSS Teil in den Flash bekommen würde.

Ich habe schon versucht das ganze mit einer Schleife auszulesen, was aber ebenfalls zum Reboot des Nodemcu’s führt:

for (byte k = 0; k < strlen_P(html_kopf); k++) {
    buff += pgm_read_byte_near(html_kopf + k);
    
  }

Ich würde mich sehr über Hilfe freuen.

Vielen Dank & Grüße,
Björn

EDIT: Ich habe gerade die SDK Version vom ESP8266 Core 2.5.2 auf die v3 angehoben und mein Webserver funktioniert wieder. Wenn ich den ESP Core auf 2.4.2 runtersetze nutze ich ebenfalls die SDK v. 2.4.2 wie beim ESP Core 2.5.2. Also liegt das Problem irgendwo hier?

Würde mich freuen, wenn jemand noch einen Rat hat. Vielleicht ist mein Code auch nicht richtig und der Compiler rettet mich nur bei der SDK v3…

Hallo,

warum lagerst die Websete nicht auf dem Filesystem des ESP aus. Dann kannst Du die Webseite mit einem normalen Editor erstellen und testen.

Heinz

Ich würde die Html, CSS, JS usw. Dateien im Flash des Esp speichern.
Dazu kannst du gerne meinen Esp8266 Spiffs Datei Manager nutzen. Damit bist du viel schneller bei der Entwicklung deiner Dateien.

Ansonsten mit Core 2.5.2

const char html_kopf[] PROGMEM = R"(
<!DOCTYPE HTML>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
</style>
</head>
<body>
<h1>Testwebseite</h1>
)";

const char html_ende[] PROGMEM = R"(
</body></html>
)";
     
void handleInfo() {
  char webserver_uhrzeit[10];
  sprintf(webserver_uhrzeit, "%02u:%02u", 12, 6);
  String buff;
  buff = FPSTR(html_kopf);
  buff += "<h2>Informationen</h2>";
  buff += "Es ist <b>";
  buff += webserver_uhrzeit;
  buff += " Uhr</b>

";
  buff += "Restzeit bis NTP Abruf: <b>0.00 Std.</b>
";
  buff += "Letzter NTP Abruf: <b> xx </b>

";
  buff += FPSTR(html_ende);
  server.send(200, "text/html", buff);
}

Gruß Fips

Hallo Fips,

vielen Dank für deine Hilfe :). Ich werde mich auch deinen Dateimanager genauer ansehen.
Habe nur das Problem das die HTML Seiten kaum statischen Inhalt haben und viel auf die Variablen die gesetzt sind oder werden reagieren soll.

Deshalb habe ich bisher immer einen Teil ins Progmem gelegt, ausgegeben und dann entsprechend der Variablen den HTML Code fortgesetzt.

Liebe Grüße,
Björn

CSS dürfte doch statisch sein. Das kannst Du ins SPIFFS auslagern.

Gruß Tommy

Peter1612:
Habe nur das Problem das die HTML Seiten kaum statischen Inhalt haben und viel auf die Variablen die gesetzt sind oder werden reagieren soll.

Deshalb habe ich bisher immer einen Teil ins Progmem gelegt, ausgegeben und dann entsprechend der Variablen den HTML Code fortgesetzt.

Liebe Grüße,
Björn

fetch api

Ausreichend Beispiele auf meiner Webseite zum nachbauen.

Gruß Fips

Hallo Fips,

erstmal danke, dass du mir deinen Code gezeigt hast - bin damit schon ein ganzes Stück weiter gekommen.

Wenn ich ein Formular innerhalb der Webseite habe, wertest du die Daten glaube ich per JavaScript aus. Ich habe das ganze nun etwas geändert und wollte dich fragen, ob das ganze so stimmig ist?

Diesen Code habe ich als Tab beim Hauptsketch eingebunden.

Über **"/setup"**kann Javascript sich aktuelle Variablen auslesen. Über “/werte.html” wird die Webseite neugeladen wenn das Formular abgeschickt wird.

void werte() {                 
  server.on("/setup", []() {
    String temp = "[";
    temp += (String)"{\"name\":\"Helligkeit\",\"wert\":\"" + seed + "\"},";
    temp += (String)"{\"name\":\"Farbton\",\"wert\":\"" + eins + "\"},";
    temp += (String)"{\"name\":\"Nutz_RTC\",\"wert\":\"" + zwei + "\"}";
    server.send(200, "application/json", temp += "]");
  });

  server.on("/werte.html", []() {
    if (server.hasArg("TEMP")) {
      if ( server.arg("bla") != "") {
        eins = server.arg("bla").toInt();
      }
    }

    File f = SPIFFS.open( "/werte.html", "r"); // Datei zum lesen öffnen
    String data = f.readString(); // Inhalt der Textdatei wird gelesen...
    f.close(); // Wir schließen die Datei
    server.send(200, "text/html", data);
  });
}

Die Webseite sieht so aus:

<!-- For more information visit: https://fipsok.de -->
<!DOCTYPE HTML>
<html lang="de">
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Testwerte</title>
     
     
      <script>
         function list() {
           fetch('/setup').then(function (response) {
             return response.json();
           }).then(function (json) {
             for (var i = 0; i < json.length; i++) {
               document.getElementById(json[i].name).innerHTML = json[i].wert;
             }
             
           });
         }
         document.addEventListener('DOMContentLoaded', list);
         
         setInterval(list, 5000); // Interval einstellen  in ms -->
      </script>
   </head>
   <body>
      <h1>Werte uebergeben...</h1>
      
         Helligkeit: <div style="display:inline" id="Helligkeit"></div>

         Farbton: <div style="display:inline" id="Farbton"></div>

         Wird die RTC genutzt: <div style="display:inline" id="Nutz_RTC"></div>
         
         


         <form action='/werte.html' method='POST'>
         <input type="text" name="bla">
<input type="submit" name="TEMP" value="Klick mich">
         </form>
      
   </body>

Wenn ich nun auf Senden drücken, wird das Formular ausgewertet und die Datei der Webseite wird über Spiffs neueingelesen und wieder an den Client geschickt. Es funktioniert auch alles :slight_smile: - nur nicht das es einen einfacheren Weg gibt, denn ich übersehen habe?

Liebe Grüße,
Björn

FPSTR hat dir eh schon Fips gezeigt.

statische Teile kannst du auch Zeilenweise mit dem F-Makro in den Flash verschieben.

Mehrere Zeilen auch mit einem einzigen Makro, beides kann man mit deinem Beispiel zeigen:

// Webseite die Aufgerufen wird
void handleInfo() {
  String buff;
  buff += FPSTR(html_kopf);
  buff += F("<h2>Informationen</h2>");
  char webserver_uhrzeit[10];
  sprintf(webserver_uhrzeit, "%02u:%02u", 12, 6);
  buff += F("Es ist <b> xx:xx:xx Uhr</b>

"
            "Restzeit bis NTP Abruf: <b>0.00 Std.</b>
");
            "Letzter NTP Abruf: <b> xx </b>

");
  buff += FPSTR(html_ende);
  server.send(200, "text/html", buff);
}

lies auch mal hier: Guide to PROGMEM on ESP8266 and Arduino IDE — ESP8266 Arduino Core 2.7.1-106-g1a381477 documentation

Bin gerade dabei alles ins Spiffs zu verschieben. Bis jetzt gelingt es mir auch.

Folgendes ist mir jetzt aufgefallen ich habe zB folgendes JSON:

[{"name":"Wort_Farbe","wert":"128"},{"name":"Nutze_RTC","wert":"Aktiviert"},{"name":"Nutze_LDR","wert":"Aktiviert"},{"name":"Start_LDR","wert":"0"},{"name":"Ende_LDR","wert":"1020"},{"name":"Start_Hell","wert":"10"},{"name":"Ende_Hell","wert":"50"},{"name":"Start_Nachtruhe","wert":"1"},{"name":"Ende_Nachtruhe","wert":"6"}]

Die Werte hole ich mir mit folgenden JavaScript:

<script>
         function list2() {
           fetch('/lese_setup').then(function (response) {
             return response.json();
           }).then(function (json2) {
             for (var i = 0; i < json2.length; i++) {
               document.getElementById(json2[i].name).innerHTML = json2[i].wert;
             }
             
           });
         }
         document.addEventListener('DOMContentLoaded', list2);
         
         setInterval(list2, 1000); // Interval einstellen  in ms -->
      </script>

Die Werte gebe ich auf meine Webseite so aus:

<div style="display:inline" id="Wort_Farbe"></div>
<div style="display:inline" id="Nutze_RTC"></div>
<div style="display:inline" id="Nutze_LDR"></div>
usw...

Wenn ich alle Werte nacheinander abrufe, klappt alles wunderbar. Lasse ich zwischendurch einen aus - werden auch alle nachfolgenden Werte nicht mehr angezeigt. Sprich lasse ich “Nutze_RTC” aus wird zwar “Wort_Farbe” angezeigt aber “Nutze_LDR” nicht mehr :frowning:

Kann mir da jemand helfen? Dann kann ich diesen Umbau abschließen.

Danke für eure Zeit!

Im einfachsten Fall immer alle übertragen.
Ansonsten immer den Feldnamen mit prüfen und nur die übertragenen aktualisieren.
Es ist dabei sinnvoll, wenn die Feldnamen/-id im HTML genau so heißen, wie die im JSON.

Gruß Tommy

Hallo Thommy,

genau das habe ich probiert, jeder Webseite immer alle Daten zu geben - egal ob sie dort Verwendung finden.

Leider klappt das nicht. Sobald ich einzelne Werte weglasse wird gar nichts mehr angezeigt.

Vielleicht liegt der Fehler auch beim Generieren? Das JSON sieht aber gut aus - siehe meinen vorherigen Post.

server.on("/lese_setup", []() {

    // Formatiere den RTC Wert lesbar
    String s_nutze_rtc;
    if (EEPROM.read(nutze_rtc_addr) == 1) {
      s_nutze_rtc = "Aktiviert";
    }
    else {
      s_nutze_rtc = "Deaktiviert";
    }

    // Formatiere den LDR Wert lesbar
    String s_nutze_ldr;
    if (EEPROM.read(ldr_aktiv_addr) == 1) {
      s_nutze_ldr = "Aktiviert";
    }
    else {
      s_nutze_ldr = "Deaktiviert";
    }

    String temp = "[";
    temp += (String)"{\"name\":\"Wort_Farbe\",\"wert\":\"" + wcolor + "\"},";

    temp += "{\"name\":\"Nutze_RTC\",\"wert\":\"" + s_nutze_rtc + "\"},";

    temp += "{\"name\":\"Nutze_LDR\",\"wert\":\"" + s_nutze_ldr + "\"},";

    temp += (String)"{\"name\":\"Start_LDR\",\"wert\":\"" + EEPROM.read(s_ldr_addr) * 4 + "\"},";
    temp += (String)"{\"name\":\"Ende_LDR\",\"wert\":\"" + EEPROM.read(e_ldr_addr) * 4 + "\"},";
    temp += (String)"{\"name\":\"Start_Hell\",\"wert\":\"" + EEPROM.read(s_helligkeit_addr) + "\"},";
    temp += (String)"{\"name\":\"Ende_Hell\",\"wert\":\"" + EEPROM.read(e_helligkeit_addr) + "\"},";

    temp += (String)"{\"name\":\"Start_Nachtruhe\",\"wert\":\"" + EEPROM.read(s_uhr_addr) + "\"},";
    temp += (String)"{\"name\":\"Ende_Nachtruhe\",\"wert\":\"" + EEPROM.read(e_uhr_addr) + "\"}";



    server.send(200, "application/json", temp += "]");

  });

  // Bereite die Werte für die /index.html Seite vor
  server.on("/lese_informationen", []() {

    // Formatiere die Uhrzeit auf dem Webserver lesbar
    char webserver_uhrzeit[8];
    sprintf(webserver_uhrzeit, "%02u:%02u:%02u", hour(), minute(), second());

    // Formatiere den Nachtruhewert lesbar um
    String s_nachtruhe;
    if (!nachtruhe) {
      s_nachtruhe = "Deaktiviert";
    }
    else {
      s_nachtruhe = "Aktiviert";
    }

    String temp = "[";
    temp += (String)"{\"name\":\"Helligkeit\",\"wert\":\"" + helligkeit_led + "\"},"; // Aktuelle Helligkeit
    temp += (String)"{\"name\":\"Restdauer_NTP\",\"wert\":\"" + restdauer_rtc_update + "," + restdauer_rtc_update_min + "\"},"; // Wann wird der NTP wieder abgefragt
    temp += (String)"{\"name\":\"Timestamp_NTP\",\"wert\":\"" + timestamp_rtc + "\"},"; // Wann wurde der NTP abgefragt
    temp += (String)"{\"name\":\"Uhrzeit\",\"wert\":\"" + webserver_uhrzeit + "\"},"; // Aktuelle Uhrzeit auf dem ESP
    temp += "{\"name\":\"Nachtruhe\",\"wert\":\"" + s_nachtruhe + "\"},";
    temp += (String)"{\"name\":\"LDR_Wert\",\"wert\":\"" + ldr + "\"}";


    server.send(200, "application/json", temp += "]");
  });
}

Hab es jetzt schon für die index.html und setup.html gesplitet.

EDIT: Ich vermute es liegt am JavaScript Teil. Sobald ein Wert nicht gefunden werden kann zum Ersetzen bricht die Funktion komplett ab. Hab es jetzt mit unterschiedlichen Konstellationen nachgestellt. Sobald ein Wert (id=) nicht da ist oder falsch geschrieben ist werden die nachfolgenden Werte nicht mehr gesetzt.

LG,
Björn

https://www.mediaevent.de/javascript/for-in-foreach.html

Gruß Fips

Ich würde die alt gediente for Schleife durch "for...in" oder im Notfall auch durch "Object.keys()" ersetzen.

Aber warten wir mal ab, was die JavaScript Experten dazu sagen!

Gruß Fips

imho brauchst die Ausgabe nur unter bedingung setzen und prüfen ob im Dokument die ID existiert. Damit entsteht kein Fehler und das JS wird weiter abgearbeitet.

vieleicht so:

for (var i = 0; i < json2.length; i++) {
  if document.getElementById(json2[i].name) document.getElementById(json2[i].name).innerHTML = json2[i].wert;
}

Zitat: Sempervivum

Du hättest besser den Code gepostet, der nicht funktioniert.

Da hat er wohl recht.

Mit herkömlicher for Schleife.

<!DOCTYPE HTML>
<html lang="de">
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Testwerte</title>
      <script>
         function list() {
           fetch('/json_array_object').then(function (response) {
             return response.json();
           }).then(function (json) {
             for (var i = 0; i < json.length; i++) {
               document.getElementById(json[i].name).innerHTML = json[i].wert;
             }
           });
         }
 document.addEventListener('DOMContentLoaded', list);
      </script>
   </head>
   <body>
      <h1>Werte übergeben...</h1>
         <div>w_sekunden_hue: <span id="w_sekunden_hue"></span></div>
         <div>w_sekunden_sat: <span id="w_sekunden_sat"></span></div>
         <div>w_sekunden_val: <span id="w_sekunden_val"></span></div>
         <div>w_helligkeit: <span id="w_helligkeit"></span></div>
         <div>wcolor: <span id="wcolor"></span></div>
         <div>ESIST: <span id="ESIST"></span></div>
   </body>
</html>

Funktioniert

 server.on("/json_array_object", []() {
    String json = R"([{"name":"w_sekunden_hue","wert":"0"},
      {"name":"w_sekunden_sat","wert":"128"},
      {"name":"w_sekunden_val","wert":"40"},
      {"name":"w_helligkeit","wert":"10"},
      {"name":"wcolor","wert":"111"},
      {"name":"ESIST","wert":"0"}])";
    server.send(200, "application/json", json);
  });

Funktioniert auch ohne w_sekunden_sat

 server.on("/json_array_object", []() {
    String json = R"([{"name":"w_sekunden_hue","wert":"0"},
    
      {"name":"w_sekunden_val","wert":"40"},
      {"name":"w_helligkeit","wert":"10"},
      {"name":"wcolor","wert":"111"},
      {"name":"ESIST","wert":"0"}])";
    server.send(200, "application/json", json);
  });

Mit for in, das wäre mein Favorit.

<!DOCTYPE HTML>
<html lang="de">
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Testwerte</title>
      <script>
 function list() {
           fetch('/json_object').then(function (response) {
             return response.json();
           }).then(function (obj) {
             for (var key in obj) {
               document.getElementById(key).innerHTML = obj[key];
             }
           });
         }
         document.addEventListener('DOMContentLoaded', list);
      </script>
   </head>
   <body>
      <h1>Werte übergeben...</h1>
         <div>w_sekunden_hue: <span id="w_sekunden_hue"></span></div>
         <div>w_sekunden_sat: <span id="w_sekunden_sat"></span></div>
         <div>w_sekunden_val: <span id="w_sekunden_val"></span></div>
         <div>w_helligkeit: <span id="w_helligkeit"></span></div>
         <div>wcolor: <span id="wcolor"></span></div>
         <div>ESIST: <span id="ESIST"></span></div>
   </body>
</html>

Funktioniert

 server.on("/json_object", []() {
    String json = R"({"w_sekunden_hue":"0",
       "w_sekunden_sat":"128",
       "w_sekunden_val":"40",
       "w_helligkeit":"10",
       "wcolor":"111",
       "ESIST":"0"})";
    server.send(200, "application/json", json);
  });

Ausgabe Webseite:

Werte übergeben…
w_sekunden_hue: 0
w_sekunden_sat: 128
w_sekunden_val: 40
w_helligkeit: 10
wcolor: 111
ESIST: 0

Funktioniert auch ohne w_sekunden_sat

 server.on("/json_object", []() {
    String json = R"({"w_sekunden_hue":"0",

       "w_sekunden_val":"40",
       "w_helligkeit":"10",
       "wcolor":"111",
       "ESIST":"0"})";
    server.send(200, "application/json", json);
  });

Ausgabe Webseite:

Werte übergeben…
w_sekunden_hue: 0
w_sekunden_sat:
w_sekunden_val: 40
w_helligkeit: 10
wcolor: 111
ESIST: 0

Jetzt solltest du unbedingt zeigen wie und was bei dir nicht geht.

Gruß Fips

wenn du noch den sketch offen, hast, versuch bitte mal im JSON ein "neues" Feld in der Mitte hinzuzugeben, wenns klappt alles gut, wenn nicht ...

Hallo,

danke für eure Antworten noiasca und Fips!

@Fips.
Sorry fürs Crossposting, ich weiß das ist nicht so gerne gesehen - hatte gestern aber echt ein Brett vorm Kopf… und wollte niemanden sein Fachwissen absprechen falls es so rübergekommen ist. Manchmal will ich zuviel sofort und dann geht erstmal nichts mehr.

Was du und auch Sempervivum geschrieben haben, dass ich besser den Code gepostet hätte der nicht geht stimmt so nicht ganz.

Der Code funktioniert so lange ich ALLE Objekte aus dem JSON auch einer ID im HTML Code zuordne. Bleiben wir bei dem Beispiel:

Im JSON Array ist “w_sekunden_sat” vorhanden aber auf der Webseite gibt es kein <div ID="w_sekunden_sat>, dann wird nur noch <div ID="w_sekunden_hue> gesetzt aber alle anderen Werte nicht mehr, da es zu einem Fehler kommt wenn er w_sekunden_sat sucht, und es nicht findet.

Ich habe meine Frage wohl unglücklich gestellt :(.

Die Lösung ist vorher zu überprüfen, ob das HTML Element ermittelt werden kann und nur dann zu setzen. Wie noiasca sagte. Aus dem Javascript Forum kam folgenden Lösung:

             for (var i = 0; i < json.length; i++) {
               var ele = document.getElementById(json[i].name);
               if (ele) ele.innerHTML = json[i].wert;
             }

Liebe Grüße und noch eine schöne Woche,
Björn

MajorW:
Sorry fürs Crossposting, ich weiß das ist nicht so gerne gesehen…

Da es sich um Javascript handelt, sehe ich das nicht als Crossposting an.

Besser beschrieben, wo du was weglassen möchtest, hätten wir auch hier eine Lösung gefunden.

TypeError: document.getElementById(…) is null
Ist klar wenn es diese ID im Html nicht gibt.

Möglichkeit 1:
Wie im Javascript Forum vorgeschlagen, abfragen ob die ID existiert.

Möglichkeit 2:
Elemente erst erzeugen wenn der Client das Json erhalten hat.

Webseite

<!DOCTYPE HTML>
<html lang="de">
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Testwerte</title>
      <script>
        function list() {
           fetch('/json_object').then(function (response) {
             return response.json();
           }).then(function (obj) {
             var buf = '';
             for (var key in obj) {
               buf += `<div>${key}: <span>${obj[key]}<span></div>`;
             }
             document.querySelector('body').insertAdjacentHTML('beforeend', buf);
           });
         }
         document.addEventListener('DOMContentLoaded', list);
      </script>
   </head>
   <body>
      <h1>Werte übergeben...</h1>
   </body>
</html>

Json

 server.on("/json_object", []() {
    String json = R"({"W_sekunden_hue":"10",
       "W_sekunden_sat":"128",
       "W_sekunden_val":"40",
       "W_helligkeit":"10",
       "Wcolor":"111",
       "ESIST":"0"})";
    server.send(200, "application/json", json);
  });

Ausgabe

Werte übergeben…
W_sekunden_hue: 10
W_sekunden_sat: 128
W_sekunden_val: 40
W_helligkeit: 10
Wcolor: 111
ESIST: 0

Gruß Fips