Genuino bzw. Arduino unplugged

Die letzten Folgen über Kommunikation behandelten den Zugriff auf das Internet über das Ethernet-Shield. Neben systemnahen Sketches für Arduino Uno und Mega lag der Fokus auf anwendungsspezifischen Protokollen, insbesondere auf MQTT und CoAP. In der vorliegenden und in den anschließenden Folgen kommen WiFi-Lösungen zur Sprache.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 10 Min.
Von
  • Dr. Michael Stal
Inhaltsverzeichnis

Die letzten Folgen über Kommunikation behandelten den Zugriff auf das Internet über das Ethernet-Shield. Neben systemnahen Sketches für Arduino Uno und Mega lag der Fokus auf anwendungsspezifischen Protokollen, insbesondere auf MQTT und CoAP. In der vorliegenden und in den anschließenden Folgen kommen WiFi-Lösungen zur Sprache.

Für WiFi auf dem Arduino gibt es eine preiswerte Variante, die auf dem Chip ESP8266 basiert. Allerdings ist diese Variante bisweilen mit einigen Mühen verbunden. Das Thema werden wir in nachfolgenden Folgen noch genauer betrachten.

Das WiFi 101 Shield - gut aber auch teuer

(Bild: arduino.cc)

Zunächst möchte ich aber den Weg des geringsten Widerstands gehen und eines der WiFi-Shields verwenden. Wer das Original-Shield WiFi 101 von der Arduino-Seite betrachtet, dürfte wegen des deftigen Preises einen Schrecken bekommen. Alternativen wie das CC3000 Shield von AdaFruit oder das Seeedstudio WiFi Shield gehören zur selben Preisliga.

Das BlueFly-Shield von Watterott

(Bild: www.watterott.com)

Ich habe mich deshalb für ein preisgünstiges Shield entschieden, das zum 101 Shield kompatibel ist und lediglich mit knapp unter 24 Euro zu Buche schlägt – das BlueFly-Shield von Watterott. Einziger Haken: Die Headerpins sind selbst zu löten. Aber das bereitet nur wenig Mühe. Notfalls bitten Sie einfach einen Hobby-Elektroniker in der Nachbarschaft darum.

Wer es wagen möchte: Es gibt im Internet zahlreiche Lötanleitungen wie dieses sehr anschauliche YouTube-Video.

Um das WiFi101 Shield zu betreiben, besorgen Sie sich die Bibliothek WiFi101 von der Arduino GitHub-Seite. Laden Sie die Bibliothek herunter (.zip-Datei) und machen Sie sie der IDE wie üblich bekannt: Sketch | Include Library | Add .ZIP Library. Anschließend wählen Sie einen Beispiel-Sketch: File | Examples | WiFi101 | ConnectWithWPA. Wer aus verschiedenen Gründen das unsichere WEP verwendet, findet unter den Beispielen auch den Sketch ConnectWithWEP.

Im nachfolgend abgebildeten Code müssen Sie dem Arduino Zugangsdaten (SSID, Passwort) Ihres WLAN bekannt machen.

#include <SPI.h>
#include <WiFi101.h>

char ssid[] = "yourNetwork"; // your network SSID (name)
char pass[] = "secretPassword"; // your network password
int status = WL_IDLE_STATUS; // the Wifi radio's status

... und anschließend den Sketch starten. Nun können Sie (hoffentlich) im seriellen Monitor einen erfolgreichen Verbindungsaufbau beobachten. Bei mir zu Hause lautet die Ausgabe beispielsweise:

Attempting to connect to WPA SSID: StalWLAN
You're connected to the networkSSID: StalWLAN
SSID: CA:B2:95:D7:96:8
signal strength (RSSI):-65
Encryption Type:2

IP Address: 192.168.178.97
192.168.178.97
MAC address: F8:F0:5:F5:DB:AB
SSID: StalWLAN
BSSID: CA:B2:95:D7:96:8
signal strength (RSSI):-62
Encryption Type:2

Als kleine Übung wollen wir das Network Time Protocol (NTP) nutzen, das als eines der ältesten Internet-Protokolle gilt. Sein Zweck ist die Uhren-Synchronisation von Computern mittels paketbasierten UDP-Zugriffs auf einen NTP-Server. Davon existieren weltweit eine ganze Menge. Die zeitliche Abweichung liegt im Bereich weniger Millisekunden. Die Zeit, die wir zurückerhalten, ist die Coordinated Universal Time (UTC).

UDP-Kommunikation unterstützt die Bibliothek WiFi101 bereits. Es gibt auf arduino.cc das Beispielprogramm UdpNTPClient, das genau die gewünschten Funktionen liefert. Hier nur die wichtigsten Ausschnitte aus dem genannten Beispielsketch, den kein geringerer als Arduino-Mitbegründer Tom Igoe verfasst hat.

Zunächst bindet der Sketch die entsprechende Header-Datei der Bibliothek ein:

#include <WiFi101.h>

Der lokale Port der UDP-Kommunikation (eingehende Pakete) ist üblicherweise 2390.

unsigned int localPort = 2390;      

Der Server im Beispiel hat die URL time.nist.go:

IPAddress timeServer(129, 6, 15, 28); 

In Deutschland bietet sich zum Beispiel auch an: de.pool.ntp.org. Dessen IP-Adresse ist 212.112.228.242.

Um eingehende Pakete entgegennehmen zu können, ist die Kommunikation wie folgt zu initiieren.

WiFiUDP Udp; // Variablenvereinbarung
Udp.begin(localPort); // Kommunikation starten

Damit wir die Zeit erfahren, müssen wir ein Anfragepaket an den NTP-Server senden. Die Variable packetBuffer definiert einen Puffer mit 48 Bytes (NTP_PACKET_SIZE := 48). Das vom Arduino per WLAN versendete Anfragepaket analysiert der NTP-Server, ändert es an einigen Stellen und schickt es anschließend zurück. Den Inhalt des Pakets beschreibt der Standard RFC 958.

Wir versenden folgendes Paket per UDP:

memset(packetBuffer, 0, NTP_PACKET_SIZE);
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
Udp.beginPacket(timeServer, 123); //NTP Anfragen gehen an Port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();

Danach kommt die Antwort vom NTP-Server an.

if ( Udp.parsePacket() ) {
// Daten lesen und in Puffer speichern
Udp.read(packetBuffer, NTP_PACKET_SIZE);

Daraus muss der Sketch die Zeitinformation extrahieren. Die vier Bytes ab packetBuffer[40] enthalten den Zeitstempel im Big-Endian-Format.

Mittels highWord << 16 | lowWord berechnet der Sketch aus diesen vier Bytes die Zahl der Sekunden seit dem 1. Januar 1900. Daraus lassen sich Uhrzeit und Datum errechnen. Dazu aber mehr im Beispielsketch UdpNTPClient.

In den Folgen zur Kommunikation mithilfe des Ethernet-Shields kamen auch Anwendungsprotokolle wie MQTT und CoAP zur Sprache. Beide können wir auch mit wenig Arbeit im WLAN benutzen. Exemplarisch betrachtet dieser Beitrag MQTT. Wir verwenden erneut Mosquitto und MQTT.fx als Werkzeuge (Beitrag mit Beschreibung von mosquitto und MQTT.fx). Für die genauen Details schnuppern Sie kurz in den verlinkten Beitrag.

Mehr Infos

Hinweis in eigener Sache

Kürzlich ist mein Artikel zum Genuino MKR1000 auf heise Developer erschienen. Dieses Board ist speziell für IoT-Anwendungen konzipiert.

Wir verwenden erneut die Bibliothek PubSubClient und als Vorlage den Sketch, den wir auch schon bei der Ethernet-Kommunikation mittels MQTT eingesetzt hatten. Im Code wurden die Referenzen zum Ethernet-Shield durch Referenzen zur WiFI-Bibliothek ersetzt. Das funktioniert nur deshalb, weil PubSubClient lediglich voraussetzt, dass die Kommunikationsbibliothek dieselbe Schnittstelle implementiert. Wir haben es also mit dem Strategy Pattern zu tun. Vor dem Hochladen des Sketches starten Sie bitte den MQTT-Server (mosquitto). Im Sketch müssen Sie alle Adressen und Zugangsdaten entsprechend Ihrer eigenen Umgebung anpassen.

Zur Erinnerung: Der nachfolgende Arduino-Sketch meldet sich beim lokalen mosquitto-Broker an. In der Methode callback() erfolgt die Behandlung eingehender Nachrichten aus subTopic. Eigene Nachrichten publiziert der Sketch in das Topic pubTopic.

//////////////////////////////////////////////////////////
// Dieser Sketch für den Arduino MKR1000 oder ZERO
// nutzt PubSubClient plus WiFi101.
//
// Als MQTT-Broker ist mosquitto im Einsatz.
// Der Code hängt aber nicht von mosquitto ab.
//
// (c) Michael Stal, 2016
//
// Der Sketch meldet sich für Nachrichteneingang in
// Topic subTopic an und schreibt Nachrichten in pubTopic.
// Bei Eingang von Nachrichten in subTopic wird die
// Methode callback aufgerufen.
///////////////////////////////////////////////////////////



#include <SPI.h>
#include <WiFi101.h>
#include <PubSubClient.h>

// Diese Methode wird aufgerufen, sobald eine MQTT-Nachricht
// beim abonnierten Topic eintrifft:

void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Nachricht eingetroffen [");
Serial.print(topic); // von welchem topic
Serial.print("]: ");
for (int i=0;i<length;i++) { // Nachricht ausgeben
Serial.print((char)payload[i]);
}
Serial.println();
}

char ssid[] = "<ssid>"; // Ihre WLAN SSID
char pass[] = "<passwort>"; // Ihr WLAN Passwort
char pubTopic[] = "outTopic"; // Topic zum Publizieren
char subTopic[] = "inTopic"; // Topic zum Nachrichten empfangen

// Wifi Verbindungsobjekt:
WiFiClient wifiClient;
IPAddress server(192, 168, 178, 40); // IP Adresse MQTT-Broker
IPAddress ip; // eigene IP Adresse <- DHCP
PubSubClient client(wifiClient); // Dependency Injection

// Diese Methode stellt die Verbindung zum Server wieder her:
void reconnect() {
// Versuch, Verbindung wiederherzustellen
while (!client.connected()) {
Serial.print("Versuch des MQTT Verbindungsaufbaus mit ");
Serial.println(server);

//Verbindungsversuch:
if (client.connect("arduinoClient")) {
Serial.println("Erfolgreich verbunden!");

// Nun versendet der Arduino eine Nachricht in outTopic ...
client.publish(pubTopic,"Arduino nach Hause telefonieren");

// und meldet sich bei inTopic für eingehende Nachrichten an:
client.subscribe(subTopic);
} else { // Im Fehlerfall => Fehlermeldung und neuer Versuch
Serial.print("Fehler, rc=");
Serial.print(client.state()); // Fehlercode
Serial.println(" Nächster Versuch in 5 Sekunden");
// 5 Sekunden Pause vor dem nächsten Versuch
delay(5000);
}
}
}

void setup()
{
Serial.begin(57600); // Seriellen Monitor starten
// Broker erreichbar über ip-Adresse = server, port = 1883
// 1883: Port für unverschlüsselte Kommunikation

client.setServer(server, 1883); // Adresse des MQTT-Brokers
client.setCallback(callback); // Handler für eingehende Nachrichten
// WiFi-Verbindung aufbauen
WiFi.begin(ssid, pass);
// Eigene IP-Adresse merken
ip = WiFi.localIP();
Serial.print("Zugewiesene IP-Adresse lautet: ");
Serial.println(ip);
// Kleine Rast
delay(1500);
}

void loop()
{
// Solange probieren bis verbunden:
if (!client.connected()) {
reconnect();
}
client.loop();
}

Starten Sie anschließend das Werkzeug MQTT.fx. Auch das hatten wir schon früher besprochen (früherer Blog-Post). Selbst das damals verwendete JavaScript können wir aus der Schublade holen. Zur Erinnerung: MQTT-Unterstützung lässt sich über den Node Package Manager installieren:

npm install mqtt --save

Damit Sie nicht zu oft umblättern müssen, habe ich das Programmlisting hierher kopiert.

var mqtt    = require('mqtt');
var client = mqtt.connect('mqtt://localhost:1883');

// Verbindung zum Broker aufbauen:
client.on('connect', function () {

// Nachrichten aus outTopic abonnieren:

client.subscribe('outTopic');

// Nachrichten auf inTopic publizieren:
client.publish('inTopic', 'Hallo Arduino, hier ist JavaScript!');
});

// client.on enthält eine Ereignisbehandlung für eingehende Nachrichten


client.on('message', function (topic, message) {

// Eingegangene Nachricht auslesen:

console.log(message.toString());
client.end(); // Verbindung kappen
});

Der Umgang mit dem WiFi101-Shield gestaltet sich relativ problemlos. Nur bei der Initialisierung benötigt WiFi höheren Aufwand. Wenn Bibliotheken der Schnittstelle der Ethernet-Bibliothek folgen, lassen sich existierende Bibliotheken und Sketches häufig auch für WLAN-Kommunikation nutzen.

Einziger Hemmschuh sind die Kosten für WiFi-Boards, die teilweise dieselben Preisregionen erreichen wie ausgewachsene Raspberry Pi-Boards. Dass es auch günstiger geht, zeigt das BlueFly-Shield.

Ob es sogar zum Schnäppchenpreis funktioniert, wollen wir in den beiden kommenden Folgen untersuchen.

Mehr Infos

()