Motivation:
Normalerweise muß der Client, also die HTML-Seite des Browsers, aktiv werden und sich Informationen beim Server abholen, um diese anzeigen zu können. Bei zyklisch wiederkehrenden Daten wie Temperaturen oder Zeiten ist das unproblematisch.
Bei sporadisch auftretenden Informationen wäre es aber wünschenswert, könnte auch der Server die Initiative ergreifen. Entsprechende Fragen im Forum wurden mit dem Begriff "Websockets" beantwortet. Also habe ich im weltweiten Netz gesucht und auch Antworten gefunden. Diese möchte ich nun in die von mir gerne genutzten Tabs von Fips einbauen.
Anleitung: Einführung zu fipsok.de
IDE 1.8.19 / ESP32-Core 2.0.5 / Hardware:
Ich nutze ausschließlich den ESP32, sollte aber mit wenigen Änderungen auch mit dem ESP8266 funktionieren.
Bibliotheken:
Erstaunlicherweise sind die Websockets nicht Teil des ESP32-Cores für die Arduino-IDE. Also muß man externe Bibliotheken nutzen:
- ESP32 WebSocket Server: Control Outputs (Arduino IDE) ist eine lesenswerte Seite, um die Abläufe zu verstehen. Die verwendete Bibliothek ESPAsyncWebServer enthält einen kompletten Webserver einschließlich Websockets. Dadurch braucht es nur einen Port für beide Komunikationswege.
Für eine eigenständige Anwendung wäre der AsyncWebServer meine Wahl, im Zusammenhang mit den Tabs von Fips scheint es mir eine unnötige Doppelung der Fähigkeiten zu sein. Meine Entscheidung, diese Bibliothek nicht zu verwenden, wäre eventuell einer Diskussion wert. - Die Arduino-IDE bietet arduinoWebSockets an (in der Bibliotheksverwaltung nach "WebSockets" von Markus Sattler suchen!). Die Beispiele für den ESP32 enthalten eine Kommunikation zwischen zwei ESPs, allerdings die Richtung Server → Client nur als Kommentar. Ist nicht das, was ich will, aber die Bibliothek scheint mir geeignet.
Beide Bibliotheken nutzen ESPAsyncTCP.
Ziel:
Basierend auf Esp32 OnBoardLed Tab wird die Spannung eines analogen Eingangs gemessen und nur bei signifikanter Änderung der Wert zur Anzeige an den Browser geschickt.
Dateisystem:
SPIFFS und LittleFS stehen zur Auswahl, wobei die Tendenz zu letzterem geht, weshalb ich dieses verwende. LittleFS_ESP32.ino
Wichtige Stellen:
- Messung.ino:
webSocket.sendTXT(clientID, text); // schickt eine Nachricht an den Client - WebSocket.js:
document.getElementById('msg').innerHTML = e.data;zur Textanzeige - index.html:
<span id='msg'>%MSG%</span>Position für die Anzeige
Programme als Tabs im Editor der Arduino-IDE:
Als Grundlage verwende ich "Esp32 OnBoardLed Tab" zum Umschalten der eingebauten LED. Bewußt verwende ich nicht die Websockets für diese Funktionalität.
Esp32 WebServer Tab
// ****************************************************************
// Sketch Esp32 Webserver Modular(Tab)
// created: Jens Fleischer, 2018-07-06
// last mod: Jens Fleischer, 2020-03-26
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp32
// Software: Esp32 Arduino Core 1.0.0 - 1.0.4
// Getestet auf: ESP32 NodeMCU-32s
/******************************************************************
Copyright (c) 2018 Jens Fleischer. All rights reserved.
This file is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*******************************************************************/
// Der WebServermodular stellt den Haupt Tab dar.
// #include "LittleFS.h" #include <WebServer.h> müssen im Haupttab aufgerufen werden
// Die Funktionalität des ESP32 Webservers ist erforderlich.
// "server.onNotFound()" darf nicht im Setup des ESP32 Webserver stehen.
// Inklusive Arduino OTA-Updates
/**************************************************************************************/
#define LED_BUILTIN 13
#include <zugangsdaten.h>
const char* ssid = STA_SSID; // << kann bis zu 32 Zeichen haben
const char* password = STA_PASSWORD; // << mindestens 8 Zeichen jedoch nicht länger als 64 Zeichen
#define SPIFFS LittleFS // Diese Textersetzung benötigt man für Tabs, die etwas wie `SPIFFS.open()` enthalten.
#include <WebServer.h>
#include <ArduinoOTA.h>
#include <LittleFS.h>
#include <WebSocketsServer.h> // WebSockets by Markus Sattler (https://github.com/Links2004/arduinoWebSockets)
#define DEBUGGING // Einkommentieren für die Serielle Ausgabe
#ifdef DEBUGGING
#define DEBUG_B(...) Serial.begin(__VA_ARGS__)
#define DEBUG_P(...) Serial.println(__VA_ARGS__)
#define DEBUG_F(...) Serial.printf(__VA_ARGS__)
#else
#define DEBUG_B(...)
#define DEBUG_P(...)
#define DEBUG_F(...)
#endif
WebServer server(80); // mit Webserver über Port 80 kommunizieren
WebSocketsServer webSocket(81); // mit Websocket-Server über Port 81 kommunizieren
void setup() {
DEBUG_B(115200);
DEBUG_F("\nSketchname: %s\nBuild: %s\t\tIDE: %d.%d.%d\n\n", __FILE__, __TIMESTAMP__, ARDUINO / 10000, ARDUINO % 10000 / 100, ARDUINO % 100 / 10 ? ARDUINO % 100 : ARDUINO % 10);
littlefs();
admin();
Connect();
setupWebSocket(); // Websocket-Server einrichten
// setupTime();
// espboardLed();
onboardLed();
// bme280();
// dht22(); // Die Tabs die du nutzen möchtest, musst du einkommentieren
// bh1750();
// setupSchaltUhr();
ArduinoOTA.onStart([]() {
//save(); //Einkommentieren wenn Werte vor dem Update gesichert werden sollen
});
ArduinoOTA.begin();
server.begin();
DEBUG_P("HTTP Server gestartet\n\n");
}
void loop() {
ArduinoOTA.handle();
server.handleClient();
webSocket.loop(); // Websocket-Ereignisse abhandeln
messung(); // Meßwert ermitteln
if (millis() < 0x2FFF || millis() > 0xFFFFF0FF) runtime(); // Auskommentieren falls du den Admin Tab nicht nutzen möchtest
// emailResponse();
// dualSchaltuhr(); // Die Tabs die du nutzen möchtest, musst du Einkommentieren
}
Messung.ino
const byte potiPin = 35;
uint8_t clientID = 0;
void messung() {
const int DELTA = 100;
static int altWert = 5000;
int aktWert = analogRead(potiPin);
if (altWert - aktWert > DELTA || aktWert - altWert > DELTA) {
altWert = aktWert;
float spg = aktWert * 3.3 / 4096;
String text = String("Spannung: ") + String(spg) + String(" V");
webSocket.sendTXT(clientID, text); // schickt eine Nachricht an den Client
}
}
WSServer.ino
void setupWebSocket()
{
webSocket.begin(); // Websocket-Server starten
webSocket.onEvent(webSocketEvent); // wenn eine Websocket-Nachricht reinkommt, gehe zur Funktion 'webSocketEvent'
DEBUG_P("WebSocket server started.");
}
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) // When a WebSocket message is received
{
switch (type) {
case WStype_DISCONNECTED: // if the websocket is disconnected
clientID = 0;
DEBUG_F("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED: { // if a new websocket connection is established
clientID = num;
IPAddress ip = webSocket.remoteIP(num);
DEBUG_F("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
}
break;
case WStype_TEXT: // if new text data is received
DEBUG_F("[%u] get Text: %s\n", num, payload);
break;
default:
break;
}
}
Dateien für LittleFS
Diese Dateien werden auf dem Rechner im Verzeichnis data abgelegt. Für das Upload-Programm der Arduino-Bibliothek ist dieser Name verbindlich.
WebSocket.js
var connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']);
connection.onopen = function () {
connection.send('Connect ' + new Date());
document.getElementById('msg').innerHTML = e.data;
};
connection.onerror = function (error) {
console.log('WebSocket Error ', error);
};
connection.onmessage = function (e) {
console.log('Server: ', e.data);
document.getElementById('msg').innerHTML = e.data;
};
connection.onclose = function(){
console.log('WebSocket connection closed');
};
index.html
<!DOCTYPE html> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style32.css">
<title>Onboard Led</title>
<script src="WebSocket.js" type="text/javascript"></script>
<style>
section {
align-items: center;
text-align: center;
background-color: black;
width: 200px;
height: 40px;
border: .15em solid #909294;
box-shadow: 5px 10px 5px #5a5a5b;
border-radius: .2em;
}
span {
color: #00ff05;
position: relative;
top: 0.7em;
font-weight: bold
}
</style>
</head>
<body>
<h2>ESP32 Dev Module</h2>
<h2>Onboard-Led schalten<br>Spannung anzeigen</h2>
<button class="button"></button>
<p></p>
<section>
<span id='msg'>%MSG%</span>
</section>
<script>
function once(arg) {
fetch('bled' + arg).then(function (response) {
return response.text();
}).then(function (text) {
elem.innerHTML = 'LED ' + text;
text == 'Ein' ? elem.style.backgroundColor = '#adff2f' : elem.style.backgroundColor = 'red';
});
}
var elem = document.querySelector('button');
elem.addEventListener('click', function () {
once('?zap=');
});
once('');
</script>
</body>
</html>
Alle Dateien:
ESP_WebSocket_server2client.zip (16,5 KB)
Browseranzeigen
EDIT Mai 2025 nach Hinweisen von @noiasca
