Webserver -  Aktualisieren mittels fetch API

Auf den vorhergehenden Seiten haben wir uns die prinzipielle Ausgabe einer Webseite und die Gestaltung auf einem ESP8266 angesehen. Jetzt geht es um die Aktualisierung der Webseite. Um neue Werte auf einer Webseite darzustellen könnte man diese einfach neu laden. Das geht mit entsprechenden Anweisungen in einem META-Tag ("refresh"). Hier verwende ich jedoch den Update mittels fecht API.

Sehr vereinfacht dargestellt: mit Hilfe des fetch API ruft ein Client (der Webbrowser) Daten von einem Webserver auf und aktualisert in der Folge die Webseite. Genauer: in meinem Fall werden einzelne Teile der Webseite aktualisiert.

Wir benötigen 3 Teile

  • eine HTML Dokument das so aufbereitet ist, damit einzelne Teile veränderbar werden
  • ein fetch API das neue Daten von einer Resource abholt und mittels JavaScript die Webseite ändert
  • einen ESP Webserver der die zu aktualisierenden Daten als JSON zur Verfügung stellt

Das HTML Dokument

In HTML Dokument gibt es daher einerseits

  • im Header einen Link auf das JavaScript (rot). Alternativ könnte man das JavaScript auch direkt im HTML schreiben.
  • eindeutige ID's für jene Element die mittels JavaScript aktualisert werden sollen (grün), z. B. über das inline Tag <span id='eindeutig'></span
void addTop()
{
String message;
message = F("<!DOCTYPE html>\n"
            "<html>\n<head>\n<title>" TXT_BOARDNAME " - Board " TXT_BOARDID "</title>\n"
            "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n"
            "<meta name=\"viewport\" content=\"width=device-width\">\n"
            "<link rel='stylesheet' type='text/css' href='/f.css'>\n"
            "<script src='j.js'></script>\n"
            "</head>\n");

in den Seiten, die klare Identifikation der Elemente mittels ID's, wenn es im Lauftext ist, z.B. mit dem span-Tag:

message += F("<h2>Values (with update)</h2>\n");
message += F("<p>Internal Voltage measured by ESP: <span id='internalVcc'>"); 
message += ESP.getVcc();
message += F("</span>mV</p>\n");

Der Abschnitt der ursprünglich dynamisch mittels ESP.getVcc() eingebunden wurde, steht somit innerhalb eines span tags "internalVcc" und kann daher mittels JavaScript eindeutig identifiziert werden.

Das JavaScript

Für die eigentliche JavaScript Datei gibt es wieder einen Handler im setup()

server.on("/j.js", handleJs); // javscript based on fetch API to update the page

sowie im Tab "server" die entsprechende damit die Datei (das JavaScript) zur Verfügung gestellt werden kann.

void handleJs() {
  // Output: a fetch API / JavaScript
  
  String message;
  message += F("const url ='json';\n"
               "function renew(){\n"
               " document.getElementById('sec').style.color = 'blue'\n"   
               " fetch(url)\n" // Call the fetch function passing the url of the API as a parameter
               " .then(response => {return response.json();})\n"
               " .then(jo => {\n"
               "   document.getElementById('sec').innerHTML = Math.floor(jo['ss'] % 60); \n"     
               "   for (var i in jo)\n"
               "    {if (document.getElementById(i)) document.getElementById(i).innerHTML = jo[i];}\n"    
               // add other fields here (e.g. the delivered JSON name doesn't fit to the html id
               // finally, update the runtime
               "   if (jo['ss'] > 60) { document.getElementById('min').innerHTML = Math.floor(jo['ss'] / 60 % 60);}\n"
               "   if (jo['ss'] > 3600) {document.getElementById('hour').innerHTML = Math.floor(jo['ss'] / 3600 % 24);}\n"
               "   if (jo['ss'] > 86400) {document.getElementById('day').innerHTML = Math.floor(jo['ss'] / 86400 % 7);}\n"
               "   if (jo['ss'] > 604800) {document.getElementById('week').innerHTML = Math.floor(jo['ss'] / 604800 % 52);}\n"
               "   document.getElementById('sec').style.color = 'dimgray';\n"  
               " })\n"
               " .catch(function() {\n"            
               "  document.getElementById('sec').style.color = 'red';\n"
               " });\n"
               "}\n"
               "document.addEventListener('DOMContentLoaded', renew, setInterval(renew, ");
  message += ajaxIntervall * 1000;
  message += F("));");
              
  server.send(200, "text/javascript", message);

Das JavaScript ruft die Daten mittels "json" vom Webserver ab. Wenn die Daten einlangen gibt es zwei Möglichkeiten:

  1. Daten bei denen der Schlüssel exakt einer ID im HTML Dokument gleichen werden ausgetauscht (magenta). Unter Beibehaltung der Namenskonvention zwischen JSON und HTML-Dokument kann man "viele" Daten aus dem JSON mit nur zwei Zeile im JavaScript aktualisieren. Das vorangestellte
    if(document.getElementById(i))
    soll nur einen Fehler verhindern, wenn das Element nicht auf im HTML Dokument vorhanden wäre. Das kann geschehen, wenn das JSON mehr Daten liefert als auf der Seite benötigt werden.
  2. Daten werden aufbereitet und speziell im Dokument geändert. Das passiert z.B. beim Zeitstempel: das JSON enthält die Laufzeit in Sekunden (ss) und diese wird auf Sekunden (sec), Minuten (min) etc. aufgeteilt. (blau)

Zur Laufzeitkontrolle siehst du auch kurz: Bei Start des JavaScript wird die Sekundenanzeige blau - wenn das Script ordnungsgemäß verarbeitet werden kann, wird die Sekunde wieder grau (dimgray). Tritt ein Fehler in der Kommunikation auf, wird die Sekunde rot. Mit dieser einfachen Fehlerdarstellung erkennst du auch relativ leicht etwaige Parsing Fehler: bleibt die Sekundenanzeige blau, hat der Browser zwar aktuelle Daten erhalten, aber eine der Aktualisierungen ist nicht korrekt. Meist ist es ein Fehler im JavaScript.

Eine Anmerkung: "früher" hätte man das mittels AJAX/XMLHttpRequest. Heute wird das Fetch API von fast allen Browsern unterstützt. Für den alten IE könnte eventuell noch die Variante mit AJAX/XMLHttpRequest notwendig sein.

Das JSON

Zuletzt dürfen darfst du nicht auf das JSON mit den aktuellen Daten vergessen. Auch dafür brauchst du wieder einen eigenen Handler im setup():

 server.on("/json", handleJson); // send data in JSON format

und im Tab "server" die entsprechende Funktion. Im wesentlichen ist das nur eine reine Ausgabe der Schlüssel und Werte:

void handleJson() {
  // Output: send data to browser as JSON

  String message = "";
  message = (F("{\"ss\":")); // Start of JSON and the first object "ss":
  message += millis() / 1000;
  message += (F(",\"internalVcc\":"));
  message += ESP.getVcc();
  message += (F(",\"button1\":"));
  message += digitalRead(BUTTON1_PIN);
  message += (F(",\"output1\":"));
  message += digitalRead(OUTPUT1_PIN);
  message += (F("}")); // End of JSON
  server.send(200, "application/json", message);
}

dieses Funktion generiert eine JSON Ausgabe in der Art:

{"ss":7785,"internalVcc":3041,"button1":1,"output1":0}

Wie bereits beim JavaScript angeführt, sollen die Schlüssel zu den id's im HTML Dokument passen, damit ein einfaches Update möglich ist. Im Beispiel geschieht dies für die Schlüssel "internalVcc", "button1" und "output1". Wenn die Schlüssel nicht zusammpassen, so müssen sie im JavaScript extra behandelt werden. Ein Beispiel dafür ist der Zeitstempel "ss".

Damit hast du nun zwei neue Komponenten (das fetch API und das JSON) eingeführt sowie das bestehende HTML Dokument für die Aktualisierung mittels JavaScript adaptiert.

Es geht weiter mit "Webserver - GPIO Schalten über Webseite".

Links

Protokoll

erstellt: 2020-04-01 | Stand: 2021-02-10