RESTful mit CoAP
Die letzte Folge hat nachrichtenbasierte Kommunikation über das IoT-Protokoll MQTT adressiert. Als Alternative bietet sich CoAP als REST des kleinen Geräts an.
- Dr. Michael Stal
Die letzte Folge hat nachrichtenbasierte Kommunikation über das IoT-Protokoll MQTT adressiert. Als Alternative bietet sich CoAP als REST des kleinen Geräts an.
Das nachrichtenbasierte Protokoll MQTT funktioniert recht gut für IoT-Anwendungen, hat allerdings einen Haken. Es fühlt sich weniger nach Webprotokoll an, sondern erinnert an existierende Kommunikations-Middleware aus dem Client-Server-Umfeld. Daran ist zwar nichts verkehrt, aber irgendwie wirkt es in der Welt von HTTP und HTML ein wenig wie ein Fremdkörper. Mit den Webservices à la SOAP verhält es sich im Prinzip genauso. Sie funktionieren, aber die Alternative REST (Representational State Transfer) konnte relativ schnell den Siegeszug antreten, weil das World Wide Web das REST-Gen bereits in sich trägt. REST ist in erster Linie ein Architekturmuster.
Nebenbei bemerkt: Falls Sie sich schon immer gefragt haben, wer eigentlich der Schöpfer von REST ist: REST entstand im Jahre 2000 als Ergebnis der Doktorarbeit von Roy Fielding.
Was bisher geschah:
- RESTful mit CoAP
- Kommunikation ĂĽber MQTT
- Schritt fĂĽr Schritt
- Anwendungen mit Servo-Motoren
- Gut motorisiert mit Gleichstrommotoren
- Arduino für Fledermäuse
- Bewegungserkennung durch Infrarot-Strahlung
- Anschluss von LCD-Displays ĂĽber den IIC-Bus
- Lauschen mit Sensoren
- JavaScript an Arduino: Es werde Licht
- Anschluss vieler LEDs an wenige Ausgänge
- Do-it-Yourself-Projekt: iXduino – Arduino on a Breadboard
- Crashkurs "Elektronik fĂĽr IoT-Anwendungen" (Teil 3 von 3)
- Crashkurs "Elektronik fĂĽr IoT-Anwendungen" (Teil 2 von 3)
- Crashkurs "Elektronik fĂĽr IoT-Anwendungen" (Teil 1 von 3)
REST: Architekturprinzipien
REST gibt eine ganze Reihe von Randbedingungen vor. Im Rahmen von Architekturmustern wĂĽrde man von Forces sprechen.
- FĂĽr jede Ressource gibt es eine einheitliche Schnittstelle, u.a. mit den Verben/Methoden GET, PUT, POST, DELETE.
- REST verwendet Client-Server-Interaktion, wobei Client und Server über Schnittstellen interagieren, die sie von Implementierungsdetails des Kommunikationspartners unabhängig machen.
- Eine fundamentale Eigenschaft von REST ist seine Zustandslosigkeit. Verbindungen merken sich keine Vorgeschichte. Zustandsinformation muss der Client deshalb bei jedem Aufruf mitsenden. Das heiĂźt ĂĽbrigens nicht, dass auf dem Server keine Session-Information gespeichert werden darf. Allerdings sollte der Server ausschlieĂźlich mittels der vom Client empfangenen Argumente die abgespeicherte Information wieder finden.
- Caching dient dazu, mehrfach angefragte Information vorzuhalten, um die Performanz zu erhöhen.
- Eine Schichtenarchitektur vermeidet Abhängigkeiten einer Anwendung oder Schicht von weiter unterliegenden Schichten. Es besteht für die Anwendung daher nur eine Abhängigkeit zur nächsten südlichen Schicht.
- Ein optionales Prinzip ist das der aufrufgetriebenen zwischenzeitlichen Erzeugung von Code auf dem Server, um dessen Funktionalität dynamisch zu erweitern. Inzwischen ist dieser optionale Teil zumindest im Web die Regel.
Wer jetzt auf den Geschmack gekommen ist, könnte sich eines der zahlreichen REST-Tutorials verabreichen lassen wie das von Dr. Dobbs.
REST in a Nutshell
Wie aber funktioniert REST? Starten wir mir der guten Nachricht: Wer HTTP versteht, versteht auch REST. Es bildet immerhin die architektonische Basis fĂĽr das Web. Systeme, die dieser Architektur folgen, erhalten dementsprechend das Attribut RESTful. Im Gegensatz zu TCP oder UDP ist REST anwendungsorientiert, d.h., es versteckt die ĂĽblichen systemnahen Mechanismen vor der Anwendung, die sich dadurch nicht um Dinge wie Flusskontrolle, Fehlererkennung- und -behandlung, Abbildung typisierter Daten auf untypisierte Daten zu kĂĽmmern braucht.
Stark vereinfacht, aus 10.000 Metern Höhe, gilt:
- Im REST-Universum gibt es Komponenten, die Ressourcen verwalten (z.B. Hosts bzw. Webserver und die von ihnen verwalteten Webseiten bzw. Dokumente aus Browsersicht).
- Komponenten und ihre Resourcen besitzen eindeutige Ortskoordinaten repräsentiert als URIs (Uniform Resource Identifiers). Im Falle des Web sind URIs als URLs (Uniform Resource Locations) kodiert, etwa http://wohnung.erika.mustermann.de.
- Eine gewünschte Ressource der jeweiligen Komponente lässt sich relativ zur Komponente mittels URNs (Uniform Resource Names) adressieren, etwa durch /wohnzimmer/jalousie. Insgesamt ergibt sich also als Adresse der Ressource: http://wohnung.erika.mustermann.de/wohnzimmer/jalousie. Wir haben es im konkreten Beispiel mit der Wohnzimmer-Jalousie in Frau Mustermanns Wohnung zu tun, deren Zustand wir programmatisch durch GET-Aufrufe abfragen können.
- Apropos GET. Jede Ressource besitzt, wie bereits erwähnt, eine universelle Standardschnittstelle mit potenziellen "Methoden" bzw. Verben (GET, PUT, DELETE, POST).
- Kommunikation mit Ressourcen erfolgt fast immer über HTTP, wobei Clients zusätzliche Parameter übergeben und als Ergebnis verschiedene Arten von Medien zurückerhalten können.
RESTlos glĂĽcklich?
REST ist zwar verständlich und relativ leichtgewichtig, aber für kleine Mikrocontroller nicht leichtgewichtig genug. Für solche Systeme gibt es CoAP:
- Das Constrained Application Protocol umfasst zum einen eine ressourcenschonende Untermenge von REST für die Kommunikation eingebetteter Geräte.
- Zum anderen ist CoAP eine Erweiterung von REST, indem es die zusätzlichen Verben OBSERVE und DISCOVER einführt, um die Bedürfnisse von Sensorik zu befriedigen.
- Darüber hinaus verwendet CoAP UDP als unterliegendes Protokoll statt HTTP bzw. TCP wie REST. UDP liefert paketbasierte Kommunikation statt verbindungsorientierte, was wegen des nachrichtenorientierten Ansatzes von CoAP Sinn macht und die Performanz erhöht.
Wer sich detailliert für CoAP interessiert, sei auf die CoAP-Webseite verwiesen. Diese enthält auch eine Liste verfügbarer CoAP-Implementierungen für verschiedene Sprachen und verschiedene Umgebungen.
CoAP-Nachrichten
Eine CoAP-Nachricht besteht aus einem vier Bytes großen Header, gefolgt von diversen Daten variabler Länge und Position. Betrachten wir zunächst den Header:
- Die ersten 2 Bits definieren die Version (0-3), wobei momentan Version 1 die einzige zulässige Version darstellt.
- Die nächsten 2 Bits bestimmen den Nachrichtentyp: 0 steht für Confirmable, 1 für Non-confirmable, 2 für Acknowledgement, 3 für Reset.
- Die darauf folgenden 4 Bits geben die Länge eines variablen Token-Bereichs in Bytes an, wobei Längen größer als 8 momentan reserviert und damit nicht erlaubt sind. Tokens sind weiter unten erläutert.
- Danach kommen 8 Bit fĂĽr das Feld Code.
- Die drei MSBs (Most Sigificant Bits) definieren eine Klasse, die restlichen 5 Bits das Detail (klasse.detail). Beispielsweise kennzeichnet 0.00 eine leere Nachricht. Dabei bedeutet Klasse 0 eine Anfrage, 2 eine erfolgreiche Antwort, 4 eine FehlerrĂĽckmeldung des Client, 5 eine FehlerrĂĽckmeldung der Servers. Alle anderen Werte sind reserviert.
- Das Feld "detail" fungiert bei einem Aufruf als Wert der Aufrufmethode, bei einer erfolgreichen RĂĽckmeldung als Antwortcode. Im Standard RFC 7252 gibt es in Kapitel 12.1 eine AufschlĂĽsselung der Detailcodes in den sogenannten CoAP Code Registries.
- Die letzten 2 Bytes des Headers enthalten die Message ID in der vom Netzwerkprotokoll vorgegebenen Reihenfolge. Diese dient auf Systemebene dazu, um Duplikate aufzuspĂĽren und um Nachrichten der Typen Acknowledgement/Reset auf Nachrichten der Typen Confirmable/Non-confirmable in Beziehung zu setzen.
An den Header schließen sich die Bytes des bereits erwähnten Tokenfelds an, gefolgt von den Optionen. Das Tokenfeld definiert ein Bytefeld, um Nachrichten und Antworten miteinander korrelieren zu können.
Optionen wiederum bestehen aus einer Optionsnummer, der Länge des Optionswerts und dem eigentlichen Optionswert. Beispiele für Optionen sind zum Beispiel die URL-Adresse des Hosts, oder die Adresse der Ressource.
Danach folgen die Binärzahl 11111111 (0xFF) und eine eventuelle Nutzlast, die sich bis zum Ende des UDP-Datagrams erstreckt.
Achtung: Auch in den Optionen kann der Wert 0xFF auftauchen, weshalb ein einfaches Suchen nach dieser Signatur nicht genĂĽgt.
Kommunikationsabläufe in CoAP
Bei einem CoAP-basierten Nachrichtenaustausch ist zwischen Confirmable (zu bestätigende) und Non-Confirmable (nicht zu bestätigende) Nachrichten zu unterscheiden. Bei ersteren erwartet der Absender ein Acknowledgement des Senders, bei letzteren nicht.
Eine Optimierung ist das sogenannte Piggybacking. Der Sender verschickt eine Confirmable-Nachricht. Der Empfänger verpackt seine Antwort in der Acknowledgement-Nachricht, statt einen weiteren Nachrichtenaustausch zu initiieren.
In einem vollständigen Ablauf sendet der Sender eine Confirmable-Nachricht an den Empfänger, der das mit einer ACK-Nachricht quittiert. Zu einem späteren Zeitpunkt schickt der Empfänger seinerseits eine Confirmable-Antwort an den Sender, die dieser ebenfalls mit einer ACK-Nachricht quittiert.
Im letzten betrachteten Fall sendet der Sender eine Non-Confirmable-Nachricht an den Empfänger, der wiederum seine Antwort als Non-Confirmable-Antwort zurücksendet. Dadurch reduziert sich die Menge der Nachrichten, allerdings zum Preis einer Fire'n'Forget-Semantik. Bei nicht zu bestätigenden Nachrichten kann der Client nie sicher sein, ob die Nachricht auch angekommen ist.
Damit wären diese wesentlichen Kommunikationsszenerien von CoAP aufgezählt. Nicht dargestellt wurden OBSERVE (ein beobachtetes Objekt sendet kontinuierlich Nachrichten an einen Empfänger) und DISCOVER (ein Client sucht nach bestimmten Diensten).
- OBSERVE ist den Sensoren und dem daraus resultierenden Bedarf an ereignisbasierter Kommunikation geschuldet. Ein Temperatursensor kann also bei Temperaturänderungen oder zu bestimmten Zeitpunkten entsprechende Meldungen an den Client senden.
- DISCOVER dient beispielsweise dazu, für ein Gerät festzustellen, welche Sensoren beziehungsweise Aktoren es an Bord hat.
Ob das im Sinne der REST-Philosophie ist, sei dahingestellt. FĂĽr das Internet der Dinge ist es jedenfalls nĂĽtzlich.
microcoap
Die gute Nachricht lautet: Es gibt eine Implementierung von CoAP für die Arduino-Welt. Die schlechte: Ein Genuino/Arduino Uno reicht nicht aus, sondern stößt beim Speicherbedarf schnell an seine Grenzen. Es sollte daher schon ein Mega, Due, Yun, MKR1000 oder Zero sein. Das gilt natürlich erst recht, wenn Benutzer das Betriebssystem RIOT-OS zusammen mit der Bibliothek libcoap installieren oder libcoap auf dem TinyOS. Der vorliegende Beitrag beschränkt sich allerdings auf microcoap.
microcoap ist keine Bibliothek, sondern die exemplarische Implementierung eines CoAP-basierten Servers auf einem Arduino-Board. Es versendet Antworten immer als Teil der Acknowledgement-Nachricht, d.h. mittels Piggybacking.
Auf GitHub finden Interessierte das entsprechende Projekt. Kopieren Sie die Dateien auf ein Unterverzeichnis Ihres Arduino-Ordners. Als Erstes sollten Sie main-posix.c löschen. Diese Datei ist nur dann relevant, wenn Sie mit Hilfe des beigefügten Makefiles eine POSIX-konforme C/C++-Anwendung generieren wollen, was wir nicht vorhaben. Ohne Löschen oder Verschieben der Datei kommt es bei der Übersetzung des Arduino-Sketches microcoap.ino zu Fehlermeldungen des Compilers.
Um den Quellcode für eigene Zwecke zu nutzen, müssen wir ihn erst einmal verstehen. Das fällt mit der weiter oben gewonnenen Kenntnis der REST-Grundlagen wesentlich leichter.
In der Datei coap.c findet sich die Methode int coap_parse(coap_packet_t *pkt, const uint8_t *buf, size_t buflen. Sie parst jedes empfangene und in einem Puffer abgelegte UDP-Paket, um daraus die Bestandteile der Nachricht zu analysieren.
coap_parseruft seinerseits diverse Analysemethoden auf, um die verschiedenen Bestandteile der Nachricht zu analysieren:
int coap_parseHeader(coap_header_t *hdr, const uint8_t *buf, size_t buflen)
coap_parseToken(coap_buffer_t *tokbuf, const coap_header_t *hdr, const uint8_t *buf, size_t buflen)
int coap_parseOption(coap_option_t *option, uint16_t *running_delta, const uint8_t **buf, size_t buflen)
int coap_parseOptionsAndPayload(coap_option_t *options, uint8_t *numOptions, coap_buffer_t *payload, const coap_header_t *hdr, const uint8_t *buf, size_t buflen),
Beispielsweise dient coap_parseHeader dazu, die Struktur coap_header_t
(mit dem Header der Nachricht) zu fĂĽllen, der seinerseits in coap.h definiert ist:
typedef struct
{ uint8_t ver; /* CoAP Versionsnummer */
uint8_t t; /* CoAP Nachrichtentyp */
uint8_t tkl; /* Tokenlänge: = Länge des Tokenfelds */
uint8_t code; /* CoAP Status Code. Kann sein Request (0.xx), Success reponse (2.xx),
* client error response (4.xx), or server error response (5.xx) * For possible values, see http://tools.ietf.org/html/rfc7252#section-12.1 */
uint8_t id[2]; /* Message ID */
} coap_header_t
Diverse Hilfsroutinen mit dem Präfix dump_ erlauben die Ausgabe der komplexen Datentypen zu Testzwecken.
Im Arduino-Sketch findet sich der folgende Aufruf zum Parsen der gesamten Nachricht:
if (0 != (rc = coap_parse(&pkt, packetbuf, sz))) { ... }
Nachdem das CoAP-Paket (pkt) erfolgreich analysiert werden konnte, tätigt der Sketch folgenden Methodenaufruf:
coap_handle_req(&scratch_buf, &pkt, &rsppkt)
=> pkt wurde von coap_parse erzeugt.
coap_rw_buffer_t& scratch_buf
=> enthält einen 32 Byte Puffer.
coap_header_t& rsppkt
=> enthält das von der Handlermethode zurückgelieferte Antwortpaket.
Blicken wir nun genauer in diese Methode:
int coap_handle_req(coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt)
{
const coap_option_t *opt;
uint8_t count;
int i;
const coap_endpoint_t *ep = endpoints; // Endpunkte des Hosts
while(NULL != ep->handler) // Iterieren ĂĽber alle Ereignishandler
{
if (ep->method != inpkt->hdr.code) // falls codes nicht gleich, weiter
goto next;
// Ist die Liste der Optionen nichtleer?
if (NULL != (opt = coap_findOptions(inpkt, COAP_OPTION_URI_PATH, &count)))
{
if (count != ep->path->count)
goto next;
for (i=0;i<count;i++)
{
if (opt[i].buf.len != strlen(ep->path->elems[i]))
goto next;
if (0 != memcmp(ep->path->elems[i], opt[i].buf.p, opt[i].buf.len))
goto next;
}
// match!
return ep->handler(scratch, inpkt, outpkt, inpkt->hdr.id[0], npkt->hdr.id[1]); // Aufruf Handler
}
next:
ep++; // nächste Schleifeniteration
}
// Jetzt lässt sich ein Antwortpaket schnüren
coap_make_response(scratch, outpkt, NULL, 0, inpkt->hdr.id[0],
inpkt->hdr.id[1], &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE);
return 0;
}
Die Variable endpoints in unserem Beispiel (siehe weiter unten) enthält eine Liste aller verfügbaren Endpunkte, die jeweils coap_endpoint_t als Typ haben:
typedef struct
{
coap_method_t method; /* (i.e. POST, PUT oder GET) */
coap_endpoint_func handler; /* Handler fĂĽr diese Art des Endpunkts (ruft coap_make_response() auf) */
const coap_endpoint_path_t *path; /* Resourcenpfad (z.B. foo/bar/) */
const char *core_attr; /* Das 'ct' Attribut, wie es RFC7252 in Kapitel
7.2.1. definiert: * Das Attribut gibt einen Hinweis auf das * Medienformat zurĂĽckgelieferter Inhalte
* (Kapitel 12.3. zeigt mögliche Werte) */
} coap_endpoint_t;
microcoap legt in endpoints.c folgende konkreten Endpunkte (= Ressourcen) fest.
const coap_endpoint_t endpoints[] = {
{COAP_METHOD_GET, handle_get_well_known_core, &path_well_known_core,
"ct=40"};
{COAP_METHOD_GET, handle_get_light, &path_light, "ct=0"}, // 0 => plain
// ascii
{COAP_METHOD_PUT, handle_put_light, &path_light, NULL},
{(coap_method_t)0, NULL, NULL, NULL} // markiert das Ende der Liste
}
Die Methoden mit Präfix handle_ sind die eigentlichen Ereignishandler. In den Pfadnamen (Präfix path_) stehen die relativen Zugriffspfade der jeweiligen Ressource, etwa /light oder /.well-known/core.
Der Pfad darf übrigens per Voreinstellung von microcoap maximal zwei Ebenen besitzen. Das lässt sich in der Header-Datei ändern. Nehmen wir zwei der weiter oben definierten Handler als Beispiel. Unter dem Pfad /light ist eine LED als Ressource definiert. Hat unser Host die Adresse www.<host>.de, so erfolgt der Zugriff auf die LED dementsprechend unter www.<host>.de/light. Da wir CoAP nutzen, lautete die URL coap://www.<host>.de/light
Die Variable light ist in endpoints.c vereinbart:
static char light = '0';
Mit einer GET-Methode lässt sich der Zustand der LED (ein/aus) ermitteln und das Ergebnis an den Client zurückmelden ...
static int handle_get_light(coap_rw_buffer_t *scratch,
const coap_packet_t *inpkt,
coap_packet_t *outpkt,
uint8_t id_hi,
uint8_t id_lo) {
// an dieser Stelle senden wir eine ACK-Nachricht zurĂĽck und verpacken
// in der Nachricht den aktuellen Status der LED &light
// message ID und Token sind mit denen des Aufrufs identisch
return coap_make_response(scratch, outpkt, (const uint8_t *)&light, 1,
id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_CONTENT,
COAP_CONTENTTYPE_TEXT_PLAIN);
}
Mit einer PUT-Methode lässt sich die LED ferngesteuert ein- oder ausschalten:
static int handle_put_light(coap_rw_buffer_t *scratch,
const coap_packet_t *inpkt,
coap_packet_t *outpkt,
uint8_t id_hi,
uint8_t id_lo) {
if (inpkt->payload.len == 0) // keine Nutzlast => Fehler
return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo,
&inpkt->tok, COAP_RSPCODE_BAD_REQUEST,
COAP_CONTENTTYPE_TEXT_PLAIN);
if (inpkt->payload.p[0] == '1') // Sender will LED einschalten
{
light = '1'; // Licht an und Variable light mit neuem Wert belegen
#ifdef ARDUINO
digitalWrite(led, HIGH);
#else
printf("ON\n");
#endif
// Token und Message ID sind die selben wie beim Aufruf
// Wir verpacken den Zustand der LED als Nutzlast der ACK-Nachricht
return coap_make_response(scratch, outpkt, (const uint8_t *)&light, 1,
id_hi, id_lo, &inpkt->tok,
COAP_RSPCODE_CHANGED,
COAP_CONTENTTYPE_TEXT_PLAIN);
}
else // Sender will LED ausschalten
{
light = '0'; // Licht aus
#ifdef ARDUINO
digitalWrite(led, LOW);
#else
printf("OFF\n");
#endif
// Token und Message ID sind die selben wie beim Aufruf
// Wir verpacken den Zustand der LED als Nutzlast der ACK-Nachricht
return coap_make_response(scratch, outpkt, (const uint8_t *)&light, 1,
id_hi, id_lo, &inpkt->tok,
COAP_RSPCODE_CHANGED,
COAP_CONTENTTYPE_TEXT_PLAIN);
}
}
Die Antwort an den Client wird jeweils durch Aufruf folgender Methode erzeugt:
coap_make_response(...)
int coap_make_response(coap_rw_buffer_t *scratch, coap_packet_t *pkt, const uint8_t *content, size_t content_len, uint8_t msgid_hi, uint8_t msgid_lo, const coap_buffer_t* tok, coap_responsecode_t rspcode, coap_content_type_t content_type){
pkt->hdr.ver = 0x01; // Einzige erlaube Version ist 1
pkt->hdr.t = COAP_TYPE_ACK; // Wir versenden einen Bestätigung (ACK)
pkt->hdr.tkl = 0; // Mit 0 initialisieren
pkt->hdr.code = rspcode; // Antwortcode
pkt->hdr.id[0] = msgid_hi; // Die beiden Bytes der Message ID
pkt->hdr.id[1] = msgid_lo;
pkt->numopts = 1; // Eine Option
// Token zur Korrelation zwischen Aufruf und Antwort notwendig
if (tok) { // Nichtleeres Token
pkt->hdr.tkl = tok->len; // Klasse und Länge überschreiben
pkt->tok = *tok;
}
// Sicher wegen 1 < MAXOPT
pkt->opts[0].num = COAP_OPTION_CONTENT_FORMAT;
pkt->opts[0].buf.p = scratch->p;
if (scratch->len < 2) return COAP_ERR_BUFFER_TOO_SMALL;
scratch->p[0] = ((uint16_t)content_type & 0xFF00) >> 8; // Ergebnistyp
scratch->p[1] = ((uint16_t)content_type & 0x00FF); // speichern
pkt->opts[0].buf.len = 2;
pkt->payload.p = content;
pkt->payload.len = content_len;
return 0;
}
Mit Hilfe des Beispielcodes in microcoap lassen sich eigene CoAP-Server mittels Anpassung an die eigenen BedĂĽrfnisse erstellen.
Testen des CoAP-Servers
Um den Arduino-basierten CoAP-Server zu testen, starten wir den Sketch microcoap.ino auf unserem Arduino-Board (zum Beispiel auf einem Mega oder Due). Im vorliegenden Szenario findet Ethernet-Anbindung Verwendung, Wir könnten stattdessen natürlich auch WiFi nutzen.
Zunächst soll der Browser als Testplattform dienen. Für den Test ist Mozilla Firefox notwendig. Zusätzlich brauchen wir das Firefox-Plug-in Copper, das Sie hier erhalten. Nachdem Sie das Plug-in installiert haben, können Sie mit CoAP experimentieren.
Sobald Sie es in Firefox aktiviert haben (einfach auf das orangefarbene Icon mit dem Copper-Logo klicken), können Sie dort die Zieladresse des CoAP-Servers eintragen. Die URL lautet coap://<IP-Adresse des Arduino>:5683/. Sie müssen nur noch die IP-Adresse Ihres Arduino-Ethernet-Boards einsetzen.
In dem durch den Screenshot illustrierten Beispiel bin ich zunächst zur genannten CoAP-URL navigiert, habe von den zwei verfügbaren Ressourcen (linkes Fenster oben) die Ressource light selektiert, als ausgehende Nachricht das Zeichen '1' eingetragen, und über Anklicken von PUT (Knopf ganz oben unterhalb der Browser-Menüleiste) eine PUT-Nachricht an die Resource versendet, die daraufhin die LED eingeschaltet hat.
CoAP-Hosts enthalten übrigens an der Resource-URN ./well-known/core häufig ein Beschreibung ihrer Ressourcen. Wenn Sie auf diesen Unterbaum in Copper klicken und einen GET-Aufrugf absetzen, erhalten Sie die entsprechende Information angezeigt.
CoAP und Java
Um den microchip-Server aufzurufen, können wir statt Copper auch eine Java-Anwendung nutzen. Hierfür bietet sich als Bibliothek Californium an, das im Rahmen von Eclipse verfügbar ist. Auf der Webseite erfahren Sie, wie Sie Californium in Eclipse installieren und benutzen.
Am besten Sie kopieren eines der Beispielsprojekte wie cf-helloworld-client und modifizieren dieses entsprechend:
// Tragen Sie als URI die IP Ihres eigenen Arduino-Boards ein.
try {
uri = new URI("coap://192.168.178.47:5683/light");
System.out.println(uri);
} catch (URISyntaxException e) {
System.err.println("Invalid URI: " + e.getMessage();
System.exit(-1);
}
CoapClient client = new CoapClient(uri);
System.out.println(uri);
// Erster Parameter = Payload, Zweiter Parameter = Typ des Payloads
CoapResponse response = client.put("1",0); // LED einschalten
if (response!=null) {
System.out.println(response.getCode());
System.out.println(response.getOptions());
System.out.println(response.getResponseText());
System.out.println("\nADVANCED\n");
// access advanced API with access to more details through .advanced()
System.out.println(Utils.prettyPrint(response));
} else {
System.out.println("No response received.");
}
Fazit
CoAP ist ein für eingebettete Systeme adaptiertes REST-Protokoll, das den RESTful-Architekturstil auch für IoT-Systeme eröffnet. Dabei stellt es allerdings mehr Ressourcen-Anforderungen als beispielsweise MQTT, weshalb auf schmalbrüstigeren Boards wie dem Arduino Uno eine Verwendung von CoAP nicht praktikabel erscheint. Wir kommen nochmals auf das Thema CoAP zurück, sobald es mal um die Arduino-Highend-Klasse wie bei den Vertretern der Yûn-Dynastie geht.
microcoap ist eine leichtgewichtige CoAP-Server-Implementierung für Arduino und POSIX, die der Entwickler aber noch für eigene Bedürfnisse anpassen muss, etwa um als Client gegenüber CoAP-Servern zu agieren. Eine Alternative ist die Installation des RIOT-OS-Betriebsystems auf Arduino Due oder Mega bei Nutzung von libcoap, eine andere die Installation von TinyOS auf dem Arduino ebenfalls mit zusätzlicher libcoap-Installation. ()