Für ein ESP-Projekte wollte ich ein ansprechendes Web-Interface zur Anzeige von Messdaten implementieren. Im Netz findet man eine große Anzahl von Control-Bibliotheken. Die können fast alles, sind aber auch entsprechend riesig. Große Dateien drücken die Performance eines ESP-Projekts nicht unwesentlich. Der Grund ist, dass diese Dateien nur in relativ kleinen Stücken übertragen werden. Wegen des TCP-Overheads dauert das dann relativ lange.
Aus diesem Grund habe ich eigene Controls entwickelt und dabei insbesondere auf die Code-Größe geachtet. Die meisten sind aus frei zugänglichen Bibliotheken extrahiert und abgewandelt.
Als erstes werden technische Möglichkeiten aufgezeigt, den Datenverkehr zu reduzieren.
Inhaltsverzeichnis
Eine Performance-Steigerung erreicht man dadurch, dass Dateien, die sich eigentlich nie ändern, nicht bei jedem neuen Laden der Seite übertragen werden müssen. Die Header-Angabe max-age im HTTP-Response gibt eine Lebensdauer für das übertragene Objekt an. Angegeben wird die Anzahl Sekunden, während derer das zurück gelieferte Objekt gültig ist. Bis zum Ablauf dieser Zeit wird, wenn möglich, das Objekt aus dem Browser-Cache geladen. Mit dem AsyncWebServer kann dies z.B. so erreichen:
serveStatic("/ein.png", SPIFFS, "/ein.png").setCacheControl("max-age=3600");
Die Datei "ein.png" besitzt eine Lebensdauer von 3600 s = 1 h. Wenn man mehrere Dateien mit einer Lebensdauer versieht, sollten diese leicht unterschiedlich sein, damit zu einem späteren Zeitpunkt nicht alle Dateien gleichzeitig abgerufen werden.
Im Netzt findet man, dass man die Lebensdauer auf bis zu ein Jahr (= 31.536.000) setzen kann. Jedoch begrenzen einige Browser diese Zeit, Firefox z.B. auf 86400 (= 1 Tag).
JavaScript- und CSS-Dateien enthalten eine Menge Text, der den Inhalt lesbar macht, aber für die Verarbeitung nicht notwendig ist. Dieser Text wird natürlich mit übertragen und verschwendet Ressourcen. Minimierer oder Kompressoren schaffen Abhilfe: siehe Tipps zum spinnen: JavaScript-Minifier.
Um veränderte Daten anzuzeigen besteht die Möglichkeit, die Web-Seite regelmäßig in kurzen Abständen
neu zu laden. Dies geht z.B. über den Eintrag <META HTTP-EQUIV="refresh" CONTENT="15">
im <head>-Tag der Seite. Im Content-Attribut steht die Anzahl
an Sekunden, nach der der Browser ein Neuladen der Seite auslösen soll. Diese Methode ist einfach,
sorgt aber dafür, dass die Seite neu übertragen und aufgebaut werden muss. Besser ist es, per JavaScript
ein separaten HTTP-Request abzusetzen, der nur die veränderten Daten lädt.
Zunächst definiert man eine Funktion, die den Request auslöst und auf die Antwort reagiert:
function refreshData() {
var request = new XMLHttpRequest();
request.open("GET","data");
request.addEventListener('load', function(event) {
if (request.status >= 200 && request.status < 300) {
// request.responseText steht der zurück gelieferte Text
// ToDo: Text auswerten und Seiteninhalte anpassen
// z.B.: if (request.responseText == "on") … (s.u.)
} else {
// keine gültige Antwort
// ToDo: Seiteninhalte anpassen
// z.B.: console.log("No answer");
}
});
request.send();
}
Dann sorgt man dafür, dass die Funktion regelmäßig ausgelöst wird:
refreshData();
window.setInterval(refreshData, 1000);
Diesen Code verknüpft man mit dem Load- oder dem DOMContentLoaded-Ereignis, z.B.:
document.addEventListener('DOMContentLoaded', function(event) {
refreshData();
window.setInterval(refreshData, 1000);
});
Der Web-Server muss auf die HTTP-Anfrage zur URL "data" entsprechend antworten. z.B.:
on("/data", HTTP_ANY, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", Relais.isOn() ? "on" : "off");
});
liefert "on" bzw. "off" als Antwort, je nach Stellung des Relais.
Das Odometer-Control habe ich aus den SteelSeries-Canvas von Gerrit Grunwald (HanSolo) extrahiert und ein wenig angepasst. Das Steuerelement zeichnet sich auf ein existierendes <canvas>-Element.
<canvas id="canvasOdometer" width="240" height="90" style="background-color:#D0D0D0"></canvas>
Es lässt sich über den Konstruktor konfigurieren:
var UrsOdometer = function (canvas, parameters)
z.B.:
odometer1 = new UrsOdometer('canvasOdometer', {height:60, decimals:1, digits:4, left:15, top:15});
Argument | Bedeutung | Anmerkung |
---|---|---|
canvas | <canvas>-Element oder ID eines <canvas>-Elements. | Wird ein String übergeben, wird das <canvas>-Element per document.getElementByID gesucht. Ansonsten wird direkt mit dem Element gearbeitet. Wird das Parameter-Member context (s.u.) angegeben, wird dieses Argument nicht ausgewertet. |
parameters | Ein Objekt, dessen Member Konfigurationsangaben für das Steuerelement bereit stellen. | Am einfachsten geschieht dies, indem man das Objekt im Konstruktor erzeugt: {<Name>:<Wert> [, <Name>:<Wert>]∞} (s. Beispiel). Die Möglichen Parameterangaben sind in der folgenden Tabelle aufgelistet. |
Folgende Parameter-Angaben im Konstruktor werden ausgewertet:
Parameter | Typ | Bedeutung | Voreinstellung |
---|---|---|---|
context | CanvasRenderingContext2D | Das Konstruktor-Argument canvas wird nicht ausgewertet. Statt dessen erfolgt die Ausgabe über das hier angegebene Objekt. | NULL |
left | Number | Position der linken Kante des Steuerelements. | 0 |
top | Number | Position der Oberkante des Steuerelements. | 0 |
height | Number | Höhe der Ziffern. Die Weite ergibt sich automatisch. Wenn top angegeben wird, sollte auch height angegeben werden. |
Höhe des <canvas>-Elements. |
resize | Boolean | Die Größe des <canvas>-Elements wird auf die Größe
des Steuerelements eingestellt. Die Angabe ist nur dann sinnvoll, wenn left und top nicht angegeben werden. |
false |
digits | Integer | Anzahl Vorkommastellen. | 6 |
decimals | Integer | Anzahl Nachkommastellen. | 1 |
valueForeColor | Color | Farbe der Vorkommastellenziffern. | '#F8F8F8' (nahezu weiß) |
valueBackColor | Color | Farbe des Hintergrunds der Vorkommastellenziffern. | '#050505' (nahezu schwarz) |
decimalForeColor | Color | Farbe der Nachkommastellenziffern. | '#F01010' (nahezu rot) |
decimalBackColor | Color | Farbe des Hintergrunds der Nachkommastellenziffern. | '#F0F0F0' (sehr helles grau) |
font | String | Schriftart | 'sans-serif' |
wobbleFactor | Number | Vertikaler Zufallsversatz der einzelnen Ziffern | 0.07 |
value | Number | Anzeigewert. Teile der letzten Stelle werden durch teilweise Rotation der letzten Ziffer angezeigt. Bei Werten, die größer als der maximale Anzeigewert sind, werden die führenden Ziffern abgeschnitten. |
0 |
Methode | Bedeutung | Anmerkung |
---|---|---|
setValue (NewVal) | Legt einen neuen Anzeigewert fest. | Das Steuerelement wird neu gezeichnet. |
getValue() | Ruft den aktuellen Anzeigewert ab. | |
repaint() | Bewirkt ein Neuzeichnen des Steuerelements. |
Das folgende Beispiel zeigt die Verwendung dieses Steuerelements:
<!DOCTYPE html>
<html manifest="demo.manifest"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>UrsOdometer</title>
<script src="UrsOdometer-min.js"></script>
</head>
<body onload="init()" >
<canvas id="canvasOdometer" width="240" height="90" style="background-color:#D0D0D0"></canvas>
<script>
var odo, n = 999997.76;
function init() {
odo = new UrsOdometer('canvasOdometer', {height:60, decimals:1, digits:4, left:15, top:15});
updateOdo();
}
function updateOdo() {
n += 0.005;
odo.setValue(n);
setTimeout("updateOdo()", 50);
}
</script>
</body>
</html>
Das RAR-Archiv enthält folgende Dateien:
Das LCD-Display habe ich aus den SteelSeries-Canvas von Gerrit Grunwald (HanSolo) extrahiert (dort heißt das entsprechende Element 'single') und ein wenig angepasst. Das Steuerelement zeichnet sich auf ein existierendes <canvas>-Element.
<canvas id="canvasLCD" height="150" width="200" style="background-color: #c0c0c0">
</canvas>
Es lässt sich über den Konstruktor konfigurieren:
var UrsLCDDisplay = function (canvas, parameters)
z.B.:
var LCD1 = new UrsLCDDisplay('canvasLCD
', {height:60, width:155, left:15, top:15});
Argument | Bedeutung | Anmerkung |
---|---|---|
canvas | <canvas>-Element oder ID eines <canvas>-Elements. | Wird ein String übergeben, wird das <canvas>-Element per document.getElementByID gesucht. Ansonsten wird direkt mit dem Element gearbeitet. Wird das Parameter-Member context (s.u.) angegeben, wird dieses Argument nicht ausgewertet. |
parameters | Ein Objekt, dessen Member Konfigurationsangaben für das Steuerelement bereit stellen. | Am einfachsten geschieht dies, indem man das Objekt im Konstruktor erzeugt: {<Name>:<Wert> [, <Name>:<Wert>]∞} (s. Beispiel). Die Möglichen Parameterangaben sind in der folgenden Tabelle aufgelistet. |
Folgende Parameter-Angaben im Konstruktor werden ausgewertet:
Parameter | Typ | Bedeutung | Voreinstellung |
---|---|---|---|
context | CanvasRenderingContext2D | Das Konstruktor-Argument canvas wird nicht ausgewertet. Statt dessen erfolgt die Ausgabe über das hier angegebene Objekt. | NULL |
left | Number | Position der linken Kante des Steuerelements. | 0 |
top | Number | Position der Oberkante des Steuerelements. | 0 |
height | Number | Höhe des Steuerelements. | Höhe des <canvas>-Elements. |
width | Number | Breite des Steuerelements. | Breite des <canvas>-Elements. |
align | String | Textausrichtung: "left", "center" oder "right" | "right" |
value | String | Anzeigewert. |
'0' |
Methode | Bedeutung | Anmerkung |
---|---|---|
setValue (newValue) | Legt einen neuen Anzeigewert fest. | Das Steuerelement wird neu gezeichnet. |
setAlign (newAlign) | Textausrichtung festlegen, | "left", "center" oder "right" sind möglich. |
repaint () | Bewirkt ein Neuzeichnen des Steuerelements. |
Das folgende Beispiel zeigt die Verwendung dieses Steuerelements:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<title>URS LCD Display</title>
</head>
<body onload="init()">
<canvas id="canvasLCD" height="150" width="200" style="background-color: #c0c0c0">
</canvas>
<script>
var LCD1;
function init() {
// Anzeige initialisieren
LCD1 = new UrsLCDDisplay('canvasLCD', {
width: 155,
height: 40,
left: 30,
top: 50
});
// Wert setzen
LCD1.setValue("3d 18h 44s")
}
</script>
<script src="UrsLCDDisplay-min.js"></script>
</body>
</html>
Das RAR-Archiv enthält folgende Dateien:
Die Quelle für dieses Steuerelement ist das Sliding Scale Gauge von Tefik Becirovic auf Code Project. Das Steuerelement zeichnet sich auf ein existierendes <canvas>-Element.
<canvas id="slideCanvas" width="330" height="90" style="background-color:#D0D0D0"></canvas><br>
Es lässt sich über den Konstruktor konfigurieren:
var UrsSlidingScale = function (canvas, parameters)
z.B.:
SlidingScale = new UrsSlidingScale('slideCanvas', {height:60, width:300, top: 15, left: 15, scaleColor: '#4682b4'});
Argument | Bedeutung | Anmerkung |
---|---|---|
canvas | <canvas>-Element oder ID eines <canvas>-Elements. | Wird ein String übergeben, wird das <canvas>-Element per document.getElementByID gesucht. Ansonsten wird direkt mit dem Element gearbeitet. Wird das Parameter-Member context (s.u.) angegeben, wird dieses Argument nicht ausgewertet. |
parameters | Ein Objekt, dessen Member Konfigurationsangaben für das Steuerelement bereit stellen. | Am einfachsten geschieht dies, indem man das Objekt im Konstruktor erzeugt: {<Name>:<Wert> [, <Name>:<Wert>]∞} (s. Beispiel). Die Möglichen Parameterangaben sind in der folgenden Tabelle aufgelistet. |
Folgende Parameter-Angaben im Konstruktor werden ausgewertet:
Parameter | Typ | Bedeutung | Voreinstellung |
---|---|---|---|
context | CanvasRenderingContext2D | Das Konstruktor-Argument canvas wird nicht ausgewertet. Statt dessen erfolgt die Ausgabe über das hier angegebene Objekt. | NULL |
left | Number | Position der linken Kante des Steuerelements. | 0 |
top | Number | Position der Oberkante des Steuerelements. | 0 |
height | Number | Höhe des Steuerelements. Wenn top angegeben wird, sollte auch height angegeben werden. |
Höhe des <canvas>-Elements. |
width | Number | Breite des Steuerelements. Wenn left angegeben wird, sollte auch width angegeben werden. |
Breite des <canvas>-Elements. |
resize | Boolean | Die Größe des <canvas>-Elements wird auf die Größe
des Steuerelements eingestellt. Die Angabe ist nur dann sinnvoll, wenn left und top nicht angegeben werden. |
false |
scaleRange | Integer | Wertebereich des Skalenteils, der im Steuerelementfenster angezeigt wird. | 100 |
largeTicksCount | Integer | Anzahl der Hauptstriche innerhalb von scaleRange. | 5 |
smallTicksCount | Integer | Anzahl der Teilstriche innerhalb von largeTicksCount. | 4 |
largeTicksLength | Integer | Länge der Hauptstriche. | 15 |
smallTicksLength | Integer | Länge der Teilstriche. | 10 |
scaleColor | Color | Farbe der Skalenbeschriftung. | '#00000' (schwarz) |
value | Number | Anzeigewert. Teile der letzten Stelle werden durch teilweise Rotation der letzten Ziffer angezeigt. Bei Werten, die größer als der maximale Anzeigewert sind, werden die führenden Ziffern abgeschnitten. |
0 |
Methode | Bedeutung | Anmerkung |
---|---|---|
setValue (NewVal) | Legt einen neuen Anzeigewert fest. | Das Steuerelement wird neu gezeichnet. |
getValue() | Ruft den aktuellen Anzeigewert ab. | |
repaint() | Bewirkt ein Neuzeichnen des Steuerelements. |
Das folgende Beispiel zeigt die Verwendung dieses Steuerelements:
<!DOCTYPE html>
<html manifest="demo.manifest">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>UrsSlidingScale</title>
<script src="UrsSlidingScale-min.js"></script>
</head>
<body onload="init()">
<canvas id="slideCanvas" width="330" height="90" style="background-color:#D0D0D0"></canvas><br>
<script>
var sld;
var sldVal = 124;
var UpDown = 1;
function init() {
sld = new UrsSlidingScale('slideCanvas',
{height:60, width:300, top: 15, left: 15, scaleColor: '#4682b4', value: sldVal});
updateSld();
}
function updateSld() {
if (UpDown == 1) { // steigende Werte
if (sld.getValue() < sldVal) { // weiter erhöhen
sld.setValue(sld.getValue() + 0.1);
} else { // neuer niedriger Wert
UpDown = -1;
sldVal -= Math.random() * 20;
if (sldVal < 0)
sldVal = 0;
}
} else { // fallende Werte
if (sld.getValue() > sldVal) { // weiter reduzieren
sld.setValue(sld.getValue() - 0.1);
} else { // neuer höherer Wert
UpDown = 1;
sldVal += Math.random() * 20;
}
}
setTimeout("updateSld()", 1);
}
</script>
</body>
</html>
Das RAR-Archiv enthält folgende Dateien:
Wenn sich die Werte der im folgenden beschriebenen Controls instantan ändern sieht das nicht immer gut aus. Schöner wäre ein gleitender Übergang. Von den Controls UrsOdometer und UrsSlidingScale gibt es deshalb eine erweiterte Version UrsOdometerEx und UrsSlidingScaleEx. Diese besitzen eine zusätzliche Methode setValueAninamted(newValue, duration). Bei der Verwendung dieser Methoden wird der neue Wert in mehreren Schritten eingestellt, um das physikalische Verhalten entsprechender Anzeigegeräte nachzubilden. Dies nennt man Tweening.
Die SteelSeries-Canvas-Bibliothek enthält auch ein Modul tween.js, durch dass die Zwischenschritte elegant berechnet werden können. Leider habe ich keine komplette Dokumentation von dieses Typs gefunden. Aus diversen lässt sich aber das Folgende ableiten.
Wenn das Anzeige-Element einen neuen Wert erhalten soll, wird dessen Methode setValueAninamted(newValue, duration) aufgerufen. newValue ist der neue Anzeigewert und duration ist die Zeit in Sekunden innerhalb der der neue Wert erreicht werden soll.
In setValueAninamted wird ggf. eine bereits eine aktive Animation gestoppt. Dann wird ein neues Objekt des Typs Tween angelegt. Dem Konstruktor werden in dieser Anwendung mitgegeben:
Die Übergangsfunktion legt fest, wie sich der aktuelle Wert zum neuen entwickelt. Setzt man z.B. hier die in tween.js definierte Funktion Tween.regularEaseOut ein, erfolgt zunächst eine schnelle Annäherung an den Zielwert. Mit der Zeit nimmt die Änderung jedoch ab (Abbremsung). Der Werteverlauf ist dann etwa folgender:
Weitere Funktionen unter WEB-Controls: Tweening.
Bevor das Tweening gestartet wird, wird dem Tween-Objekt ein Event-Handler für das Ereignis onMotionChanged mitgegeben. Der Parameter der Handler-Methode enthält u.a. den neu einzustellenden Wert. Dieser wird auf die entsprechende Eigenschaft des Controls übertragen (value) und eine Neuzeichnung veranlasst.
Wenn die Anzeigedaten regelmäßiges von einem Server nachgeladen werden, empfiehlt es sich, die duration etwas größer als das Nachlade-Interval anzugeben. Die Anzeige stoppt dann nicht zwischen zwei Messwerten.
Beim Odometer zeigt die lineare Tweening-Funktion ein ansprechendes Verhalten, beim SlidingScale liefert Tween.strongEaseOut gute Ergebnisse. Andere Funktionen können im Quellcode angegeben werden.
Hinweis: Die angezeigten Werte sind nicht synchronisiert sondern Zufallswerte.
Das RAR-Archiv enthält folgende Dateien: