HTML Kochbuch mit ESP8266 und Arduino IDE

Mit der Arduino IDE kann man Programme für den ESP8266 entwickeln, die über WLAN und WEB-Browser die Anwendung steuern. Die Benutzeroberfläche und die Benutzeraktionen werden mit Hilfe von HTML definiert.

Der Vorteil dieses Konzepts liegt darin, dass man mit PC, Tablet oder Smartphone die Anwendung steuern kann. Das ist unabhängig von Betriebssystem und Gerätehardware möglich. WLAN-Fähigkeit und Web-Browser sind ohnehin so gut wie immer vorhanden und reichen völlig aus.

Hier wird gezeigt wie man mit der Arduino IDE Programme für den ESP8266 schreiben kann, die die HTML-Seiten aufbauen, an den WEB-Partner senden und die empfangenen Benutzereingaben auswerten.

HTML-Editoren können hier nicht eingesetzt werden, Die HTML-Seiten müssen Element für Element selbst definiert werden.

Hier das erste Beispiel. Es sieht so aus :
 HTML_Kochbuch_Bild1

In diesem Beispielen wird nichts gesteuert. Es werden nur die Klicks auf „OK“ und „nicht OK“ gezählt. Die Besonderheit : Es wird auch ein kleines Bild dargestellt.

Das dazugehörenden Beispielprogramm mutet auf den ersten Blick ein wenig „oversized“ an, weil es viele Unterroutinen enthält, die teilweise hier nicht aufgerufen werden.

Diese Routinen werden jedoch für andere Steuerelemente benötigt. Das Programm eignet sich in der vorliegenden Form als Kopiervorlage. Es enthält die wichtigsten Unterroutinen und kann auf einfache Weise ausgebaut werden.

Coding
//---------------------------------------------------------------------
//ESP866 HTML Demo 01
//---------------------------------------------------------------------
// Author  : Hubert Baumgarten
//---------------------------------------------------------------------
#include <ESP8266WiFi.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

#include "bild1.c"

#define BGTDEBUG 1

//---------------------------------------------------------------------
// WiFi

byte my_WiFi_Mode = 0;  // WIFI_STA = 1 = Workstation  WIFI_AP = 2  = Accesspoint

const char * ssid_sta     = "<your SSID>";
const char * password_sta = "<Your Password>";

const char * ssid_ap = "ESP_HTML_01";
const char * password_ap = "";    // alternativ :  = "12345678";

WiFiServer server(80);
WiFiClient client;

#define MAX_PACKAGE_SIZE 2048
char HTML_String[5000];
char HTTP_Header[150];

//---------------------------------------------------------------------
// Allgemeine Variablen

int ok_count = 0;
int notok_count = 0;
int Aufruf_Zaehler = 0;

#define ACTION_OK 1
#define ACTION_NOTOK 2
int action;

//---------------------------------------------------------------------
void setup() {
#ifdef BGTDEBUG
  Serial.begin(115200);

  for (int i = 10; i > 0; i--) {
    Serial.print("Warte ");
    Serial.print(i);
    Serial.println(" sec");
    delay(1000);
  }
  Serial.println("ESP_HTML_01");
#endif

  //---------------------------------------------------------------------
  // WiFi starten
  WiFi_Start_STA();
  if (my_WiFi_Mode == 0) WiFi_Start_AP();
}

//---------------------------------------------------------------------
void loop() {
  WiFI_Traffic();
  delay(10);
}

//---------------------------------------------------------------------
void WiFi_Start_STA() {
  unsigned long timeout;

  WiFi.mode(WIFI_STA);   //  Workstation

  WiFi.begin(ssid_sta, password_sta);
  timeout = millis() + 12000L;
  while (WiFi.status() != WL_CONNECTED && millis() < timeout) {
    delay(10);
  }

  if (WiFi.status() == WL_CONNECTED) {
    server.begin();
    my_WiFi_Mode = WIFI_STA;

#ifdef BGTDEBUG
    Serial.print("Connected IP - Address : ");
    for (int i = 0; i < 3; i++) {
      Serial.print( WiFi.localIP()[i]);
      Serial.print(".");
    }
    Serial.println(WiFi.localIP()[3]);
#endif
  } else {
    WiFi.mode(WIFI_OFF);
#ifdef BGTDEBUG
    Serial.println("WLAN-Connection failed");
#endif
  }
}

//---------------------------------------------------------------------
void WiFi_Start_AP() {
  WiFi.mode(WIFI_AP);   // Accesspoint
  WiFi.softAP(ssid_ap, password_ap);
  server.begin();
  IPAddress myIP = WiFi.softAPIP();
  my_WiFi_Mode = WIFI_AP;

#ifdef BGTDEBUG
  Serial.print("Accesspoint started - Name : ");
  Serial.print(ssid_ap);
  Serial.print( " IP address: ");
  Serial.println(myIP);
#endif
}
//---------------------------------------------------------------------
void WiFI_Traffic() {

  char my_char;
  int htmlPtr = 0;
  unsigned long my_timeout;

  // Check if a client has connected
  client = server.available();
  if (!client)  {
    return;
  }

  my_timeout = millis() + 250L;
  while (!client.available() && (millis() < my_timeout) ) delay(10);
  delay(10);
  if (millis() > my_timeout)  {
#ifdef BGTDEBUG
    Serial.println("Client connection timeout!");
#endif
    return;
  }
  //---------------------------------------------------------------------
  htmlPtr = 0;
  my_char = 0;
  while (client.available() && my_char != 'r') {
    my_char = client.read();
    HTML_String[htmlPtr++] = my_char;
  }
  client.flush();
  HTML_String[htmlPtr] = 0;
#ifdef BGTDEBUG
  Serial.println ("--------------------------------------------------------");
  Serial.print("Remote IP - Address : ");
  for (int i = 0; i < 3; i++) {
    Serial.print( client.remoteIP()[i]);
    Serial.print(".");
  }
  Serial.println(client.remoteIP()[3]);

  exhibit("Remote Port ", client.remotePort());
  exhibit ("Request : ", HTML_String);
#endif
  Aufruf_Zaehler++;
  if (Find_Start ("bild1.gif", HTML_String) > 0) {
    send_bin(Bild1, BILD1_LEN, "image/gif", "bild1.gif");
    return;
  }

  if (Find_Start ("/?", HTML_String) < 0 && Find_Start ("GET / HTTP", HTML_String) < 0 ) {
    send_not_found();
    return;
  }

  //---------------------------------------------------------------------
  // Benutzereingaben einlesen und verarbeiten
  //---------------------------------------------------------------------
  action = Pick_Parameter_Zahl("ACTION=", HTML_String);
  if ( action == ACTION_OK) ok_count++;
  if ( action == ACTION_NOTOK) notok_count++;

  //---------------------------------------------------------------------
  //Antwortseite aufbauen
  make_HTML01();

  //---------------------------------------------------------------------
  // Header aufbauen
  strcpy(HTTP_Header , "HTTP/1.1 200 OKrn");
  strcat(HTTP_Header, "Content-Length: ");
  strcati(HTTP_Header, strlen(HTML_String));
  strcat(HTTP_Header, "rn");
  strcat(HTTP_Header, "Content-Type: text/htmlrn");
  strcat(HTTP_Header, "Connection: closern");
  strcat(HTTP_Header, "rn");

#ifdef BGTDEBUG
  exhibit("Header : ", HTTP_Header);
  exhibit("Laenge Header : ", strlen(HTTP_Header));
  exhibit("Laenge HTML   : ", strlen(HTML_String));
#endif

  client.print(HTTP_Header);
  delay(20);

  send_HTML();

}

//---------------------------------------------------------------------
// HTML Seite 01 aufbauen
//---------------------------------------------------------------------
void make_HTML01() {

  strcpy( HTML_String, "<!DOCTYPE html>");
  strcat( HTML_String, "<html>");
  strcat( HTML_String, "<head>");
  strcat( HTML_String, "<title>HTML Demo</title>");
  strcat( HTML_String, "</head>");
  strcat( HTML_String, "<body bgcolor="#adcede">");
  strcat( HTML_String, "<font color="#000000" face="VERDANA,ARIAL,HELVETICA">");
  strcat( HTML_String, "<h1>HTML Demo   ");
  strcat( HTML_String, "<img src="bild1.gif" alt="mein Bild"></h1>");

  strcat( HTML_String, "<form>");

  strcat( HTML_String, "<button style= "width:120px" name="ACTION" value="");
  strcati(HTML_String, ACTION_OK);
  strcat( HTML_String, "">OK</button>");

  strcat( HTML_String, "   ");

  strcat( HTML_String, "<button style= "width:120px" name="ACTION" value="");
  strcati(HTML_String, ACTION_NOTOK);
  strcat( HTML_String, "">nicht OK</button>");

  strcat( HTML_String, "</form>");

  strcat( HTML_String, "<BR>");

  strcat( HTML_String, "<FONT SIZE=-1>");
  strcat( HTML_String, "Aufrufzähler : ");
  strcati(HTML_String, Aufruf_Zaehler);
  strcat( HTML_String, "  Anzahl OK : ");
  strcati(HTML_String, ok_count);
  strcat( HTML_String, "  Anzahl NOK : ");
  strcati(HTML_String, notok_count);
  strcat( HTML_String, "<br>");

  strcat( HTML_String, "</font>");
  strcat( HTML_String, "</font>");
  strcat( HTML_String, "</body>");
  strcat( HTML_String, "</html>");
}

//--------------------------------------------------------------------------
void send_bin(const unsigned char * bin , int bin_len, const char * type, const char * file) {
  int my_len, my_ptr, my_send;

  strcpy(HTTP_Header, "HTTP/1.1 200 OKrn");
  strcat(HTTP_Header, "Content-Length: ");
  strcati(HTTP_Header, bin_len);
  strcat(HTTP_Header, "rn");
  strcat(HTTP_Header, "Content-Type: ");
  strcat(HTTP_Header, type);
  strcat(HTTP_Header, "rn");
  strcat(HTTP_Header, "Content-Location: ");
  strcat(HTTP_Header, file);
  strcat(HTTP_Header, "rn");
  strcat(HTTP_Header, "Connection: closern");
  strcat(HTTP_Header, "rn");

#ifdef BGTDEBUG
  exhibit("Header : ", HTTP_Header);
  exhibit("Laenge Header : ", (unsigned long) strlen(HTTP_Header));
  exhibit("Laenge Bin    : ", bin_len);
#endif

  client.print(HTTP_Header);
  delay(20);

  //---------------------------------------------------------------------
  // in Portionen senden

  my_len = bin_len;
  my_ptr = 0;
  my_send = 0;

  while ((my_len - my_send) > 0) {
    my_send = my_ptr + MAX_PACKAGE_SIZE;
    if (my_send > my_len) {
      client.write(&bin[my_ptr], my_len - my_ptr);
      delay(20);
      my_send = my_len;
    } else {
      client.write(&bin[my_ptr], MAX_PACKAGE_SIZE);
      delay(20);
      my_ptr = my_send;
    }
  }
  client.stop();
}
//--------------------------------------------------------------------------
void send_not_found() {
#ifdef BGTDEBUG
  Serial.println("Sende Not Found");
#endif
  client.print("HTTP/1.1 404 Not Foundrnrn");
  delay(20);
  client.stop();
}

//--------------------------------------------------------------------------
void send_HTML() {
  char my_char;
int  my_len = strlen(HTML_String);
int  my_ptr = 0;
int  my_send = 0;

  //--------------------------------------------------------------------------
  // in Portionen senden
  while ((my_len - my_send) > 0) {
    my_send = my_ptr + MAX_PACKAGE_SIZE;
    if (my_send > my_len) {
      client.print(&HTML_String[my_ptr]);
      delay(20);
#ifdef BGTDEBUG
      Serial.println(&HTML_String[my_ptr]);
#endif
      my_send = my_len;
    } else {
      my_char = HTML_String[my_send];
      // Auf Anfang eines Tags positionieren
      while ( my_char != '<') my_char = HTML_String[--my_send];
      HTML_String[my_send] = 0;
      client.print(&HTML_String[my_ptr]);
      delay(20);
#ifdef BGTDEBUG
      Serial.println(&HTML_String[my_ptr]);
#endif
      HTML_String[my_send] =  my_char;
      my_ptr = my_send;
    }
  }
  client.stop();
}

//----------------------------------------------------------------------------------------------
void set_colgroup(int w1, int w2, int w3, int w4, int w5) {
  strcat( HTML_String, "<colgroup>");
  set_colgroup1(w1);
  set_colgroup1(w2);
  set_colgroup1(w3);
  set_colgroup1(w4);
  set_colgroup1(w5);
  strcat( HTML_String, "</colgroup>");
}

//------------------------------------------------------------------------------------------
void set_colgroup1(int ww) {
  if (ww == 0) return;
  strcat( HTML_String, "<col width="");
  strcati( HTML_String, ww);
  strcat( HTML_String, "">");
}

//---------------------------------------------------------------------
void strcati(char* tx, int i) {
  char tmp[8];

  itoa(i, tmp, 10);
  strcat (tx, tmp);
}

//---------------------------------------------------------------------
void strcati2(char* tx, int i) {
  char tmp[8];

  itoa(i, tmp, 10);
  if (strlen(tmp) < 2) strcat (tx, "0");
  strcat (tx, tmp);
}

//---------------------------------------------------------------------
int Pick_Parameter_Zahl(const char * par, char * str) {
  int myIdx = Find_End(par, str);
  
  if (myIdx >= 0) return  Pick_Dec(str, myIdx);
  else return -1;
}
//---------------------------------------------------------------------
int Find_End(const char * such, const char * str) {
  int tmp = Find_Start(such, str);
  if (tmp >= 0)tmp += strlen(such);
  return tmp;
}

//---------------------------------------------------------------------
int Find_Start(const char * such, const char * str) {
  int tmp = -1;
  int ww = strlen(str) - strlen(such);
  int ll = strlen(such);

  for (int i = 0; i <= ww && tmp == -1; i++) {
    if (strncmp(such, &str[i], ll) == 0) tmp = i;
  }
  return tmp;
}
//---------------------------------------------------------------------
int Pick_Dec(const char * tx, int idx ) {
  int tmp = 0;
  
  for (int p = idx; p < idx + 5 && (tx[p] >= '0' && tx[p] <= '9') ; p++) {
    tmp = 10 * tmp + tx[p] - '0';
  }
  return tmp;
}
//----------------------------------------------------------------------------
int Pick_N_Zahl(const char * tx, char separator, byte n) {

  int ll = strlen(tx);
  int tmp = -1;
  byte anz = 1;
  byte i = 0;
  while (i < ll && anz < n) {
    if (tx[i] == separator)anz++;
    i++;
  }
  if (i < ll) return Pick_Dec(tx, i);
  else return -1;
}

//---------------------------------------------------------------------
int Pick_Hex(const char * tx, int idx ) {
  int tmp = 0;

  for (int p = idx; p < idx + 5 && ( (tx[p] >= '0' && tx[p] <= '9') || (tx[p] >= 'A' && tx[p] <= 'F')) ; p++) {
    if (tx[p] <= '9')tmp = 16 * tmp + tx[p] - '0';
    else tmp = 16 * tmp + tx[p] - 55;
  }

  return tmp;
}

//---------------------------------------------------------------------
void Pick_Text(char * tx_ziel, char  * tx_quelle, int max_ziel) {

  int p_ziel = 0;
  int p_quelle = 0;
  int len_quelle = strlen(tx_quelle);

  while (p_ziel < max_ziel && p_quelle < len_quelle && tx_quelle[p_quelle] && tx_quelle[p_quelle] != ' ' && tx_quelle[p_quelle] !=  '&') {
    if (tx_quelle[p_quelle] == '%') {
      tx_ziel[p_ziel] = (HexChar_to_NumChar( tx_quelle[p_quelle + 1]) << 4) + HexChar_to_NumChar(tx_quelle[p_quelle + 2]);
      p_quelle += 2;
    } else if (tx_quelle[p_quelle] == '+') {
      tx_ziel[p_ziel] = ' ';
    }
    else {
      tx_ziel[p_ziel] = tx_quelle[p_quelle];
    }
    p_ziel++;
    p_quelle++;
  }

  tx_ziel[p_ziel] = 0;
}
//---------------------------------------------------------------------
char HexChar_to_NumChar( char c) {
  if (c >= '0' && c <= '9') return c - '0';
  if (c >= 'A' && c <= 'F') return c - 55;
  return 0;
}

#ifdef BGTDEBUG
//---------------------------------------------------------------------
void exhibit(const char * tx, int v) {
  Serial.print(tx);
  Serial.println(v);
}
//---------------------------------------------------------------------
void exhibit(const char * tx, unsigned int v) {
  Serial.print(tx);
  Serial.println(v);
}
//---------------------------------------------------------------------
void exhibit(const char * tx, unsigned long v) {
  Serial.print(tx);
  Serial.println(v);
}
//---------------------------------------------------------------------
void exhibit(const char * tx, const char * v) {
  Serial.print(tx);
  Serial.println(v);
}
#endif
Der Sketch zum download:

Erläuterungen zum Programmrahmen

Im Setup wird 10 Sekunden gewarten, damit man in Ruhe den Serial-Monitor starten kann und alle Meldungen mitbekommt

Dann wird zunächst versucht sich beim lokalen WLAN anzumelden. Wenn dies fehlschlägt wird ein eigener WEB-Server gestartet.

In der Loop wird die Routine WIFI-Traffic aufgerufen, um die über WALN empfangenen Benutzereingaben zu verarbeiten und die aktuelle HTML-Seite aufzubauen und zu senden.

Die Web-Browser rufen nicht nur die HTML-Seite und die darin enthaltenen Verweise auf Grafiken usw. ab. Sie fragen z.B. auch nach einem Favoriten-Ikon. Wenn man diese Requests ignoriert, weden sie alle paar Sekunden wiederholt. Daher wird in meinem Programm auf alles was nicht bekannt ist und bedient werden kann, mit „Not found“ geantwortet. Das merkt sich der Browser und weitere Anfragen unterbleiben.

Nach einem Abruf der HTML-Seite ohne jeden Parameter (Browsereingabe z.B. http://192.168.178.41/) kommt dieser Request an.:

GET / HTTP/1.1

Zwischen dem Slash und dem HTTP stehen ggf. Eingabe-Parameter oder der Name einer Datei die, abgerufen wird. In meinem Beispiel wird eine Bilddatei verlinkt :

HTML-Beispiel :

<img src=„bild.gif“ alt=„mein Bild“>

Die Bilddatei wird nach dem Senden der HTML-Seite automatisch angefordert :

GET /bild1.gif HTTP/1.1

Die Bilddatei wurde zu einem Char-Array umgesetzt und in einer eigenen Code-Datei Bild1.c gespeichert.:

#include "arduino.h"
#define BILD1_LEN  1515 
const unsigned char Bild1[] = {
 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x2B, 0x00, 0x28, 0x00, 0x70, 0x00, 0x00, 0x21, 0xF9, 0x04, 
 ....
 0x5B, 0xD1, 0xFD, 0xA2, 0xC8, 0x34, 0x5F, 0x01, 0x01, 0x00, 0x3B};

Diese binären Daten werden mit write übertragen. Write ist erforderlich, weil print beim ersten Auftreten eines 0x00 die Übertragung beenden würde.

Das Senden der HTML- und Binär-Daten geschieht in Portionen. Max. 2048 Bytes dürfen auf einmal geschickt werden, danach muß eine Pause von 20 mSec eingelegt werden.

Die Buttons haben die Attribute Name und Value. Beides wird beim Anklicken gesendet. So kann identifiziert werden, welcher Button angeklickt wurde.

HTML-Beispiel :

<button style=“width:200px“ name=„ACTION“ value=„1“>OK</button>

Wenn dieser Knopf angeklickt wird, kommt folgender Request :

GET /?ACTION=1 HTTP/1.1

Ich gebe die HTML-Seite auch auf dem Serialmonitor aus. Von dort kann sie mit Cut and Paste abgegriffen und z.B. zur Syntax-Prüfung in ein entsprechendes Programm übertragen werden.

Ein gute Seite zur Syntax-Prüfung im Netz ist z.B. :

http://www.dirtymarkup.com/

Das sieht so aus :

HTML_Kochbuch_Bild2

Wenn man seine Seite eingefügt hat, kann man mit Enter die Eingabe in mehrere Zeilen aufteilen. Das sieht dann so aus.

HTML_Kochbuch_Bild3

Hinweise auf Syntax-Fehler stehen ggf. am Anfang einer Zeile. Man kann den Fehler durch Aufteilen der Zeilen (Enter) einkreisen.

Es ist dringend angeraten, seine HTMLSeite zu überprüfen, um sicher zu gehen, dass sich kein Fehler eingeschlichen hat. Die verschiedenen WEB-Browser behandeln Fehler ganz unterschiedlich. Einige korrigieren bestimmte Fehler erfolgreich und alles sieht gut aus, einige nicht, und zeigen u.U. einen Scherbenhaufen (Browser in Voodoo-Mode) an.

Aufbau der HTML-Seite

Ich baue die HTML-Seite als Char-Array auf. Das benötigt weniger Speicher und läuft daher stabiler als der Aufbau mit Hilfe einer String-Variablen. Das Char-Array muß natürlich groß genug definiert sein. Es wird mit strcat zusammengesetzt. Eine eigenen Routine strcati dient zum Anfügen von Integerzahlen.

Die Benutzereingaben müssen in

<form> … </form>

eingebettet sein. Innerhalb dieses Blockes muß ein Button sein, der die Übertragung der Benutzereingaben dieses Blocks auslöst.

Auf einer HTML-Seite dürfen mehrere solcher Blöcke vorhanden sein, um z.B. die Eingaben zu strukurieren und unnötige Datenübertragungen zu vermeiden.

Wie oben bereits erwähnt, werden die Atrribute Name und Value beim Anklicken eines Button gesendet. Im Request kommen dann Name=Value an. Ich benutze bei allen Buttons immer den gleichen Namen „ACTION“ mit verschiedenen numerischen Werten in Value. Die Auswertung ist dann einfach. Mit der Routine Pick_Parameter_Zahl wird der Zahlenwert hinter „ACTION=“ ausgelesen. Der eingelesen Wert wird dann interpretiert. Das ist einfacher als verschiedenen Namen zu verwenden, die dann einzeln ausgelesen werden müssten,

Die verschieden Werte für ACTION habe ich als Kompilerkonstanten definiert z.B. ACTION_OK. Diese Konstanten verwende ich beim Aufbau der HTML-Seite und beim Interpretieren der Benutzereingaben. Das schafft Klarheit und Übersichtlichkeit und dokumentiert sich automatisch.

Dieses Anwendungsbeispiel

Hier werden nur die Anzahl Klicks auf „Ok“ und „Nicht OK“ gezählt. Das is denkbar einfach:

//  Variablen

int ok_count = 0;
int notok_count = 0;
int Aufruf_Zaehler = 0;

#define ACTION_OK 1
#define ACTION_NOTOK 2
int action;

// HTML Ausgabe

strcat( HTML_String, "<button style= "width:120px" name="ACTION" value="");
strcati(HTML_String, ACTION_OK);
strcat( HTML_String, "">OK</button>");

strcat( HTML_String, "   ");

strcat( HTML_String, "<button style= "width:120px" name="ACTION" value="");
strcati(HTML_String, ACTION_NOTOK);
strcat( HTML_String, "">nicht OK</button>");

strcat( HTML_String, "</form>");

strcat( HTML_String, "<BR>");

strcat( HTML_String, "<FONT SIZE=-1>");
strcat( HTML_String, "Aufrufzähler : ");
strcati(HTML_String, Aufruf_Zaehler);
strcat( HTML_String, "  Anzahl OK : ");
strcati(HTML_String, ok_count);
strcat( HTML_String, "  Anzahl NOK : ");
strcati(HTML_String, notok_count);

// Auswertung

action = Pick_Parameter_Zahl("ACTION=", HTML_String);
if ( action == ACTION_OK) ok_count++;
if ( action == ACTION_NOTOK) notok_count++;

2. Anwendungsbeispiel

Im folgenden Anwendungsbeispiel möchte ich zeigen, wie die wichtigsten Steuerelemente definiert und ausgewertet werden.

Textfelder

HTML-Beispiel :

<input type=„text“ style=“width:200px“ name=„VORNAME“ maxlength=„20“ value=„Bärbel“>

Das sieht so aus :
HTML_Kochbuch_Bild4

Der Request sieht so aus :

GET /?VORNAME=B%E4rbel&ACTION=4&NACHNAME=von+der+Waterkant HTTP/1.1

Die Felder sind durch ein & getrennt. Umlaute werden hexadezimal zurückgegeben. Dem Headezimalwert ist ein % vorangestellt. In diesem Beispiel wird der Buchstabe ä als %E4 codiert. Leerzeichen werden als + dargestellt. Die Routine Pick_Text bereitet die empfangenen Textfelder auf, dh. Hexcodes und + -Zeichen werden umgesetzt.

Auswertung :

  if ( action == ACTION_SET_NAME) {
    myIndex = Find_End("VORNAME=", HTML_String);
    if (myIndex >= 0) {
      Pick_Text(Vorname, &HTML_String[myIndex], 20);
    }
    myIndex = Find_End("NACHNAME=", HTML_String);
    if (myIndex >= 0) {
      Pick_Text(Nachname, &HTML_String[myIndex], 20);
    }
  }

Uhrzeit und Datum

HTML-Beispiel :

<input type=“time“ style=“width:100px“ name=“UHRZEIT“ value=„16:47:00“>
<input type=“date“ style=“width:100px“ name=“DATUM“ value=„2016-02-09“>

Das sieht so aus :

HTML_Kochbuch_Bild5

Der Request sieht so aus :

GET /?UHRZEIT=16%3A47%3A00&ACTION=3&DATUM=2016-02-09 HTTP/1.1

In der Uhrzeit sind die Trennzeichen : hexadezimal-codiert,. Das Datum wird in der angelsächsischen Reihenfolge Jahr, Monat Tag angegeben.

Auswertung :

if ( action == ACTION_SET_DATE_TIME) {
  myIndex = Find_End("UHRZEIT=", HTML_String);
  if (myIndex >= 0) {
    Pick_Text(tmp_string, &HTML_String[myIndex], 8);
    Uhrzeit_HH = Pick_N_Zahl(tmp_string, ':', 1);
    Uhrzeit_MM = Pick_N_Zahl(tmp_string, ':', 2);
    Uhrzeit_SS = Pick_N_Zahl(tmp_string, ':', 3);
  }
  myIndex = Find_End("DATUM=", HTML_String);
  if (myIndex >= 0) {
    Pick_Text(tmp_string, &HTML_String[myIndex], 10);
    Datum_JJJJ = Pick_N_Zahl(tmp_string, '-', 1);
    Datum_MM = Pick_N_Zahl(tmp_string, '-', 2);
    Datum_TT = Pick_N_Zahl(tmp_string, '-', 3);
  }
}


Auswahlfelder

HTML-Beispiel für Checkbox :

<input type=„checkbox“ name=„WOCHENTAG0“ id=„WT0“ value=„1“ checked>
<label for=„WT0“>Mo</label>

Man muß die Checkbox definieren und explizit ein Beschriftungslabel zuordnen. Das Attribut checked zeigt an, das diese Checkbox angekreuzt angezeigt werden soll. Im Request erhält man nur für angekreixte Checkboxen Name und Value. Über nicht angekreuzte Checkboxen wird geschwiegen. In meinem unternstehenden Beispiel habe ich alle 7 Wochentage aufgeführt und baue die angekreutenTage als Bit in ein 1-Byte-Datenfeld ein..

Auswertung :

Wochentage = 0;
for (int i = 0; i < 7; i++) {
  strcpy( tmp_string, "WOCHENTAG");
  strcati( tmp_string, i);
  strcat( tmp_string, "=");
  if (Pick_Parameter_Zahl(tmp_string, HTML_String) == 1)
    Wochentage |= 1 << i;
}

HTML-Beispiel für Radiobutton :

<input type=“radio“ name=„JAHRESZEIT“ id=“JZ0″ value=„0“ checked>
<label for=“JZ0″>Frühling</label>
<input type=“radio“ name=„JAHRESZEIT“ id=“JZ1″ value=„1“>
<label for=“JZ1″>Sommer</label>

Man muß auch hier zunächst den Radiobutton definieren und dann explizit ein Beschriftungslabel zuordnen.
Die Gruppierung der Gruppe geschieht über das Attribut Name, das bei allen Gruppenmitgliedern den gleichen Wert hat. Genau ein Radiobutton der Gruppe darf ein checked enthalten

Auswertung :

Jahreszeit = Pick_Parameter_Zahl("JAHRESZEIT=", HTML_String);

HTML-Beispiel für Combobox :

<select name=„WETTER“ style=“width:160px“>
<option selected value=„0“>Regen</option>
<option value=„1“>Wolken</option>
</select>

Zwischen select und endselect werden die Auswahloptionen aufgeführt. Die vorgewählte Option erhält das Attribut selected.

Auswertung :

Wetter = Pick_Parameter_Zahl("WETTER=", HTML_String);

Das sieht so aus :

HTML_Kochbuch_Bild6

Der Request für alles obige sieht so aus :

GET /?WOCHENTAG1=1&WOCHENTAG2=1&WOCHENTAG3=1&ACTION=5&JAHRESZEIT=2&WETTER=2 HTTP/1.1

Slider

HTML-Beispiel :

<input type=„range“ name=„VOLUME“ min=„0“ max=„30“ value=„15“>

Das sieht so aus :

HTML_Kochbuch_Bild7
Der Request sieht so aus :

GET /?VOLUME=15&ACTION=6 HTTP/1.1

Auswertung :

if ( action == ACTION_LIES_VOLUME) {
  Volume = Pick_Parameter_Zahl("VOLUME=", HTML_String);
}

Listing (ohne die oben bereits wiedergegebenen Unterroutinen)

Coding
//---------------------------------------------------------------------
//ESP866 HTML Demo 02
//---------------------------------------------------------------------
// Author  : Hubert Baumgarten
//---------------------------------------------------------------------
#include <ESP8266WiFi.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

#define BGTDEBUG 1

//---------------------------------------------------------------------
// WiFi

byte my_WiFi_Mode = 0;  // WIFI_STA = 1 = Workstation  WIFI_AP = 2  = Accesspoint

const char * ssid_sta     = "<Your SSID>";
const char * password_sta = "<Your Password>";

const char * ssid_ap      = "ESP_HTML_02";
const char * password_ap  = "";    // alternativ :  = "12345678";

WiFiServer server(80);
WiFiClient client;

#define MAX_PACKAGE_SIZE 2048
char HTML_String[5000];
char HTTP_Header[150];

//---------------------------------------------------------------------
// Allgemeine Variablen

int Aufruf_Zaehler = 0;

#define ACTION_OK 1
#define ACTION_NOTOK 2
#define ACTION_SET_DATE_TIME 3
#define ACTION_SET_NAME 4
#define ACTION_LIES_AUSWAHL 5
#define ACTION_LIES_VOLUME 6

int action;

// Vor- Nachname
char Vorname[20] = "Bärbel";
char Nachname[20] = "von der Waterkant";

// Uhrzeit Datum
byte Uhrzeit_HH = 16;
byte Uhrzeit_MM = 47;
byte Uhrzeit_SS = 0;
byte Datum_TT = 9;
byte Datum_MM = 2;
int Datum_JJJJ = 2016;

// checkboxen
char Wochentage_tab[7][3] = {"Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"};
byte Wochentage = 0;

// Radiobutton
char Jahreszeiten_tab[4][15] = {"Frühling", "Sommer", "Herbst", "Winter"};
byte Jahreszeit = 0;

// Combobox
char Wetter_tab[4][10] = {"Sonne", "Wolken", "Regen", "Schnee"};
byte Wetter;


// Slider
byte Volume = 15;

char tmp_string[20];
//---------------------------------------------------------------------
void setup() {
#ifdef BGTDEBUG
  Serial.begin(115200);

  for (int i = 10; i > 0; i--) {
    Serial.print("Warte ");
    Serial.print(i);
    Serial.println(" sec");
    delay(1000);
  }
  Serial.println("ESP_HTML_02");
#endif

  //---------------------------------------------------------------------
  // WiFi starten

  WiFi_Start_STA();
  if (my_WiFi_Mode == 0) WiFi_Start_AP();

}

//---------------------------------------------------------------------
void loop() {

  WiFI_Traffic();
  delay(10);
}

//---------------------------------------------------------------------
void WiFI_Traffic() {

  char my_char;
  int htmlPtr = 0;
  int myIdx;
  int myIndex;
  unsigned long my_timeout;

  // Check if a client has connected
  client = server.available();
  if (!client)  {
    return;
  }

  my_timeout = millis() + 250L;
  while (!client.available() && (millis() < my_timeout) ) delay(10);
  delay(10);
  if (millis() > my_timeout)  {
    return;
  }
  //---------------------------------------------------------------------
  htmlPtr = 0;
  my_char = 0;
  while (client.available() && my_char != 'r') {
    my_char = client.read();
    HTML_String[htmlPtr++] = my_char;
  }
  client.flush();
  HTML_String[htmlPtr] = 0;
#ifdef BGTDEBUG
  exhibit ("Request : ", HTML_String);
#endif

  Aufruf_Zaehler++;

  if (Find_Start ("/?", HTML_String) < 0 && Find_Start ("GET / HTTP", HTML_String) < 0 ) {
    send_not_found();
    return;
  }

  //---------------------------------------------------------------------
  // Benutzereingaben einlesen und verarbeiten
  //---------------------------------------------------------------------
  action = Pick_Parameter_Zahl("ACTION=", HTML_String);

  // Vor und Nachname
  if ( action == ACTION_SET_NAME) {

    myIndex = Find_End("VORNAME=", HTML_String);
    if (myIndex >= 0) {
      Pick_Text(Vorname, &HTML_String[myIndex], 20);
#ifdef BGTDEBUG
      exhibit ("Vorname  : ", Vorname);
#endif
    }

    myIndex = Find_End("NACHNAME=", HTML_String);
    if (myIndex >= 0) {
      Pick_Text(Nachname, &HTML_String[myIndex], 20);
#ifdef BGTDEBUG
      exhibit ("Nachname  : ", Nachname);
#endif
    }
  }
  // Uhrzeit und Datum
  if ( action == ACTION_SET_DATE_TIME) {
    // UHRZEIT=12%3A35%3A25
    myIndex = Find_End("UHRZEIT=", HTML_String);
    if (myIndex >= 0) {
      Pick_Text(tmp_string, &HTML_String[myIndex], 8);
      Uhrzeit_HH = Pick_N_Zahl(tmp_string, ':', 1);
      Uhrzeit_MM = Pick_N_Zahl(tmp_string, ':', 2);
      Uhrzeit_SS = Pick_N_Zahl(tmp_string, ':', 3);
#ifdef BGTDEBUG
      Serial.print("Neue Uhrzeit ");
      Serial.print(Uhrzeit_HH);
      Serial.print(":");
      Serial.print(Uhrzeit_MM);
      Serial.print(":");
      Serial.println(Uhrzeit_SS);
#endif
    }
    // DATUM=2015-12-31
    myIndex = Find_End("DATUM=", HTML_String);
    if (myIndex >= 0) {
      Pick_Text(tmp_string, &HTML_String[myIndex], 10);
      Datum_JJJJ = Pick_N_Zahl(tmp_string, '-', 1);
      Datum_MM = Pick_N_Zahl(tmp_string, '-', 2);
      Datum_TT = Pick_N_Zahl(tmp_string, '-', 3);
#ifdef BGTDEBUG
      Serial.print("Neues Datum ");
      Serial.print(Datum_TT);
      Serial.print(".");
      Serial.print(Datum_MM);
      Serial.print(".");
      Serial.println(Datum_JJJJ);
#endif
    }
  }


  if ( action == ACTION_LIES_AUSWAHL) {
    Wochentage = 0;
    for (int i = 0; i < 7; i++) {
      strcpy( tmp_string, "WOCHENTAG");
      strcati( tmp_string, i);
      strcat( tmp_string, "=");
      if (Pick_Parameter_Zahl(tmp_string, HTML_String) == 1)Wochentage |= 1 << i;
    }
    Jahreszeit = Pick_Parameter_Zahl("JAHRESZEIT=", HTML_String);
    Wetter = Pick_Parameter_Zahl("WETTER=", HTML_String);

  }

  if ( action == ACTION_LIES_VOLUME) {
    Volume = Pick_Parameter_Zahl("VOLUME=", HTML_String);
  }


  //---------------------------------------------------------------------
  //Antwortseite aufbauen

  make_HTML01();

  //---------------------------------------------------------------------
  // Header aufbauen
  strcpy(HTTP_Header , "HTTP/1.1 200 OKrn");
  strcat(HTTP_Header, "Content-Length: ");
  strcati(HTTP_Header, strlen(HTML_String));
  strcat(HTTP_Header, "rn");
  strcat(HTTP_Header, "Content-Type: text/htmlrn");
  strcat(HTTP_Header, "Connection: closern");
  strcat(HTTP_Header, "rn");

#ifdef BGTDEBUG
  exhibit("Header : ", HTTP_Header);
  exhibit("Laenge Header : ", strlen(HTTP_Header));
  exhibit("Laenge HTML   : ", strlen(HTML_String));
#endif

  client.print(HTTP_Header);
  delay(20);

  send_HTML();

}

//---------------------------------------------------------------------
// HTML Seite 01 aufbauen
//---------------------------------------------------------------------
void make_HTML01() {

  strcpy( HTML_String, "<!DOCTYPE html>");
  strcat( HTML_String, "<html>");
  strcat( HTML_String, "<head>");
  strcat( HTML_String, "<title>HTML Demo</title>");
  strcat( HTML_String, "</head>");
  strcat( HTML_String, "<body bgcolor="#adcede">");
  strcat( HTML_String, "<font color="#000000" face="VERDANA,ARIAL,HELVETICA">");
  strcat( HTML_String, "<h1>HTML Demo</h1>");


  //-----------------------------------------------------------------------------------------
  // Textfelder Vor- und Nachname
  strcat( HTML_String, "<h2>Textfelder</h2>");
  strcat( HTML_String, "<form>");
  strcat( HTML_String, "<table>");
  set_colgroup(150, 270, 150, 0, 0);

  strcat( HTML_String, "<tr>");
  strcat( HTML_String, "<td><b>Vorname</b></td>");
  strcat( HTML_String, "<td>");
  strcat( HTML_String, "<input type="text" style= "width:200px" name="VORNAME" maxlength="20" Value ="");
  strcat( HTML_String, Vorname);
  strcat( HTML_String, ""></td>");
  strcat( HTML_String, "<td><button style= "width:100px" name="ACTION" value="");
  strcati(HTML_String, ACTION_SET_NAME);
  strcat( HTML_String, "">Übernehmen</button></td>");
  strcat( HTML_String, "</tr>");

  strcat( HTML_String, "<tr>");
  strcat( HTML_String, "<td><b>Nachname</b></td>");
  strcat( HTML_String, "<td>");
  strcat( HTML_String, "<input type="text" style= "width:200px" name="NACHNAME" maxlength="20" Value ="");
  strcat( HTML_String, Nachname);
  strcat( HTML_String, ""></td>");
  strcat( HTML_String, "</tr>");

  strcat( HTML_String, "</table>");
  strcat( HTML_String, "</form>");
  strcat( HTML_String, "<br>");



  //-----------------------------------------------------------------------------------------
  // Uhrzeit + Datum
  strcat( HTML_String, "<h2>Uhrzeit und Datum</h2>");
  strcat( HTML_String, "<form>");
  strcat( HTML_String, "<table>");
  set_colgroup(150, 270, 150, 0, 0);

  strcat( HTML_String, "<tr>");
  strcat( HTML_String, "<td><b>Uhrzeit</b></td>");
  strcat( HTML_String, "<td><input type="time"   style= "width:100px" name="UHRZEIT" value="");
  strcati2( HTML_String, Uhrzeit_HH);
  strcat( HTML_String, ":");
  strcati2( HTML_String, Uhrzeit_MM);
  strcat( HTML_String, ":");
  strcati2( HTML_String, Uhrzeit_SS);

  strcat( HTML_String, ""></td>");
  strcat( HTML_String, "<td><button style= "width:100px" name="ACTION" value="");
  strcati(HTML_String, ACTION_SET_DATE_TIME);
  strcat( HTML_String, "">Übernehmen</button></td>");
  strcat( HTML_String, "</tr>");

  strcat( HTML_String, "<tr>");
  strcat( HTML_String, "<td><b>Datum</b></td>");
  strcat( HTML_String, "<td><input type="date"  style= "width:100px" name="DATUM" value="");
  strcati( HTML_String, Datum_JJJJ);
  strcat( HTML_String, "-");
  strcati2( HTML_String, Datum_MM);
  strcat( HTML_String, "-");
  strcati2( HTML_String, Datum_TT);
  strcat( HTML_String, ""></td></tr>");

  strcat( HTML_String, "</table>");
  strcat( HTML_String, "</form>");
  strcat( HTML_String, "<br>");

  //-----------------------------------------------------------------------------------------
  // Checkboxen
  strcat( HTML_String, "<h2>Checkbox, Radiobutton und Combobox</h2>");
  strcat( HTML_String, "<form>");
  strcat( HTML_String, "<table>");
  set_colgroup(150, 270, 150, 0, 0);

  strcat( HTML_String, "<tr>");
  strcat( HTML_String, "<td><b>Wochentage</b></td>");

  strcat( HTML_String, "<td>");
  for (int i = 0; i < 7; i++) {
    if (i == 5)strcat( HTML_String, "<br>");
    strcat( HTML_String, "<input type="checkbox" name="WOCHENTAG");
    strcati( HTML_String, i);
    strcat( HTML_String, "" id = "WT");
    strcati( HTML_String, i);
    strcat( HTML_String, "" value = "1" ");
    if (Wochentage & 1 << i) strcat( HTML_String, "checked ");
    strcat( HTML_String, "> ");
    strcat( HTML_String, "<label for ="WT");
    strcati( HTML_String, i);
    strcat( HTML_String, "">");
    strcat( HTML_String, Wochentage_tab[i]);
    strcat( HTML_String, "</label>");
  }
  strcat( HTML_String, "</td>");

  strcat( HTML_String, "<td><button style= "width:100px" name="ACTION" value="");
  strcati(HTML_String, ACTION_LIES_AUSWAHL);
  strcat( HTML_String, "">Übernehmen</button></td>");
  strcat( HTML_String, "</tr>");

  //-----------------------------------------------------------------------------------------
  // Radiobuttons

  for (int i = 0; i < 4; i++) {
    strcat( HTML_String, "<tr>");
    if (i == 0)  strcat( HTML_String, "<td><b>Jahreszeit</b></td>");
    else strcat( HTML_String, "<td> </td>");
    strcat( HTML_String, "<td><input type = "radio" name="JAHRESZEIT" id="JZ");
    strcati( HTML_String, i);
    strcat( HTML_String, "" value="");
    strcati( HTML_String, i);
    strcat( HTML_String, """);
    if (Jahreszeit == i)strcat( HTML_String, " CHECKED");
    strcat( HTML_String, "><label for="JZ");
    strcati( HTML_String, i);
    strcat( HTML_String, "">");
    strcat( HTML_String, Jahreszeiten_tab[i]);
    strcat( HTML_String, "</label></td></tr>");
  }

  //-----------------------------------------------------------------------------------------
  // Combobox
  strcat( HTML_String, "<tr><td><b>Wetter</b></td>");

  strcat( HTML_String, "<td>");
  strcat( HTML_String, "<select name = "WETTER" style= "width:160px">");
  for (int i = 0; i < 4; i++) {
    strcat( HTML_String, "<option ");
    if (Wetter == i)strcat( HTML_String, "selected ");
    strcat( HTML_String, "value = "");
    strcati( HTML_String, i);
    strcat(HTML_String, "">");
    strcat(HTML_String, Wetter_tab[i]);
    strcat(HTML_String, "</option>");
  }
  strcat( HTML_String, "</select>");
  strcat( HTML_String, "</td></tr>");

  strcat( HTML_String, "</table>");
  strcat( HTML_String, "</form>");
  strcat( HTML_String, "<br>");

  //-----------------------------------------------------------------------------------------
  // Slider
  strcat( HTML_String, "<h2>Slider</h2>");
  strcat( HTML_String, "<form>");
  strcat( HTML_String, "<table>");
  set_colgroup(150, 270, 150, 0, 0);

  strcat( HTML_String, "<tr><td><b>Lautstärke</b></td>");

  strcat( HTML_String, "<td>");
  strcat( HTML_String, "<input type="range" name="VOLUME" min="0" max="30" value = "");
  strcati(HTML_String, Volume);
  strcat( HTML_String, "">");
  strcat( HTML_String, "</td>");

  strcat( HTML_String, "<td><button style= "width:100px" name="ACTION" value="");
  strcati(HTML_String, ACTION_LIES_VOLUME);
  strcat( HTML_String, "">Übernehmen</button></td>");
  strcat( HTML_String, "</tr>");

  strcat( HTML_String, "</table>");
  strcat( HTML_String, "</form>");
  strcat( HTML_String, "<BR>");

  strcat( HTML_String, "<FONT SIZE=-1>");
  strcat( HTML_String, "Aufrufzähler : ");
  strcati(HTML_String, Aufruf_Zaehler);
  strcat( HTML_String, "</font>");
  strcat( HTML_String, "</font>");
  strcat( HTML_String, "</body>");
  strcat( HTML_String, "</html>");
}
Der Sketch zum download:

Weitere Details zu HTML findet man hier :

www.w3schools.com Englische Seite. Kurz und bündig.

wiki.selfhtml.org Deutsche Seite. Ausführliche Beispiele.

Das war’s. Viel Spaß beim Ausprobieren.

Hubert Baumgarten

*Info: In diesem Beitrag verweisen orangefarbende Links auf Affiliates.