ESP8266 Webserver als serielle Konsole

Ich habe hier vor einiger Zeit das Projekt gesehen: Es war eine kleiner Webserver der auf dem RX-PIN des ESP8266 Daten Empfing und auf einem Webserver 1:1 darstelle ganz analog zum seriellen Monitor. Als eine Browser serielle Konsole. Ich weiß nur das es einer der üblichen "Verdächtigen" war. Ich dachte es war indem zeigt her eure geilen Projekte aber ich habe den durchgesehen aber nix gefunden. Habt ihr ne Ahnung wer/wo es war?

Gruß DerDani

Gesehen habe ich das nicht aber ich könnte mir vorstellen, auf der Weboberfläche eine Textarea zu haben, an die die Daten von RX angehängt werden und ein Clear-Button, um die Textarea zu löschen.

Entweder ein Refresh von 1 Sekunde einstellen oder vom Browser her eine Websock-Verbindung eröffnen und die Daten pushen (also die Zeichen direkt von RX zum Browser zu schieben und dort zusammen setzen). Die 2. Variante ist eleganter.

Gruß Tommy

Hier mal eine kleine Designstudie dazu. Das ist als Entwurf zu betrachten und noch weitab von Perfektion.

Auf dem ESP8266:

#include <ESP8266WiFi.h>
#include <WebSocketsServer.h> // https://github.com/Links2004/arduinoWebSockets

// WiFi-Settings
// <= 31 Zeichen
const char *ssid = "ssid";
// >= 8 oder <= 63 Zeichen oder NULL
const char *password = "passwort";

// WiFi-Config anpassen
// feste IP zuweisen, für den Aufruf
const IPAddress myIP(192,168,178,47); // Der ESP8266
const IPAddress gateway(192,168,178,1);
const IPAddress dnsServer(192,168,178,1);
const IPAddress subnet(255,255,255,0);


// Port, unter dem der Websocket erreichbar ist
#define PORT 81

// Hostname, um die ESPs unterscheiden zu können
const char *myhostname = "Wemos D1";

// besteht eine Verbindung?
boolean isConnected = false;

// WebSocketServer
WebSocketsServer ws = WebSocketsServer(PORT);

// Sendepuffer
const uint8_t bufSize = 32;
uint8_t buf[bufSize];

// Wir nutzen nur Connect/ Disconnect
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
  switch(type) {
        case WStype_DISCONNECTED:
            Serial.printf("[%u] Disconnected!\n", num);
            isConnected = false;
            ws.sendTXT(num, "MSG:Disonnected");
            break;
        case WStype_CONNECTED:
            {
              IPAddress ip = ws.remoteIP(num);
              Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
              isConnected = true;
				      // send message to client
 				      ws.sendTXT(num, "MSG:Connected to " + String(myhostname));
            }
            break;
  }  
}

void setup() {  
byte counter = 0;
  Serial.begin(115200);
  Serial.println("\nStart");
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  // Eigene Config, besonders IP
  WiFi.config(myIP, gateway, subnet, dnsServer);
  WiFi.begin(ssid, password);
	// Wait for connection
	while (WiFi.status() != WL_CONNECTED) {
		delay (500);
		Serial.print (".");
    counter++;
    if (counter > 100) {
      Serial.println("Kein WLAN. Restart!");
      ESP.restart();
    }
	}
	Serial.print("\nConnected to ");
	Serial.println(ssid );
	Serial.print("IP address: ");
	Serial.println(WiFi.localIP());
  // WebSocket
  ws.begin();
  ws.onEvent(webSocketEvent);
}

size_t writeChar(uint8_t c) {
static uint8_t idx = 0; 
byte len;
  if (!isConnected) return 0;
  buf[idx++] = c;
  // buf[idx] = '\0';
  // Serial.print(idx); Serial.print(".");Serial.println((char *)buf);
  if (idx == bufSize -1 || c == 0xd) {
    // Senden
    buf[idx] = '\0';
    // ich verwende broadcastTXT, um an alle Clients zu senden, ansonsten die Zeilen wechseln
    // ws.sendTXT(0, buf, idx-1);
    ws.broadcastTXT(buf, idx-1);
    len = idx - 1;
    idx = 0;    
    return len;
  }
  return 0;
}

void loop() {
  ws.loop(); // eingehende Daten WebSocket
  // Daten von Serial nach WebSocket schreiben
  while (Serial.available()) {
    char c = Serial.read();
    Serial.print(c,HEX);
    writeChar(c);
  }
}

Als lokale html-Seite, die im Browser aufgerufen wird (z.B. WebSerial):

<!DOCTYPE html>
<html style="height:100%;">
    <head>
        <meta charset="utf-8">
        <title>
            WebSerial
        </title>
        <style>
        table{border-collapse: collapse;border:none;}
        td {padding:5px;}
        button {width:100px;}
        </style>

		<script language = "javascript" type = "text/javascript">
			var wsUri;
			var output;
      var zidx = 0;
      var websocket;
      zarr = [];
      var isStop = false;
      
      // Ausgabe anhalten und weiterführen
      function setStop() {
        var ele = document.getElementById('stop');
        if (ele.innerText == 'Stop') {
          isStop=true;
          ele.innerText = 'Weiter';
        }
        else {
          isStop = false;
          ele.innerText = 'Stop';
        }
      }
      
      // Statusmeldungen ausgeben
      function setStatus(text) {
        var status = document.getElementById('msg');
        status.textContent = text;
      }
      
      // Check input IP und Port
      function checkIn() {
        var ele1 = document.getElementById('ip');
        var ele2 = document.getElementById('port');
        var ele3 = document.getElementById('btn');
        if (ele1.value > ' ' && ele2.value > ' ') ele3.disabled = false;
        else ele3.disabled = true;        
      }
      
      function init() {
        setStatus("DISCONNECTED");
        checkIn();
      }

      // Connect und Disconnect  
      function connect() {
        var ele = document.getElementById('btn');
        var txt = ele.innerText;
        if (ele.innerText == 'Connect') {
          setStatus('Connecting');
          var ip = document.getElementById('ip').value;
          var port = document.getElementById('port').value;
          wsUri='ws://'+ip+':'+port+'/';
          ele.innerText = 'Disconnect'
          zidx = 0;
          zarr = [];
          document.getElementById('ausgabe').value = '';
          testWebSocket();           
        }
        else {
          setStatus('Disconnecting');
          ele.innerText = 'Connect';
          websocket.close();
        }
      }
           
      // Websocketfunktionen
			function testWebSocket() {
			    websocket = new WebSocket(wsUri);
			    websocket.onopen = function(evt) {
			        // console.log("CONNECTED");
              setStatus("CONNECTED");
			    };
			    websocket.onclose = function(evt) {
			        // console.log("DISCONNECTED");
              setStatus("DISCONNECTED");
			    };
          // hier kommt was vom ESP
			    websocket.onmessage = function(evt) {
            var daten;
            // console.log("Message");
            // console.log(evt.data);
            // Eine Statusmeldung?
            if (evt.data.substr(0,4) == 'MSG:') {
              setStatus(evt.data);
              // Reboot angeforder und ist erlaubt
              if (evt.data.substr(0,22) =='MSG:Reboot vorbereitet') {
                connect();
              }
            }
            else {
              // ein "normaler" output
              if (evt.data.substr(0,4) == 'TXT:') zarr[zidx++] = evt.data.substr(4);
              else zarr[zidx++] = evt.data;
              if (zidx > 500) { // max. 500 Zeilen speichern
                zarr.splice(0,1); // erstes Element löschen
                zidx--;
              }
            }
            if (!isStop) {
              var ele = document.getElementById('ausgabe');
              ele.value = zarr.join("\n");
              if (document.getElementById('scroll').checked) ele.scrollTop = ele.scrollHeight;
            }
			    };
			    websocket.onerror = function(evt) {
			        console.log("ERROR: " + evt.data);
			    };
			}
			
    </script>
    </head>
    <body style="height:95%;" onload="init();">
    <table id="tab" style="width:100%;height:100%;">
      <tr style="background-color: #dadfcb"><td style="width:100px; height:20px;">IP: </td><td style="width:140px"><input type="text" size="16" id="ip" value="192.168.178.47" onkeyup="checkIn();"/></td>
          <td style="width:60px;">Port: </td><td style="width:60px;"><input type="text" size="5" id="port" value="81"  onkeyup="checkIn();"/></td><td></td>
          <td style="text-align:right;"><button type="button" id="btn" onclick="connect();">Connect</button></td></tr>
      <tr style="background-color: #fecc80;"><td>Status: </td><td colspan="5"><span id="msg" style="width:100%;"></span></td></tr>
      <tr style="background-color: #dadfcb"><td>Ausgabe: </td><td><button type="button" onclick="setStop();" id="stop">Stop</button></td>
      <td colspan="4"><label><input type="checkbox" id="scroll" checked> nach unten scrollen</label></td></tr>
      <tr style="background-color: #fecc80;"><td  style="height:97%;" colspan="6"><textarea id="ausgabe" style="width:99%; height:96%;"></textarea></td></tr> 
    </table>
    </body>
</html>

Das mal als schnell (und wohl auch etwas unsauber) zusammengeklöppelte Anregung. Ich habe nur mit dem seriellen Monitor getestet.

Gruß Tommy

Sehr interessant!

Kann man doch sicherlich auch dafür verwenden, die seriellen Ausgaben auszugeben, wenn der ESP8266-01 schon als Webserver benutzt wird, oder? Wie geht das dann, gibt der die Konsolenmeldungen auf einer anderen Webseite aus? Bin leider noch nicht so fit in der Programmierung, dass ich das selbst machen könnte. Habe bisher den ESP-01 so im Einsatz: Ich verbinde mich per Handy mit dem WiFi-Netzwerk, das der Winzling bereitstellt, und schalte dann über eine Webseite 192.168.4.1 einen "Schalter". Würde nun gerne zusätzlich noch die seriellen Ausgaben umleiten, damit ich den Controller wasserfest vergießen und unsichtbar verbauen kann.

Mit dem ESP8266-01 habe ich noch nicht gearbeitet, ich nehme lieber die ESP8266-Varianten mit USB und mehr Speicher, wie den Wemos D1 mini. Ansonsten habe ich mal 2 Debug-Varianten übers WLAN gebaut. Mit Websockets zum Browser oder über Telnet zum Terminal.

Gruß Tommy

Hallo, das mit "Telnet zum Terminal" liest sich gut. Man kann also ganz normal die Befehle "Print" und Println" im Script verwenden und erhält die Ausgabe in einer Putty-Session? Einbinden muss man die beiden Dateien

include DebugClass.h include DebugClass.cpp

die man im Verzeichnis des Speicherorts des zu erstellenden Scripts mit ablegt. Soweit ist es mir klar. Muss ich die "Print.h" ebenfalls mit einbinden?

Jetzt verbinde ich mich aber auf keinen Router, sondern lasse den ESP ein WLAN aufmachen mit einer Funktionsseite 192.168.4.1. Findet eine Telnetsession auf diese IP-Adresse mit Port 23 dann die Debug-Ausgabe? Auskommentieren im Beispielscript müsste ich dann wahrscheinlich diese drei Zeilen

WiFi.mode(WIFI_STA);

WiFi.config(myIP, gateway, subnet, dnsServer);

WiFi.begin(ssid, password);

stimmt das? Bin noch nicht so weit fortgeschritten in der Arduino-Programmierung, bitte daher um Nachsicht, falls es dumme Fragen sein sollten...

Es ist ein Beispielprogramm dabei. Orientiere Dich an diesem, da siehst Du, dass Du nur die DebugClass.h includen musst.

Auskommentieren geht aber anders, da solltest Du ein paar Grundlagen lernen. Du musst ihn dann als AP betreiben. In dem Modus habe ich ihn nicht betrieben/getestet, da ein PC mit Putty bei mir kein direktes WLAN hat, mit dem er auf den ESP8266 zugreifen kann. Das musst Du probieren.

Gruß Tommy

Da muss ich auch mal ran hört sich nach einer ziemlich coolen Lösung an... Respekt Tommy für die Arbeit.

Gruß DerDani

io2345: Jetzt verbinde ich mich aber auf keinen Router, sondern lasse den ESP ein WLAN aufmachen mit einer Funktionsseite 192.168.4.1. Findet eine Telnetsession auf diese IP-Adresse mit Port 23 dann die Debug-Ausgabe?

Warum lässt du den ESP nicht als Station an deinem Netz? Ist doch viel einfacher

Der ist ja in einem Moped eingebaut - da kann man per WLan und Handy was schalten

Hier der Beispiel-Code der Telnet-Lösung (Debug-mit-Telnet) angepasst an AccessPoint-Modus. (All credits go to Tommy56)
Die Einstellungen für IP-Adresse, Hostname, SSID und Passwort lassen sich natürlich auch wieder in eine Settings.h auslagern, aber das verwirrt Anfänger (wie mich) eher.

#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino

#include <ArduinoOTA.h>  // OTA-Handling
//#include "settings.h"

#define HOST_NAME "ESP123"  //Host-Name nach Bedarf anpassen

#include "DebugClass.h"        

uint32_t aktMillis, lastMillis, lastMillis_h;
const uint32_t INTERVALL = 1 * 3 * 1000;  // Neue Ausgabe alle drei Sekunden


// DebugClass Debug;

void setup() {
uint8_t counter = 0;
Serial.begin(115200);
Serial.println("\n\nStart");

IPAddress apIP(192, 168, 4, 10);  //IP-Adresse kann nach Wunsch angepasst werden

// Betrieb als AccessPoint
WiFi.mode(WIFI_AP);
WiFi.softAP("ESPdebug", "password");   //Name des erstellten WLans und Passwort hier einstellen
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));

initOTA();

// Debug initialisieren
Debug.begin(HOST_NAME); 
// Debug.setRebootEnabled(true); // Enable the reset command?

}

void loop() {
ArduinoOTA.handle();  // Wenn OTA genutzt wird
// Debug-Handler
Debug.handle();
aktMillis = millis();
if (aktMillis - lastMillis >= INTERVALL) {
 // Debug.print("Text123456");
 // Debug.println("7890");
 // Debug.println("Text3");
 // Debug.printf("%lu %s\n",micros(),"Test");
 debPrint("Text123456");      //diese drei Zeilen sind nur eine Demo-Ausgabe
 debPrintln("7890");          //um zu zeigen, dass es funktioniert
 debPrintln("Text3");         //hier kann man natürlich etwas sinnvolleres ausgeben lassen
 debPrintf("%lu %s\n",micros(),"Test");
 lastMillis = aktMillis;
}
}

Beim Zugriff über Putty dort den Modus umstellen auf “Telnet” bei “Connection Type” (steht standardmäßig auf “SSH”)

Debug_Telnet_APMode.ino (1.18 KB)

Danke für die Anpassung und den Test.

Setze Deinen Code bitte in Codetags (</>-Button oben links im Forumseditor oder [code] davor und [/code] dahinter ohne *).
So ist er auch auf portablen Geräten lesbar. Das kannst Du auch noch nachträglich ändern.

Gruß Tommy

Edit: Ich habe im Ursprungsthread auf Deinen Beitrag verlinkt.

Ich wollte das nun mal austesten, bekomme aber keine laufenden Ausgaben in Putty.
Das mit den komandos kommt, sowas : “1T1T7P123) 1T7” auch ab und zu mal. Aber das was ich alle 5 Sekunden sende, nicht. Das auf der seriellen kommt an. Wemos D1 mini. Habe einmal Putty per Telnet dran, und auch per seriell. Was habe ich nicht bedacht?

#include <INTERVAL.h>

// für ESP8266 und ESP32
#if defined(ESP8266)
#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
#elif defined(ESP32)
#include <WiFi.h>
#else
#error Only for ESP8266 or ESP32
#endif

#include <ArduinoOTA.h>  // OTA-Handling
#include "settings.h"

#define HOST_NAME "ESP123"

#include "DebugClass.h"

uint32_t aktMillis, lastMillis, lastMillis_h;
const uint32_t INTERVALL = 1 * 10 * 1000;  // 1 Minute


//DebugClass Debug;




void setup() {
  uint8_t counter = 0;
  Serial.begin(115200);
  Serial.println("\n\nStart");

  // Betrieb als Station
  WiFi.mode(WIFI_STA);
  //  WiFi.config(myIP, gateway, subnet, dnsServer);     // ich will dhcp
  WiFi.begin(ssid, password);
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay (500);
    Serial.print(".");
    counter++;
    if (counter > 100) {
      Serial.println("Kein WLAN. Restart!");
      ESP.restart();
    }
  }
  Serial.print("\nConnected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  initOTA();

  // Debug initialisieren
  Debug.begin(HOST_NAME);

  // Debug.setRebootEnabled(true); // Enable the reset command?

}

void loop() {
  ArduinoOTA.handle();  // Wenn OTA genutzt wird
  // Debug-Handler
  Debug.handle();
  INTERVAL(5000)
  {
    /**/
    Debug.println("Text123456");
    Serial.println("7890");
    Serial.println(millis() / 1000);
    Debug.printf("%lu %s\n", micros(), "Test");
    debPrint("Text123456");
    debPrintln("7890");
    debPrintln("Text3");
    debPrintf("%lu %s\n", micros(), "Test");
    lastMillis = aktMillis;

  }
}

Funktioniert der Beispielsketch im Originalposting? Wenn Du DHCP machst, kann die IP sich ändern. Das musst Du in Deiner Telnet-Verbindung mit Putty berücksichtigen.

Gruß Tommy

Ich habe es gerade getestet, da hat sich wohl irgendwas in den ESP-Libs geändert. In der 2.4.2 funktioniert es bei mir auch nicht mehr. Ich habe eine neue Version der Lib erstellt, bei der Write auch nur write macht.

Im Putty muss eingestellt sein: Terminal → Implicit CR in every LF, damit auch printf mit ‘\n’ funktioniert.

Gruß Tommy

DebugClass.zip (2.12 KB)

Die Kommunikation mit Putty geht. Nur eben die debugbAusgaben nicht. Auch in den Beispielen nicht. Werde mal deine neue zip einbauen