ESP8266 Arduino-Programm: setLED
Dieses Programm ist mein erster Versuch, das WLAN Modul ESP8266 über den Arduino Uno zu bändigen. Die Funktion des Programms erscheint recht simpel. Über Steuerbefehle lässt sich die L-Led (digital Pin13), die auf fast jedem Arduino Board vorhanden ist, Ein- und Ausschalten. Es gibt zwei Varianten. Die erste Variante erlaubt über den Browser direkt durch Eingabe einer URL die LED zu schalten. Die zweite Variante läuft über einen eigenen TCP-Client, wie ich ihn z.B. in dem Artikel über die ersten Schritte verwendet habe.
Verwendung
In dem Programm können über die Variablen
#define xSSID „Hier könnte Ihr Netzwerk stehen“
#define xPASSWORT “ Hier könnte Ihr Netzwerk stehen „
#define xPORT 80
passende Einstellungen zum Netzwerk, Passwort und Port des Servers vorgenommen werden.
Zum Aktiveren der LED über den Browser muss man die IP-Adresse gefolgt von Doppelpunkt dem Port und /Led1 eingeben.
Beispiel:
192.168.178.37:80/Led1
Zum Deaktivieren hängt man nichts oder beliebiges hinten an
192.168.178.37:80/Led0
192.168.178.37:80
192.168.178.37:80/Lalelu
Beim Senden über einen TCP-Client muss das Kommando
set 0
Oder
set 1
Lauten. Dass Leerzeichen nach set ist wichtig.
App: TCP-Client im Google PlayStore
Dies ist natürlich nur ein erster Test. Wenn das Modul irgendwo zum Einsatz kommt, muss sollte man eine praktischere Befehlsstruktur entwickeln
Das Programm
Das WLAN-Modul wird über die Software basierte serielle Schnittstelle angesprochen und hängt an den Pins 10 und 11. Ich musste die SoftwareSerial-Library etwas anpassen. Genauer gesagt habe ich den internen Buffer von 64 Bytes auf 256 Bytes erhöht. Dies war wichtig, da mir am Anfang zu viele Zeichen verloren gegangen sind. Evtl. erlaubt die aktuelle Version auch einen kleineren Buffer, ich habe dies noch nicht getestet. Damit es keine Verwechslungen gibt, ist die Library in dem Sketchordner mit dabei. Es handelt sich wie um die gleiche Library die auch mit der Arduino Software installiert wird allerdings ist an der Stelle
#define _SS_MAX_RX_BUFF 256 // RX buffer size auf
in der SoftwareSerial.h.-Datei die Größe angepasst worden.
Das Verwenden der Software-Seriellen hat hat zwei Vorteile. Zum einen kann das Modul den Programmupload nicht unerwartet stören. Zum anderen ist die Hardwareserielle noch frei fürs Debugging. Dies ist hier auch oft genutzt worden. Wenn man das Terminal nebenbei mitlaufen lässt, bekommt man einiges an Feedback. Man kann auch direkt AT-Befehle über das Terminal an das Modul verschicken.
Das Programm ist mit rund 311 Zeilen recht lang geworden. Deswegen werde ich den Quelltext nur Teilweise hier vorstellen. Der Quelltext lässt sich aber herunterladen und ist auch mit einigen, wenn auch sehr schlampig geschriebenen, Kommentaren versehen.
Ich habe versucht, möglichst viele Modul-Funktionen in eigene Routinen zu packen. Eine Liste dieser Funktionen mit Parametern:
- boolean ESP_Test() Testet die Bereitschaft des Moduls durch AT
- boolean ESP_Reset() Resetet das Modul
- void ESP_TotalStart() Resetet Modul, Verbindet WLAN, Startet Server
- boolean WLAN_Connect(char SSID[], char PASSWORT[] ) Verbindet WLAN
- boolean SERVER_Start(int Port) Startet Server
- int SERVER_Status() Server Status
- void SERVER_IPD() Behandelt reinkommende Befehle
Der Programmablauf ganz grob
- Startet WLAN- Verbindet mit angegeben Netzwerk
- Startet Server an Port 80
- Wartet auf Kommando eingeleitet durch +IPD
- Analyse des Kommandos in eigner Routine
Das Problem lag vor allem in der Auswertung unerwartet kommender Daten. Dies kann durch verschiedene Sachen hervorgerufen werden.
Ein besonders ungünstiger Fall wäre, dass das Modul ready meldet. Das würde bedeuten, dass sich das Modul resetet hat. Dies ist im Test bei mir vorgekommen, wenn auch nur selten. Um diese Fehlerquelle abzufangen wird die ESP_TotalStart in diesem Fall neu ausgeführt.
Ein andere Fall wäre eine Link oder Unlink Meldung. Das bedeutet, dass sich ein Client auf dem Server des Moduls an– bzw. abgemeldet hat. Dies erzeugt z.Z. nur eine Debugmeldung, wäre aber auch ein möglicher Ansatz für eine erweiterte Kommunikation.
Ein weiter Fall ist, dass Daten empfangen wurden. Empfangene Daten werden grundsätzlich in der Form
+IPD,0,376:DATEN
Empfangen. +IPD kennzeichnet also reinkommende Daten. Die erste Zahl, hier die Null, gibt an, von welchem der verbundenen Clients Daten gesendet wurden. Die zweite Zahl sagt aus, wie viele Zeichen gesendet wurden. Nach dem Doppelpunkt folgen schließlich die übermittelten Daten.
Besonderheiten im Programm
Variable NC:
In der Loop Routine werden Zeichen für Zeichen durchgereicht. Damit nicht jedes Zeichen auf den Anfang eines möglichen Kommandos analysiert werden muss, wird überprüft ob das vorherige Zeichen ein NewLine Zeichen war (Befehle fangen immer in neuer Zeile an). Ist dies der Fall wurde NC = true gesetzt und das Zeichen genauer betrachtet.
WLAN-Connect
In der Routine für die WLAN-Verbindung wird zunächst mittels AT+CWJAP? überprüft, ob das Modul bereits mit dem angegebenen Netzwerk verbunden ist. Dies kommt öfter vor da sich das Modul wohl nach einem Reset auch automatisch verbindet. Ein ERROR bedeutet keine Verbindung und ansonsten wird der Name des Verbunden Netzwerkes zurückgegeben und mit der vorgegeben SSID verglichen. Ist diese identisch wird die Routine vorzeitig verlassen.
Server-Status
Anhand des Server-Status wird überprüft, ob der Server bereits gestartet ist. Das kann sehr gut sein, das beim Neuaufspielen eines Programms zwar der Arduino-Controller resetet wird, das Modul aber nicht. Ich vermute das Status 4 und 5 auf Inaktivität hinweisen und dann führe ich die komplette Startroutine neu aus.
Loop-Routine
void loop() { while (ESPSerial.available()) { char inChar = ESPSerial.read(); if (NC == true) //Nur wenn NL das letze Zeichen war wird dieses Zeichen auf +,r, L oder U untersucht!! dadruch beschleunigt //Wenn hier false in die If-Afrage einteragen wird, gehjt alles in Debug durch- keine interpretation { if (inChar == '+' || inChar == 'r' || inChar == 'L' || inChar == 'U') //Diese 4 sind der beginn eines evtl interessanten Kommandos { switch (inChar) { case '+': { //debug("Befehl/Request?"); delay (1); //Delay scheont die Zuverlässigkeit von der Befehlserkennung zu erhhöhen char NextPeek = ESPSerial.peek(); if (NextPeek == 'C') { break; //z.B. durch CWLAP möglichst schneller austieg damit keine DAten aus Buffer verloren gehen } if (NextPeek == 'I') {//Vermutung IPD = Einkommender Befehl if (ESPSerial.findUntil("IPD", " ")) { //Bestätigen debug("IPD-Kommando"); SERVER_IPD(); //Unterfunktion zum analysieren des IPD-Kommandos inChar = 0; } } break; } case 'r': //ready vom Modul gesendet=? //debug("Ungeplanter Neustart??"); if (ESPSerial.findUntil("eady", "/n")) { debug("Ungeplanter ModulReset -> Neustart"); inChar = 0; ESP_TotalStart(); //Aufruf der Startroutine } break; case 'L': //debug("Link? "); //Linked: eine Verbindung wurde aufgebaut... Evtl für später zu gerbauchen -> senden an Clients if (ESPSerial.findUntil("ink", " ")) { debug("Linked!"); inChar = 0; } break; case 'U': //UnLinked: eine Verbindung wurde getrennt... Evtl für später zu gerbauchen -> senden an Clients //ALLERDINGS: scheint selten ausgeläst zu werden debug("Unlink?"); //Hier kommen Daten gelegentlich weg die mit U beginnen in neuer Zeile --- kommt eher bei Browser request vor.. sowieso abfangen in request befehl if (ESPSerial.findUntil("nlink", " ")) { debug("Unlinked"); inChar = 0; } break; } } } //New Line ermöglicht einkommenden Befehl // damit beschleunigt if (inChar == '\n') { NC = true; } else { NC = false; } //Debug-> alles lässt sich über die Konsole verfolgen Serial.write(inChar); } }
Routine: Analyse der Befehle
void SERVER_IPD() { //Beispiel +IPD,0,376:GET / HTTP/1.1 = Typischer Browser request int Client; //Client ID int NZeichen; //Anzahl der gesendeten Zeichen char buffer[5]; //Buffer eig nur für Müll char Kommando1[10]; //Buffer für das erste Kommando direkt nach dem : bis Leerzeichen char Kommando2[10]; //Buffer für zweites Kommando... noch nicht ausgereift //Löscht alles aus Kommando1 und 2- Scheint wichtig sonst bleibt müll...memset schnellste möglichkeit memset(Kommando1, 0, sizeof(Kommando1)); memset(Kommando2, 0, sizeof(Kommando2)); Client = ESPSerial.parseInt(); NZeichen = ESPSerial.parseInt(); ESPSerial.readBytesUntil(':', buffer, 5); ESPSerial.readBytesUntil(' ', Kommando1, 10); //hier konvention für Kommandolänge oder Ähnliches //Vorrausslich Request über Server if (strcmp (Kommando1, "GET") == 0) { ESPSerial.readBytesUntil(' ', Kommando2, 10); debug ("kommando2: " + String(Kommando2)); //Das aktivierende Kommando ist im browser IP:80/Led1 - Alles andere deaktivert die LED if (strcmp (Kommando2, "/Led1") == 0) { digitalWrite (LED, HIGH); debug("LED AN"); } else { digitalWrite (LED, LOW); debug("LED AUS"); } if ( ESPSerial.find("OK")) { debug ("OK Skipt"); //hier wird der ganze Quatsch vom Browser Request übersprungen... Dauet ne weile } } //Vorrausslich Request über TCP-Cient else if (strcmp (Kommando1, "set") == 0) { debug("set"); //kommando über TCP client: set 1 oder set 0 digitalWrite(13, ESPSerial.parseInt()); } //Ausgabe zu debug zwecken debug("von Client: " + String(Client) + " Zeichenanzahl: " + String(NZeichen) + " Kommando1: " + Kommando1); //+ " Kommando2: "+ Kommando2 }
ESP8266_SetLED1