Motivation

Die WiFiClient-Klasse des ESP8266 ist von Stream und damit von Print abgeleitet, ermöglicht also die Benutzung der von print(), println(), write() u.a. Leider ist die Implementierung so, dass jede Nutzung von print() zu der Übertragung eines separaten TCP-Datenpakets führt. Schlimmer noch, ja nach auszugebenden Datentyp kann es vorkommen, dass pro Zeichen eine Übertragung stattfindet. Die Anzahl der Datenpakete, die pro Sekunde mit dem ESP8266 übertragen werden können, ist jedoch begrenzt. Dies führt dazu, dass auf der Empfängerseite die Daten nur tröpfchenweise ankommen uns auf der Senderseite evtl. nicht vernachlässigbare Verzögerungen auftreten. Nicht zuletzt belastet man das WLAN unnötig.

UrsWiFiSerial ist von der Stream-Klasse abgeleitet und kapselt die WiFiClient-Klasse. Zur Performance-Steigerung (Reduzierung der Datenpakete) werden die Sendedaten gepuffert.

Dieses Projekt beschreibt zunächst die Bibliothek UrsWiFiSerial und danach das Visual-Basic-Programm, das einen TCP-Server implementiert und die Texte auf einem PC anzeigt, die mit UrsWiFiSerial ausgegeben wurden.

Dies ist die Version 2 der UrsWiFiSerial-Klasse ohne die Nutzung eines Template-Mechanismus.

In­halts­ver­zeich­nis

Bibliothek UrsWiFiSerial

Verwendung

Funktionsübersicht

Beispiel

Implementierung

Download

UrsWiFiSerial Server

Funktion

Bedienelemente

Konfiguration

Download


Bibliothek UrsWiFiSerial

UrsWiFiSerial kapselt die WiFiClient-Klasse. Die gepufferten Sendedaten steigern die Performance auf Grund der Reduzierung der zu übertragenden TCP-Pakete. Die Datenübertragung erfolgt, wenn

Verwendung

Der Constructor von UrsWiFiSerial legt die Größe des Sende-Puffers in Byte fest.

UrsWiFiSerial MySerial(64);

legt eine Instanz von UrsWiFiSerial mit dem Namen MySerial  und einer Puffergröße von 64 Byte an. Ohne die explizite Angaben (UrsUdpSerial MySerial;) einer Puffergröße wird der Standardwert 32 genommen.

MySerial.begin(remoteIP, remotePort, subscriberPort);

startet den Datenempfang über subscriberPort und legt fest, dass die Ausgabe an den IP-Endpoint (remoteIP, remotePort) erfolgen soll.

MySerial.println("Hello World");

stellt die angegebene Zeichenfolge in den Zeichenpuffer und überträgt sie auf Grund des Zeilenende-Zeichen wegen println().

MySerial.xmitOnLf = false;

stellt den Zeilenende-Mechanismus ab. true schalten ihn wieder an.

MySerial.xmit();

sogt für die Übertragung der Zeichen im Puffer.

MySerial.available();

und

MySerial.read();

dienen dem Datenempfang.

Hinweis: Besteht keine funktionierende TCP-Verbindung versendet WiFiClient keine Daten. Die Methoden zum Versenden von Daten bleiben jedoch nicht "hängen", sondern liefern meist zurück, dass 0 Bytes versandt wurden. Wegen der Pufferung ist diese Information nicht ganz so zuverlässig. Es wird zwar überprüft, ob zum Zeitpunkt der Speicherung eine Verbindung besteht, aber wenn z.B. sich der Verbindungsstatus zwischen der Speicherung im Puffer und dem eigentlichen Versandt geändert hat, waren die vorab gelieferten Informationen zu Anzahl der versandten Bytes falsch.

Funktionsübersicht

Funktion Beschreibung Anmerkung
UrsWiFiSerial (
        uint16_t bufferSize = 32)
Definiert eine Instanz von UrsWiFiSerial mit 32 Byte Puffer unter dem Namen MySerial.

Die optimale Größe des Puffers ist abhängig von der Struktur der zu übertragenden Daten. 32 Byte ist ein guter Kompromiss.

uint8_t begin (
             IPAddress ip,
             uint16_t    port)

Versucht eine Verbindung zum angegeben Server aufzubauen und startet den WiFi-Client.

  • ip: IP, mit der Daten ausgetauscht werden sollen.
  • port: zugehöriger Port.

Liefert 1, wenn erfolgreich, 0 bei Fehler.

 

 uint8_t begin (
             const char* host,
             uint16_t port)

Versucht eine Verbindung zum angegeben Server aufzubauen und startet den WiFi-Client.

  • host: Name des Host, mit der Daten ausgetauscht werden sollen.
  • port: zugehöriger Port.

Liefert 1, wenn erfolgreich, 0 bei Fehler.

 
 uint8_t begin (
             const cString host,
             uint16_t port)
wie oben  
void stop() Trennt die Verbindung zum Server. Gibt sämtliche Ressourcen frei, die während der TCP-Sitzung verwendet wurden. Erneuter Start durch Methode begin(...).
XmitOnLf Die boolesche Variable XmitOnLf legt fest, ob eine automatische Übertragung nach dem Senden eines Zeilenendezeichens '\n' erfolgen soll. Der Standardwert ist true.
bool connected(); Gibt an, ob der Client verbunden ist oder nicht. Ein Client gilt als verbunden, wenn die Verbindung geschlossen wurde, es aber noch ungelesene Daten gibt.
IPAddress remoteIP() Ruft die IP-Adresse der Remote-Verbindung ab. Wird an WiFiClient.remoteIP() weiter geleitet.
uint16_t remotePort() Ruft den Port der Remote-Verbindung ab. Wird an WiFiClient.remotePort() weiter geleitet.
size_t xmit (
         const uint8_t * = NULL,
         size_t size = 0)
Sendet noch im Zeichenpuffer vorhandene Zeichen und überträgt
anschließend den übergebenen Datenblock.
Der Datenblock wird nicht gepuffert, sondern direkt übertragen.
Die Angabe des Puffers ist optional. Wird er nicht angegeben, wird nur der Inhalt des Zeichenpuffers übertragen.
size_t NumberOfBytesToSend() Liefert die Anzahl Zeichen, die im Zeichenpuffer aktuell auf die Übertragung warten.  
     
 size_t write(uint8_t)
Schreibt einzelnes Byte in den Zeichenpuffer. Diese Methode wird von Print zur Ausgabe eines Zeichens genutzt.
size_t write(const uint8_t *, size_t) Schreibt einen Block in den Zeichenpuffer. Diese Methode wird von Print zur Ausgabe eines Zeichenblocks genutzt.
int available() Liefert die Anzahl der (im aktuellen Paket) verbleibenden Bytes. Überlädt Stream::available()
Pakete werden automatisch nachgeladen.
int read() Liest ein einzelnes Byte. Überlädt Stream::read()
Pakete werden automatisch nachgeladen.
int peek() Liest ein Byte aus der Datei, ohne zum nächsten zu wechseln. D.h., aufeinanderfolgende Aufrufe von peek () werden den gleichen Wert zurückgeben, wie der nächste Aufruf von read(). Überlädt Stream::peek()
Pakete werden automatisch nachgeladen.

Alle anderen Funktionen sind identisch mit denen der Stream-Klasse:

Beispiel

Das folgende (simple) Beispiel zeigt die Verwendung.

#include <ESP8266WiFi.h>
#include <UrsWiFiSerial.h> // <===== UrsWiFiSerial =====
// Fehlermeldungen erfolgen über die serielle Schnittstelle.
// Nutzdaten werden per MySerial über das WLAN übertragen.

// ...

String SSID = ".....";
String Password = ".....";
String RemoteServer = "...."; // URL (Servername) oder IP
uint16_t Port = 8080;

UrsWiFiSerial MySerial(64);

void setup()
{ Serial.begin(9600); 
  
  // Verbindung zum WLAN Serial.println("Connecting to WLAN " + SSID); 
  WiFi.begin(SSID.c_str(), Password.c_str());
  while (WiFi.waitForConnectResult() != WL_CONNECTED)
  { Serial.println(F("Connection Failed! Rebooting..."));
    delay(5000);
    ESP.restart();
  }

  Serial.print(F("Station IP address: "));
  Serial.println(WiFi.localIP());

  if(MySerial.begin(RemoteServer, Port)) // <===== UrsWiFiSerial =====
  { Serial.print(F("MySerial connected to server "));
    Serial.println(MySerial.remoteIP());
  }
  else
    Serial.println("MySerial connection failed");
    

  MySerial.print("Hello "); // <===== UrsWiFiSerial =====
  MySerial.println(RemoteServer); // Datenübertragung (XmitOnLf ist true)

  MySerial.print("This is "); // <===== UrsWiFiSerial =====
  MySerial.print(WiFi.hostname()); 
  MySerial.println(" speaking"); // Datenübertragung (XmitOnLf ist true)

  MySerial.XmitOnLf = false; // Automatik abschalten

  // ...
}

void loop()
{ for (uint8_t i = '0'; i = '9'; i++) // Ausgabe der Zeichen von 0 bis 9 jeweils in einer Zeile
    MySerial.println(i); // <===== UrsWiFiSerial =====
                         // Es erfolgt keine Datenübertragung.
                         // Nur insgesamt 30 Zeichen i + cr +lf, Puffergröße 64 Zeichen
  MySerial.xmit(); // Übertragung auslösen  <===== UrsWiFiSerial =====

  // ...
 
  delay(10000);
}

Implementierung

Stream implementiert bereits alle notwendigen Funktionen für die Ein- und Ausgabe diverser Datentypen. Deshalb ist es nur notwendig die Methoden zu überschreiben, die letztendlich für die Ausgabe per TCP zuständig sind. Die Klasse Print leitet letztendlich alle Ausgaben an zwei virtuelle Methoden weiter: virtual size_t Print::write(uint8_t) und virtual size_t write(const uint8_t *buffer, size_t size). Beide führen in der durch WiFiClient implementierten Version direkt zur Übertragung eines Datenpakets. UrsWiFiSerial überschreibt beide Methoden und zwar so, dass die übergebenen Zeichen zunächst zwischengepuffert werden.

Die weiteren zu Stream gehörenden Methoden werden nahezu direkt an die interne WiFiClient-Instanz weitergeleitet. Die

Hinzu kommt die Methode begin(). begin() übernimmt den Verbindungsaufbau und startet den WiFiClient.

Download

Das ZIP-Archiv für Bibliothek UrsWiFiSerial zum Download. Die entpackten Dateien ins Verzeichnis <user>\Documents\Arduino\libraries kopieren (siehe Installing Additional Arduino Libraries).

Das Archiv enthält die Bibliotheksdateien


UrsWiFiSerial Server

UrsWiFiSerialServer ist ein einfaches Visual Basic Programm, das dazu dient, Texte zu empfangen und anzuzeigen, die von einem ESP8266 mit der oben beschriebenen UrsWiFiSerial-Klasse versandt wurden.

Screenshot

Funktion

Nach dem Programmstart wird ein TCP-Server eingerichtet, der über die angezeigte IP und den angezeigten Port auf Verbindungsversuche eines Clients wartet. Verbindet sich ein Client mit dem Server werden alle übertragenen Daten in der TextBox angezeigt.

Eine Unterbrechung einer Verbindung ist nicht immer erkennbar. Deshalb wird davon ausgegangen, dass ein erneuter Verbindungsversuch ein Indiz für die Unterbrechung einer bestehenden Verbindung gewertet wird. Bei einem erneuten Verbindungsversuch wird deshalb die bestehende Verbindung getrennt und mit der neuen Verbindung weiter gearbeitet.

Bedienelemente

Konfiguration

Zur Einstellung der Port-Nr. gibt es drei Möglichkeiten:

Des Weiteren ist es notwendig, die Netzwerk-Suchmaske anzupassen. Es geht hier nur um die Anzeige der Server-IP im Fenster. Dies geschieht durch Anpassen der Datei "UrsWiFiSerialServer.exe.config". Sicherlich gibt es elegantere Möglichkeiten, aber ich habe nach einer schnellen Lösung gesucht.

Einem PC ist üblicherweise mehr als eine IP zugeordnet. Je nach Konfiguration des PCs und des Routers sind diese auch immer wieder unterschiedlich. Was meist gleich bleibt, sind die ersten Stellen der IP. Die letzten vergibt dann der für das Netz zuständige DNS-Server (meist eine Komponente des Routers). Das Programm sucht die IP-Adresse aus, die mit allen von Null verschiedenen Stellen des Eintrags "IpString" übereinstimmt. Im unteren Beispiel sind das die Adressen 192.168.178.0 bis 192.168.178.255. Beim Wechsel des Routers muss man diesen String ggf. neu einstellen.

Anpassung in Datei "UrsWiFiSerialServer.exe.config"

Anzupassende Einstellungen

8080 ist der voreigestellte Port

192.168.178.0 ist die Maske für die zu suchende IP-Adresse des Servers. Wird benötigt, um den WLAN-Adapter zu identifizieren. 0 ist Wildcard. Die Angabe 192.168.178.0 bewirkt, dass die IP-Adresse genommen wird, die mit 192.168.178 beginnt.

Firewall

Ggf. muss die Applikation und der Port auf einer installierten Firewall freigeschaltet werden. Die Windows-Firewall lässt sich übrigens so einstellen, dass sie bei einer unbekannten Verbindungsaufforderung um Genehmigung fragt. Man muss dann nur noch bestätigen. Für Windows 10: Systemsteuerung -> System Und Sicherheit -> Windows-Firewall -> Benachrichtigungseinstellungen ändern -> Benachrichtigen, wenn eine neue App von der Windows-Firewall blockiert wird.

Download

Download Visual Studio Projekt

Download Binary