Kleiner Webserver
Ziele der Station
In dieser Station wollen wir lernen, wie wir eine Webseite aufrufen die auf der SD Karte des Arduinos gespeichert ist. Über diese Seite sollen dann einzelne Pins des Arduinos geschaltet werden.
Das Projekt ist aufwändiger als die Einstiegsprojekte; du solltest also schon ein bisschen Erfahrung aus anderen Projekten gesammelt haben bevor du mit dem Webserver beginnst. Die Themen Netzwerkzugriff und HTML sind so umfangreich, dass wir sie im Rahmen dieses Projekts nur anschneiden können. Weiterführende Informationen sind im Internet aber leicht zu finden!
Materialien
Materialien die nicht in der senseBox enthalten sind
- LAN Kabel
Grundlagen
Ethernet-Shield
In der senseBox ist ein Ethernetshield enthalten. Über dieses kann man den Arduino mit Hilfe des LAN-Kabels mit dem Netzwerk verbinden.
Da es meist sehr schwer ist, über das Internet auf ein Schulnetzwerk zu zugreifen, werden wir nur über das lokale Netzwerk auf den Arduino zugreifen.
Falls ihr dennoch den Arduino über das Internet erreichbar machen wollt und Zugang zu eurem Router habt, gibt es die Möglichkeit dies über Portforwarding umzusetzen. Hier findet ihr weitere Informationen dazu.
Webserver
Als Webserver (englisch server ‚Diener‘) bezeichnet man einen Computer mit Webserver-Software (in unserem Fall den Arduino), der Dokumente an Clients wie z.B. Webbrowser überträgt. Das Dokument ist bei uns der Inhalt von index.htm.
Aufbau
Aufgabe 1
Für den Server werden drei Bibliotheken benötigt:
#include <Ethernet.h>
#include <TextFinder.h>
#include <SD.h>
Achtung Es ist wichtig, dass ihr unsere Version der Ethernet-Bibliothek nutzt (siehe Downloads). Mit der Standardbibliothek vom Arduino funktioniert unser Ethernet-Shield nicht. Die Arduinoumgebung wird die Ethernet-Bibliothek immer 'aktualisieren' wollen; das dürft ihr nicht machen.
Anschließend müssen in zwei Variablen die MAC-Adresse und die IP-Adresse des Arduino-Servers fest legen. Zusätzlich muss der Pin 4 als SD-Karten Pin festgelegt werden. Außerdem definieren wir uns einFile
-Objekt webFile
in dem die HTML Seite abgelegt wird.
// MAC-Adresse des Ethernet-Shield
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
// Lokale IP zum aufrufen des Webservers
IPAddress ip(192, 168, 0, 42);
// Pin der SD-Karte
byte sdPin = 4;
File webFile;
Dem Server müssen wir jetzt noch ein Port zuweisen. Außerdem führen wir eine Variable ein, über die definiert wird ob wir uns im Testmodus befinden. In diesem Fall werden über den seriellen Port zusätzliche Statusinformationen ausgegeben, die das System allerdings verlangsamen.
// Server port
EthernetServer server(80);
boolean testmode = false;// Auf true setzen falls etwas nicht funktioniert.
Dann werden Informationen über die serielle Schnittstelle ausgegeben.
In der setup-Methode wird im ersten Schritt die mac-Adresse und die IP-Adresse an den Server übergeben. Anschließend wird überprüft ob ein Zugriff auf die SD Karte besteht und ob auf der Karte ein Datei mit dem Namen index.htm
existiert. Falls die Variable testmode
auf true
gesetzt wurde, werden über die serielle Schnittstelle Statusinformationen ausgegeben. Das kann dir bei der Suche von Fehlern helfen.
void setup() {
if(testmode) Serial.begin(9600);
Ethernet.begin(mac, ip); // Client starten
server.begin(); // Server starten
if(testmode) Serial.println("Initialisiere SD-Karte...");
if(!SD.begin(sdPin)) {
if(testmode)Serial.println("Initialisierung der SD-Karte fehlgeschlagen!");
return;
}
if(testmode) Serial.println("SD-Karte erfolgreich initialisiert.");
if(!SD.exists("index.htm")) {
if(testmode) Serial.println("Datei (index.htm) wurde nicht gefunden!");
return;
}
if(testmode) {
Serial.println("Datei (index.htm) wurde gefunden.");
Serial.println("Verbraucher schalten");
}
}
Die loop
-Methode ist in zwei Bereiche unterteilt. Im ersten Teil werden Aktionen auf der Website ausgewertet, im zweiten Teil wird die Website von der SD-Karte ausgelesen und an den Browser gesendet.
Auswertung von Anfragen
Wenn der client verfügbar ist, wartet der finder
auf eine Anfrage vom client. Bekommt er diese, sucht er das Schlüsselwort "pin" speichert er die nächsten beiden Zeichen in den Variablen typ
, pin
und val
. Je nach Typ wird dann eine Aktion ausgeführt.
Standardmäßig sind die Typen D, A und a definiert.
D = digitalen Pin schalten
A = analogen Pin schalten
a = analogen Pin auslesen
void loop() {
/**********************
* Anfragen auswerten *
**********************/
EthernetClient client = server.available(); // Auf Anfrage warten
if(client) {
TextFinder finder(client);
if(finder.find("GET")) { //erkennt Aktion auf der Website
while(finder.findUntil("pin", "\n\r")) {// Bis das Schlüsselwort "pin" erkannt wird
char typ = char(client.read());
int pin = int(finder.getValue());
int val = int(finder.getValue());
if(typ == 'D') {
pinMode(pin, OUTPUT);
digitalWrite(pin, val);
if(testmode)Serial.print(" - D"+String(pin));
}
else if(typ == 'A') {
analogWrite(pin, val);
if(testmode)Serial.print(" - A"+String(pin));
}
else if(typ == 'a') { // a -> Sensorwert auslesen und ausgeben
val = analogRead(pin);
if(testmode)Serial.print(" - a"+String(pin));
}
else {
if(testmode) Serial.print(" - Falscher Typ");
}
//Hier können neue Befehle definiert werden die aus dem Browser gestartet werden.
if(testmode) {
if(val==1) Serial.println(" ein");
else if(val==0) Serial.println(" aus");
else Serial.println(" "+ val);
}
}
}
Webformular anzeigen
Jede Zeile des HTML Dokuments wird eingelesen und an den Browser geschickt. Da der Speicherplatz für Variablen sehr begrenzt ist, wird die Zeichenkette (String) bei jedem Leerzeichen oder Zeilenumbruch geteilt. Anschließend wird der String nach bestimmten Schlüsselwörtern durchsucht. Diese kann man nach bedarf in der Methode filter
nach bedarf definieren. Diese Schlüsselwörter können dann durch Sensorwerte ersetzt werden.
/************************
* Webformular anzeigen *
************************/
boolean current_line_is_blank = true; // eine HTTP-Anfrage endet mit einer Leerzeile und einer neuen Zeile
String htmlline = "";
while (client.connected()) {
if (client.available()) { // Wenn Daten vom Server empfangen werden
char c = client.read(); // empfangene Zeichen einlesen
if (c == '\n' && current_line_is_blank) { // wenn neue Zeile und Leerzeile empfangen
// Standard HTTP Header senden
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
// Website von SD-Karte laden
webFile = SD.open("index.htm"); // Website laden
if (webFile) {
while(webFile.available()) {
char temp = char(webFile.read());
//Bei jedem Leerzeichen wird ein neuer String generiert
if((int(temp) != 10)&&(int(temp) != 32)&&(int(temp) != 13)){
htmlline = htmlline + char(temp);
}
else {
htmlline = filter(htmlline); //Ersetzt Schlüsselwörter zum Beispiel durch Sensorwerte
client.println(htmlline);
if(testmode) Serial.println(htmlline);
htmlline = "";
}
}
webFile.close();
}
break;
}
if (c == '\n') {
current_line_is_blank = true;
}
else if (c != '\r') {
current_line_is_blank = false;
}
}
}
delay(1);
client.stop();
}
}
//Ersetzt Schlüsselwörter zum Beispiel durch Sensorwerte
// Es können nach belieben neue Schlüsselwörter definerit und durch andern Text ersätzt werden
String filter(String htmlline){
htmlline.replace("sa0en",String(analogRead(A0)));
htmlline.replace("sa1en","No sensor");
htmlline.replace("sa2en","No sensor");
htmlline.replace("sa3en","No sensor");
htmlline.replace("sa4en","No sensor");
htmlline.replace("sa5en","No sensor");
return htmlline;
}
HTML Dokument
Unsere erste kleine Webseite sieht so aus:
Diesen Code musst du kopieren und in einer Textdatei abspeichern. Den Namen der Textdatei musst du anschließend in index.htm ändern. Das HTML Dokument muss anschließend auf der SD Karte gespeichert und diese in das Ethernetshield gesteckt werden.
<!DOCTYPE html>
<html>
<head>
<title>Arduino Server senseBox</title>
<style type="text/css">
h2 { margin-bottom:5px; }
table {width: 40%;} th { background-color: #666; color: #fff; } tr { background-color: #fffbf0; color: #000; } tr:nth-child(odd) { background-color: #e4ebf2 ; } </style>
</style>
</head>
<body>
<img src="sensebox_logo_neu.png" alt="sensebox" width="700" height="150"/>
<h1>Arduino Server senseBox</h1>
<!-- D for digitalWrite, A for analogWrite, d for digitalRead, a for analogRead -->
<table>
<tr> <td>Digitale Ausgänge</td> <td> Aktion </td><td>Analoge Ausgänge</td><td>Aktion</td></td></tr>
<tr> <td>Pin0:</td> <td> <a href="/?pinD0=1" target="ifr">Ein</a> <a href="/?pinD0=0" target="ifr">Aus</a> </td> <td> Pin A0:</td> <td><a href="/?pinA0=1" target="ifr">Ein</a> <a href="/?pinA0=0" target="ifr">Aus </a> <a href="/?pina0=2" target="ifr"> getValue </a></td></tr>
<tr> <td>Pin1:</td> <td> <a href="/?pinD1=1" target="ifr">Ein</a> <a href="/?pinD1=0" target="ifr">Aus</a> </td> <td> Pin A1:</td> <td> <a href="/?pinA1=1" target="ifr">Ein</a> <a href="/?pinA1=0" target="ifr">Aus</a> <a href="/?pina1=2" target="ifr"> getValue </a></td></tr>
<tr> <td>Pin2:</td> <td> <a href="/?pinD2=1" target="ifr">Ein</a> <a href="/?pinD2=0" target="ifr">Aus</a> </td> <td> Pin A2:</td> <td> <a href="/?pinA2=1" target="ifr">Ein</a> <a href="/?pinA2=0" target="ifr">Aus</a> <a href="/?pina2=2" target="ifr"> getValue </a></td></tr>
<tr> <td>Pin3:</td> <td> <a href="/?pinD3=1" target="ifr">Ein</a> <a href="/?pinD3=0" target="ifr">Aus</a> </td> <td> Pin A3:</td> <td> <a href="/?pinA3=1" target="ifr">Ein</a> <a href="/?pinA3=0" target="ifr">Aus</a> <a href="/?pina3=2" target="ifr"> getValue </a></td></tr>
<tr> <td>Pin4:</td> <td> <a href="/?pinD4=1" target="ifr">Ein</a> <a href="/?pinD4=0" target="ifr">Aus</a> </td> <td> Pin A4:</td> <td> <a href="/?pinA4=1" target="ifr">Ein</a> <a href="/?pinA4=0" target="ifr">Aus</a> <a href="/?pina4=2" target="ifr"> getValue </a></td></tr>
<tr> <td>Pin5:</td> <td> <a href="/?pinD5=1" target="ifr">Ein</a> <a href="/?pinD5=0" target="ifr">Aus</a> </td> <td> Pin A5:</td> <td> <a href="/?pinA5=1" target="ifr">Ein</a> <a href="/?pinA5=0" target="ifr">Aus</a> <a href="/?pina5=2" target="ifr"> getValue </a></td></tr>
<tr> <td>Pin6:</td> <td> <a href="/?pinD6=1" target="ifr">Ein</a> <a href="/?pinD6=0" target="ifr">Aus</a> </td> <td></td></tr>
<tr> <td>Pin7:</td> <td> <a href="/?pinD7=1" target="ifr">Ein</a> <a href="/?pinD7=0" target="ifr">Aus</a> </td> <td></td></tr>
<tr> <td>Pin8:</td> <td> <a href="/?pinD8=1" target="ifr">Ein</a> <a href="/?pinD8=0" target="ifr">Aus</a> </td> <td></td></tr>
<tr> <td>Pin9:</td> <td> <a href="/?pinD9=1" target="ifr">Ein</a> <a href="/?pinD9=0" target="ifr">Aus</a> </td> <td></td></tr>
<tr> <td>Pin10:</td> <td> <a href="/?pinD10=1" target="ifr">Ein</a> <a href="/?pinD10=0" target="ifr">Aus</a> </td> <td></td></tr>
<tr> <td>Pin11:</td> <td> <a href="/?pinD11=1" target="ifr">Ein</a> <a href="/?pinD11=0" target="ifr">Aus</a> </td> <td></td></tr>
<tr> <td>Pin12:</td> <td> <a href="/?pinD11=1" target="ifr">Ein</a> <a href="/?pinD12=0" target="ifr">Aus</a> </td> <td></td></tr>
<tr> <td>Pin13:</td> <td> <a href="/?pinD13=1" target="ifr">Ein</a> <a href="/?pinD13=0" target="ifr">Aus</a> </td> <td></td></tr>
</table>
<iframe name="ifr" style="display:none;" width="0" height="0"></iframe>
</body>
</html>