Webserver -  Aktualisieren mittes AJAX/XMLHttpRequest

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 über AJAX.

Heute würde man dazu eher das fetch API verwenden. AJAX/XMLHttpRequest ist eigentlich nur mehr für sehr alte Browser notwendig die ohnehin nicht mehr verwendet werden sollten.

Sehr vereinfacht dargestellt: bei AJAX/XMLHttpRequest ruft ein Client (der Webbrowser) mittels JavaScript Daten von einem Webserver auf und aktualisert in der Folge die Webseite. Genauer: im konkreten Fall werden einzelne Teile der Webseite aktualisiert. H

Wir benötigen 3 Teile

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

Die HTML Seite

In der HTML Seite 0.htm gibt es daher einerseits

  • im Header einen Link auf das eigentliche JavaScript (rot)
  • Im Body Tag den Aufruf einer JavaScript Funktion bei "onload" die beim Laden der Seite aufgerufen wird (blau)
  • eindeutige ID's für jene Element die mittels JavaScript aktualisert werden sollen (grün)
void handlePage()
{
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");
message += F("<body onload='GetSwitchState(0)'>\n"
            "<header>\n<h1>" TXT_BOARDNAME " - Board " TXT_BOARDID "</h1>\n"
            "<nav><p><a href=\"0.htm\">Home</a> <a href=\"1.htm\">[Page 1]</a>"
            "<a href=\"2.htm\">[The Webclient]</a> <a href=\"r.htm\">[Remote Module]</a>"
            "<a href=\"x.htm\">[Page X]</a></p></nav>\n</header>\n"
            "<div id='cont'>\n");

if (server.uri() == "/0.htm" || server.uri() == "/") // *** HOME *** 0.htm
{
message += F("<h2>Homepage</h2>\n" // here you write your html code for your homepage. Let's give some examples...
            "<p>This is an example for a webserver on your ESP8266.<p>\n"
            "<p>Values are getting updated with AJAX/JSON. "
            "I'm still using old style XMLHttpRequest instead of fetch-API. It's up to "
            "the reader to update - but old style works fine for me ;-P</p>\n");

message += F("<h2>Values (with update)</h2>\n");
message += F("<p>Internal Voltage measured by ESP: <span id='internalVcc'>"); // example how to show values on the webserver
message += ESP.getVcc();
message += F("</span>mV</p>\n");

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

server.on("/j.js", handleAjax); // a javascript to handle AJAX/JSON Update of the page

sowie eine Funktion im IDE-Tab "server" damit die Datei zur Verfügung gestellt werden kann.

void handleAjax() {
// Output: a JavaScript
// a function in the JavaScript uses AJAX to request a JSON file from the webserver 
// and updates the values on the page if the object names and ID are the same
String message;
message += F(" function GetSwitchState(s) {\n" // 0 call function with settimeout, 1 run only once for manual update
             " nocache = '&nocache='+ Math.random() * 1000000;\n"
             " var request = new XMLHttpRequest(); \n"
             " request.onreadystatechange = function() {\n"
             " if (this.readyState == 4) {\n"
             " if (this.status == 200) {\n"
             " if (this.responseText != null) {\n"
             " var jo=JSON.parse(this.responseText);\n"
             " for (var i in jo)\n"
             " {if(document.getElementById(i)) document.getElementById(i).innerHTML=jo[i];}\n"
             // add other fields here
             " document.getElementById('sec').innerHTML = Math.floor(jo['ss']%60);\n" // example how to change a value in the HTML page
             " 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");
message += F(" document.getElementById('sec').style.color = 'dimgray';\n"
             " }}}\n"
             " else {document.getElementById('sec').style.color = 'red';}\n"
             " }\n"
             " request.open('GET', 'json?p="); // request the JSON output
message += server.uri();
message += F("' + nocache, true);\n"
             " request.send(null);\n"
             " if (s==0) setTimeout('GetSwitchState(0)', ");
message += ajaxIntervall * 1000;
message += F(");\n"
             " }");
server.send(200, "text/plain", message);
}

Das JavaScript

Das JavaScript ruft die Daten mittels "json" vom Webserver ab. Wenn die Daten einlagen gibt es zwei Varianten:

  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 einer einzigen Zeile im JavaScript aktualisieren.
  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 sieht man auch kurz: Bei Start des JavaScript wird die Sekundenanzeige rot - wenn das Script ordnungsgemäß verarbeitet werden kann, wird die Sekunde wieder grau (dimgray).

Das JSON

Zuletzt dürfen wir nicht auf das JSON vergessen. Auch dafür brauchen wir wieder einen eigenen Handler im setup():

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

und die entsprechende Funktion, die aber im wesentlichen auch nur eine reine Ausgabe der Schlüssel und Werte ist.

void handleJson() {
// Output: send data to browser as JSON
// after modification always check if JSON is still valid. Just call the JSON (json) in your webbrowser and check.
// Serial.println(F("D230 requested 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 ein JSON in der Art:

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

Damit haben wir nun zwei neue Komponenten (das JavaScript und das JSON) eingeführt sowie das bestehende HTML für die Aktualisierung mittels AJAX/XMLHttpRequest adaptiert.

Anmerkungen

"Heute" sollte man das ganze mit fetch-API machen. Der AJAX/XMLHttpRequest klappt aber immer noch und ist bei der Verwendung von alten Browsern wie den IE alternativlos.

Der Sketch der hier zum Download angeboten wird, enthält auch die Variante für AJAX/XMLHttpRequest. Diese Variante ist aber auskommentiert. Zum Aktivieren sind folgende Änderungen vorzunehmen:

  • Im Setup ist der Handler für handleJs auszukommentieren und der handleAjax zu aktivieren
  • im Tab Server ist im handlePage dafür zu sorgen, dass im body tag das JavaScript beim Event onload aufgerufen wird (ca Zeile 65)

Es geht weiter mit "Webserver - GPIO Schalten über Webseite" bzw. zurück zum Fetch API.

Links

  • Download des finalen Sketch ESP8266 Webserver WebClient
  • AJAX (auf Wikipedia)
  • JSON (auf Wikipedia)

Protokoll

erstellt: 2018-09-02 | Stand: 2021-09-01