Über das AWS-CLI und die Amazon-Dienste Greengrass und IoT Core lassen sich die Sensordaten zahlreicher Raspberrys auslesen und zusammenführen. Den Weg zur gesunden IoT-Herde erklärt der Artikel.
Zu den typischen Dingen, die sich im viel gehypten Internet der Dinge (IoT) tatsächlich sinnvoll miteinander verbinden lassen, gehören Sensoren. Zu ihnen zählen unter anderem Temperaturmesser. Freunde des Raspberry Pi ziehen diese mit Vorliebe für ihre Fingerübungen heran, denn im Onlinehandel gibt es sie bereits für kleines Geld.
Das Auslesen der Daten gelingt mit ein paar Zeilen Python-Code über ein API an den GPIO-Ports. Doch dann stellt sich die Frage: Wohin mit den Daten? Wer wertet sie wo aus?
Hinzu kommt: Den Alarm generieren klappt auf einem einzelnen Pi mit ein paar zusätzlichen Zeilen im Python-Skript recht problemlos. Was aber, wenn mehrere (womöglich Hunderte) Raspberry Pis Daten sammeln, die Entwickler an einer Stelle zentral auswerten wollen?
Wertstoffhandel
Wer viele dieser Daten ermitteln und auswerten möchte, nutzt die Cloud als Datensammelstelle, weil er sie von überall erreicht. AWS bietet eine Plattform, um Daten ohne eigene VMs einzuliefern und auszuwerten, und stellt auch gleich einen passenden Client bereit. Der füttert die Cloudlösung und macht das zentrale Verwalten vieler Geräte einfach.
Einer der Standards zum Übermitteln der Daten ist das M2M-Protokoll Message Queuing Telemetry Transport, kurz MQTT ([1], [2]). Es verteilt Nachrichten und Befehle über einen Publish-Subscribe-Mechanismus. Dabei dürfen Teilnehmer Daten zu einem so genannten Thema senden, das etwa »Wohnzimmer/Lampe/Leuchtstärke« heißen kann. Alle, die dieses Thema abonnieren, empfangen diese Nachricht und reagieren bei Bedarf darauf. MQTT läuft auf TCP/IP-Netzwerken und lässt sich auch noch über eine TLS-Schicht absichern.
Die Softwarekomponente, die diesen Mechanismus überblickt, ist der MQTT-Server, auch Broker genannt. Sensoren messen und veröffentlichen ihre Ergebnisse an ein MQTT-Thema und die Abonnenten lesen und verwenden diese Daten.
MQTT und IoT in der Cloud
Stehen die Sensoren weit verteilt im Netz, bietet es sich an, den MQTT-Dienst in der Cloud zu betreiben. Statt einen eigenen MQTT-Broker auf einer eigenen VM zu starten, bieten die großen Public Clouds AWS [3], Azure [4] und GCE [5] MQTT als einen Service an. In der Regel tragen die Plattformen Namen wie IoT Core oder ähnlich. Diese Plattformen bilden das Sammeln und Abfragen der Daten in der Cloud ab.
Bei großen Installationen bleiben aber Probleme: Woher erfahren die Sensoren, was ihre Aufgabe ist, und wie lässt sich die Software verteilen? AWS, Azure und GCE bieten auch hierfür eine Lösung, indem sie einen MQTT-Client parat halten. Der sorgt nicht nur für die Kommunikation mit der jeweiligen Cloud, er empfängt auch Code mit Arbeitsanweisungen von dort. Auf diese Weise lässt sich zentral steuern, was unter Umständen Tausende von Sensoren tun sollen. Die Plattformen kümmern sich zugleich um die Security, indem sie die Verbindungen mit TLS sichern und das Setup möglichst einfach gestalten.
Wie so eine Lösung in der Praxis aussieht, zeigt der Artikel am Beispiel von AWS und einem Raspberry Pi mit Temperatursensor. Am Ende kommt noch Amazons Serverless-Computing-Variante Lambda [6] hinzu. Der Dienst soll nicht nur das Buzzword-Bingo vervollständigen, sondern auch ganz praktisch die eingelaufenen Daten auswerten und bei einer zu hohen Temperatur einen Alarm per SMS senden.
AWS und IoT
Die beiden IoT-Technologien von AWS heißen IoT Core [7] für den MQTT-Broker sowie IoT Greengrass [8] für den Client. Unter [9] stellt Amazon Dokumentation für den Einstieg bereit. Die erklärt dem Anwender, wie er über die Weboberfläche eine erste IoT-Sammelstelle – in der AWS-Syntax nennt sich das Core – einbinden und wie er im Anschluss die Daten abfragen kann. Auch die Installation des Clients auf einem Raspberry Pi ist Thema.
Weil die Dokumentation das Setup über die Webconsole sehr vereinfacht darstellt, bleibt die Multiplizierbarkeit leider etwas auf der Strecke. Der Artikel zeigt den Aufbau über das AWS CLI, um mehrere Sensoren einzubinden.
Auf der Kommandozeile ist eine ganze Reihe von Schritten nötig, um das Ziel zu erreichen. Einige hängen voneinander ab, wenn zum Beispiel die Ausgabe eines Kommandos einen Rückgabewert (eine ID oder einen Amazon Resource Name, kurz ARN) enthält, den das nächste Kommando benötigt. Das verknüpft die einzelnen Objekte miteinander.
Greengrass verwendet so genannte Greengrass Cores, um die Außenwelt mit dem IoT Core (dem MQTT-Broker) bei AWS zu verbinden. Diese Cores müssen sich bei der Zentrale anmelden, dafür verwenden sie Zertifikate. Diese generiert entweder AWS selbst (ein Weg, den dieses Beispiel beschreibt). Es ist aber auch möglich, ein eigenes CA-Zertifikat hochzuladen und so erzeugte Zertifikate zu benutzen. Abbildung 1 zeigt eine schematische Übersicht aus [9].

Abbildung 1: Amazon stellt den Greengrass Core als Runtime vor, die AWS-Lambda lokal ausführt und sich um Messaging, Device Shadows und Security kümmert.
Geräte verwenden den Core gemäß der Zeichnung, um über ihn im Sinne eines IoT-Gateway Daten in die Cloud zu senden. Beim Core selbst handelt es sich aber auch um ein Gerät beziehungsweise “Internet-Ding”.
Damit die folgenden Kommandos funktionieren, muss der Admin ein Betriebssystem mit installiertem AWS-Kommandozeilenclient nutzen. Unter Debian, Ubuntu, Red Hat und auch Gentoo heißt das Paket »awscli«. Damit das CLI funktioniert, muss im Home des Benutzers im Verzeichnis »~/.aws« eine Datei »credentials« mit den Einträgen »aws_access_key_id« und »aws_secret_access_key« liegen. Andernfalls kann sich die Boto3-Bibliothek, die der Client verwendet, nicht bei Amazon anmelden.
Die Zugangsdaten finden sich in den Einstellungen des eigenen AWS-Accounts. Außerdem empfiehlt es sich, zusätzlich eine Datei »config« im selben Verzeichnis anzulegen, welche die Region von AWS festlegt. Diese kann etwa so aussehen:
[default] region=eu-central-1
Dank der Einstellung führt Amazon alle Kommandos in der deutschen Region in Frankfurt aus. All diese Werte lassen sich alternativ auch über Umgebungsvariablen bestimmen. Der Befehl »aws help« sowie die Dokumentation bei Amazon verraten hierzu Näheres. Existieren die erwähnten Dateien noch nicht, hilft ein Aufruf von »aws configure«, um sie interaktiv anzulegen.
Auf die meisten der Befehle, die etwas in der AWS-Welt erzeugen sollen, antworten die Amazon-Server mit einem Json-Block, der das Objekt beschreibt. In der Praxis hat es sich bewährt, jedes Kommando um ein »| tee Objektname.json« zu ergänzen. So lassen sich vor allem die IDs und ARNs später noch reproduzieren.
Listing 1
Ein Ding des Internets erzeugen
01 # aws iot create-thing --thing-name LM_Core
02 {
03 "thingArn": "arn:aws:iot:eu-central-1:566776501337:thing/LM_Core",
04 "thingName": "LM_Core",
05 "thingId": "492fcf6a-6693-44a3-aa6e-39fa5a031d63"
06 }
Im ersten Arbeitsschritt erzeugt der Admin ein “Ding”, das den Greengrass Core repräsentiert. Dabei hilft ein IoT- und kein Greengrass-Unterkommando (Listing 1). Das so erzeugte Ding heißt »LM_Core« und benötigt ein Zertifikat. Auch dafür gibt es ein passendes Kommando, das Listing 2 zeigt.
Listing 2
Schlüsselfrage
01 aws iot create-keys-and-certificate \ 02 --set-as-active \ 03 --certificate-pem-outfile gg.cert.pem \ 04 --public-key-outfile gg.pubkey.pem \ 05 --private-key-outfile gg.privatekey.pem
Als Ergebnis liefert Amazon einen Json-Block zurück, der den ARN, die ID, den öffentlichen und privaten Schlüssel sowie das Zertifikat im PEM-Format enthält. Die weiteren Optionen sorgen dafür, dass Zertifikat und Schlüssel in eigenen Dateien landen, da der Entwickler sie sonst aus dem Json-Block pulen müsste.
Im nächsten Schritt verknüpft der Admin das Zertifikat mit dem Ding. Dies ist nötig, damit sich der Pi, der dieses Zertifikat später verwendet, am Core anmelden kann. Das Kommando benötigt den ARN des Zertifikats aus dem gerade generierten Zertifikat. Das Kommando dazu zeigt Listing 3, der Amazon Resource Name ist dabei der des Zertifikats.
Listing 3
Das Ding mit einem Zertifikat verkuppeln
01 aws iot attach-thing-principal \ 02 --thing-name LM_Core \ 03 --principal arn:aws:iot:eu-central-1:566776501337:cert/6c597228cf5e4da63ee7dc4364f5a282e431a1581014fbd3cd558b86878f4fdc
In AWS funktioniert nichts ohne Berechtigungen. Hier ist eine Policy nötig, die es den Benutzern des Zertifikats erlaubt, Daten an den IoT Core zu senden und aus ihm zu empfangen. Das Anlegen einer (sehr liberalen) Policy klappt über:
aws iot create-policy --policy-name LM_Policy --policy-document file://Pfad/zu/policy.json
Listing 4 zeigt dann den Inhalt der Datei »policy.json«. Auch hier gibt AWS einen Amazon Resource Name zurück, den der Admin mit dem Zertifikat verknüpfen muss. Dazu dient das Kommando aus Listing 5.
Listing 4
Json-Definition der Policy
01 {
02 "Version": "2012-10-17", "Statement": [
03 {
04 "Effect": "Allow", "Action": [
05 "iot:Publish", "iot:Subscribe", "iot:Connect", "iot:Receive"
06 ], "Resource": [
07 "*"
08 ]
09 }, {
10 "Effect": "Allow", "Action": [
11 "iot:GetThingShadow", "iot:UpdateThingShadow",
"iot:DeleteThingShadow"
12 ], "Resource": [
13 "*"
14 ]
15 }, {
16 "Effect": "Allow", "Action": [
17 "greengrass:*"
18 ], "Resource": [
19 "*"
20 ]
21 }
22 ]
23 }
Schließlich beginnt die Arbeit an den Greengrass-Komponenten. Die Greengrass-Gruppe öffnet dabei eine Klammer um eine Reihe von Komponenten:
- Beim Core handelt es sich um den MQTT-Client, der die Daten in die Cloud liefert. Er bringt ein Ding und ein Zertifikat mit.
- Eine Ressource. Darunter versteht Greengrass Daten oder Devices, auf die es zugreifen darf. Da der Raspberry Pi Zugriff auf »/dev/gpiomem« benötigt, muss der Admin diesen definieren und einbinden. Das sollte er tun, bevor er die Funktion einrichtet, da er die Ressource auch in die Funktion einhängen muss.
- Die Funktion, welche die Lambda-Funktion, die auf dem Core laufen soll, in die Gruppe einbindet. Es dürften auch mehrere sein, aber im Beispiel bleibt der Autor bei einer.
- Das Abonnement, das es den Cores erlaubt, Daten in die AWS-Cloud zu senden (optional mit Themenfilter).
Als letzten Arbeitsschritt erzeugt der Entwickler dann die Gruppe und gibt gleich alle zuvor angelegten Komponenten mit. Diese Reihenfolge erspart es, die Gruppe ständig erweitern zu müssen.
Listing 5
ARN und Zertifikat verknüpfen
01 aws iot attach-principal-policy \ 02 --policy-name LM_policy \ 03 --principal arn:aws:iot:eu-central-1:566776501337:cert/6c597228cf5e4da63ee7dc4364f5a282e431a1581014fbd3cd558b86878f4fdc
Definitionen
Ein wiederkehrendes Schema im Datenmodell von Greengrass ist das von Definitionen und Definitionsversionen. Kommandos, die etwas erzeugen, folgen immer der Form »create-XXX-definition«. Eine erste Version generiert der Admin dabei in Json-Form über die Option »–initial-version«. Ändert er etwas an der Definition, erzeugt er eine neue Version der Komponente XXX mit »create-XXX-definition-version«. Ist diese in einer anderen Komponente enthalten (und alle stecken in der »group-definition«), muss der Admin eine neue Version des umgebenden Objekts anlegen, und zwar bis zur obersten Hierarchiestufe.
Die Unterkomponente übergibt der Admin stets in Form ihrer Version (als ARN oder über ihre eindeutige ID). Fragt er mit »get-XXX-definition« eine bestimmte Komponente ab, enthält die Antwort auch ein Feld »LatestVersion« mit der ID der letzten angelegten Version. Der ARN dieser Version ergibt sich aus dem ARN der Definition gefolgt von »/versions/id«. Zum Beispiel dient
arn:aws:greengrass:eu-central-1:566776501337:/greengrass/definition/cores/37d2b2ac-b3ff-491d-b56d-071fbb9ade51
als ARN einer Core-Definition. Und wenn die letzte Version die ID »aae9c658-16fa-4c1b-91aa-ef54340552ac« besitzt, ergibt sich der gesamte ARN der letzten Version der Core-Definition als:
arn:aws:greengrass:eu-central-1:566776501337:/greengrass/definition/cores/37d2b2ac-b3ff-491d-b56d-071fbb9ade51/versions/aae9c658-16fa-4c1b-91aa-ef54340552ac
Im ersten Schritt legt der Entwickler eine Core-Definition an. Das folgende Kommando erzeugt diese mit einer Json-artigen Syntax, die den Core als Kombination aus dem ARN des Zertifikats und dem ARN des Dings übergibt. Dass die Zeile mit »Cores« ein »[« für ein Feld anzeigt, bedeutet, dass der Admin hier mehrere Cores mitgeben darf.
Soll später ein weiterer Pi mit eigenem Zertifikat ebenfalls Daten liefern, erzeugt der Admin ein neues Zertifikat und ein neues Ding. Dann wiederholt er die bereits getätigten Arbeitsschritte für das neue Gerät, um mit dem Kommando »aws greengrass create-core-definition-version« wie in Listing 6 eine neue Version der Definition zu erzeugen.
Listing 6
Neue Core-Definition
01 aws greengrass create-core-definition \
02 --name LM1_GG_Core \
03 --initial-version ,Cores=[{CertificateArn=arn:aws:iot:eu-central-1:566776501337:cert/6c597228cf5e4da63ee7dc4364f5a282e431a1581014fbd3cd558b86878f4fdc,Id=LM_GG_Core_ID,SyncShadow=False,ThingArn=arn:aws:iot:eu-central-1:566776501337:thing/LM_Core}]'
Zu den weiteren Argumenten eines Core zählen eine ID, die eindeutig sein muss, und der Boolesche Wert »SyncShadow«, der angibt, ob der Admin ein Shadow-Objekt anlegen will. Aber das ist nicht notwendig, daher steht der Wert auf »False«. Listing 7 zeigt den Rückgabewert, den AWS als Antwort auf das Kommando schickt.
Listing 7
Rückgabe der Core-Definition
01 {
02 "LatestVersionArn": "arn:aws:greengrass:eu-central-1:566776501337:/greengrass/definition/cores/37d2b2ac-b3ff-491d-b56d-071fbb9ade51/versions/aae9c658-16fa-4c1b-91aa-ef54340552ac",
03 "Name": "LM1_GG_Core",
04 "LastUpdatedTimestamp": "2018-11-23T23:00:11.144Z",
05 "LatestVersion": "aae9c658-16fa-4c1b-91aa-ef54340552ac",
06 "CreationTimestamp": "2018-11-23T23:00:11.144Z",
07 "Id": "37d2b2ac-b3ff-491d-b56d-071fbb9ade51",
08 "Arn": "arn:aws:greengrass:eu-central-1:566776501337:/greengrass/definition/cores/37d2b2ac-b3ff-491d-b56d-071fbb9ade51"
09 }
Da die Ressource in die Gruppe und in die Funktion gehört, legt der Admin im nächsten Schritt eine Ressource für den Zugriff auf »/dev/gpiomem« an. Das Kommando dazu heißt:
aws greengrass create-resource-definition --initial-version file://resource.json
Das Aufwändige hier ist die Resource Definition, die in Listing 8 steht.
Listing 8
Json-Code für die Ressource
01 {
02 "Resources": [
03 {
04 "ResourceDataContainer": {
05 "LocalDeviceResourceData": {
06 "SourcePath": "/dev/gpiomem",
07 "GroupOwnerSetting": {
08 "AutoAddGroupOwner":
true
09 }
10 }
11 },
12 "Id": "GPIOMEMID",
13 "Name": "GPIOMEM"
14 }
15 ]
16 }
Nun folgt die Definition der Funktion. Aus Sicht von Greengrass ist dies die Kombination eines Lambda ARN, der Zuordnung der gerade definierten Ressource und einer ID. Bevor es damit losgehen kann, muss der Entwickler aber zunächst bei AWS die Lambda-Funktion im Lambda-Bereich anlegen.
Lambada mit Lambda
Der Clou ist jetzt das Erzeugen und Verteilen des Codes, der auf dem Raspberry Pi (oder den Pis) laufen soll. Hierzu kommt Amazons Lambda-Plattform zum Einsatz. Eigentlich ist die dafür gedacht, Serverless Computing auf AWS-Rechnern zu betreiben. Der Anwender stellt dafür Code bei AWS ein. Verschiedene Mechanismen rufen diesen auf, und die Ausgabe lässt sich dann in die AWS-Welt weiterreichen.
Greengrass erlaubt es nun allerdings, Lambda-Funktionen auf einen Core herunterzuladen und dort laufen zu lassen. Um die Lambda-Funktion zu generieren, lädt der Entwickler zuerst das AWS IoT Greengrass Core SDK unter »Software« in der IoT-Console herunter.
Nach dem Auspacken findet sich im Ordner »aws_greengrass_core_sdk/examples/HelloWorld/greengrassHelloWorld« eine Beispielanwendung und daneben der Ordner »greengrasssdk« für Python. In diesem landet die hier vorgestellte Anwendung.
Listing 9 zeigt den Python-Code, der auf dem Greengrass-Core laufen soll. Der Code nimmt dabei an, dass ein DHT22-Sensor am GPIO-Pin 4 hängt. Bevor der Admin das Ganze online stellt, sollte er einen Funktionstest mit einer Version des Skripts vornehmen, die nicht die Greengrass-Funktionalität verwendet.
Listing 9
Lambda-Funktion für den Core
01 import greengrasssdk
02 import platform
03 from threading import Timer
04 from datetime import datetime
05 import time
06 import json
07 import Adafruit_DHT
08 sensor = Adafruit_DHT.DHT22
09 pin = 4
10
11 client = greengrasssdk.client('iot-data')
12 my_platform = platform.platform()
13
14 def greengrass_temp_run():
15
16 humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
17 d = datetime.now()
18 data = {
19 'temperature': temperature,
20 'humidity': humidity,
21 'localtimestamp': str(d)
22 }
23
24 client.publish(topic='iot/temperature', payload=json.dumps(data))
25 Timer(60, greengrass_temp_run).start()
26
27 greengrass_temp_run()
28
29 def function_handler(event, context):
30 return
Die Funktion setzt voraus, dass das Adafruit-DHT-Modul, das zum Beispiel [10] anbietet, heruntergeladen und installiert ist. Obwohl die Lambda-Funktionen gekapselt laufen, dürfen sie alle auf dem Pi installierten Module verwenden.
Die Zeile 11, die den Client initialisiert, wirkt bekannt. Sie erinnert an die Interaktion mit AWS über den Boto3-Client, nur dass hier eine abgespeckte Version zum Einsatz kommt.
Die Python-Datei und den »greengrasssdk«-Ordner verpackt der Entwickler nun in einer Zip-Datei, die er beim Erzeugen der Lambda-Funktion einsetzt. Bevor er die Lambda-Funktion generiert, legt er zunächst wieder eine Rolle fest, die es der Funktion erlaubt, den Lambda-Service zu nutzen. Das Kommando dazu sieht wie folgt aus:
aws iam create-role --role-name LM-Rolle --assume-role-policy-document file://lmrolle.json --path /service-role/
Die Json-Datei, in der die Rollendefinition steckt, zeigt Listing 10.
Listing 10
Json-Definition der Rolle
01 {
02 "Version": "2012-10-17",
03 "Statement": [
04 {
05 "Action": "sts:AssumeRole",
06 "Effect": "Allow",
07 "Principal": {
08 "Service": "lambda.amazonaws.com"
09 }
10 }
11 ]
12 }
Nun legt der Entwickler die Lambda-Funktion an und lädt sie hoch, Listing 11 demonstriert, wie es geht. Den zur Rolle passenden ARN liefert die Antwort, die AWS beim Erzeugen der Rolle zurückgibt. Der Handler in Zeile 5 von Listing 11 dient bei Lambda-Funktionen dazu, auf ein Trigger-Event (bei dem Daten übergeben werden) zu reagieren. Da die Funktion allerdings nicht Event-getrieben agiert, sondern die Temperatur misst und publiziert, eine Minute wartet und das dann wiederholt, darf der Body leer sein. Aber er muss existieren. Die Namenskonvention lautet:
Listing 11
Lambda-Funktion anlegen und hochladen
01 aws lambda create-function \ 02 --role arn:aws:iam::566776501337:role/service-role/LM-Rolle \ 03 --function-name LM-FunctionOnCore \ 04 --runtime python2.7 \ 05 --handler greengrassTemp.function_handler \ 06 --zip-file fileb://gg.zip
Name des Python-Skripts ohne die Endung.Name der Handler-Methode
Will der Admin über Greengrass Aktionen auf dem Core anstoßen, nachdem er eine MQTT-Queue abonniert hat, ruft er genau diese Methode auf.
Der AWS-Lambda-Dienst verwaltet den Code in Versionen. Anwender können nicht ohne diese arbeiten. Ändert sich etwas am Code, nutzt der Entwickler die Arbeitsschritte »Speichern« und wählt dann unter »Aktionen« in der Lambda-Console »Neue Version Bereitstellen« aus. Vorher funktioniert der Code nicht. Das Gleiche gilt für das CLI:
aws lambda publish-version --function-name LM-FunctionOnCore
Beim Zuordnen des Lambda zu einer Greengrass-Gruppe gibt der Entwickler entweder die Version oder ein so genanntes Alias an. Ändert sich später der Code, publiziert er eine neue Version. Er ändert dann das Alias so, dass es auf die neue Version zeigt. Dadurch verteilt Greengrass auch den neuen Code. Das erfahreneren Lambda-Entwicklern eventuell bekannte automatisch angelegte Alias »$Latest« funktioniert hier nicht.
Das Erzeugen des Alias mit Verweis auf die neue erste Version erledigt der Admin mit dem Kommando:
aws lambda create-alias --function-name LM-FunctionOnCore --name LMAlias --function-version 1
Wieder liefert AWS einen Json-Block zurück, wichtig für den nächsten Aufruf ist diesmal das Feld »AliasArn«.
Das Zuordnen von Lambdas zu einer Greengrass-Gruppe erfolgt bei AWS wiederum über eine Funktionsdefinition, von der es mehrere Versionen gibt. Beim Anlegen der Funktionsdefinition gibt der Admin eine Erstversion mit. In der referenziert er die gerade angelegte Lambda-Version. Der Aufruf sieht wie folgt aus:
aws greengrass create-function-definition --name LM-LambdaFunctionDefinition --initial-version file://function.json
Listing 12 zeigt den Inhalt der zugehörigen Json-Datei »function.json«.
Listing 12
function.json
01 {
02 "Functions": [
03 {
04 "FunctionArn": "arn:aws:lambda:eu-central-1:566776501337:function:LM-FunctionOnCore:LMAlias",
05 "FunctionConfiguration": {
06 "EncodingType": "json",
07 "Environment": {
08 "AccessSysfs": true,
09 "ResourceAccessPolicies": [
10 {
11 "Permission": "rw",
12 "ResourceId": "GPIOMEMID"
13 }
14 ]
15 },
16 "MemorySize": 16384,
17 "Pinned": true,
18 "Timeout": 300
19 },
20 "Id": "LMFunctionDefId"
21 }
22 ]
23 }
Wichtig ist hier, dass der Zugriff auf die Ressource Schreib- und Lesezugriff gewährt. Das Argument für den »FunctionArn« ist der ARN des erzeugten Alias. Ändert sich etwas an der Lambda-Funktion, publiziert der Admin innerhalb der Lambda-Funktion eine neue Version. Das Alias, auf das sich der ARN hier bezieht, setzt er nun auf diese neue Version.
Im Abo
Die fehlende Komponente der Gruppe ist jetzt noch das Abonnement. Das regelt, wer in der Greengrass-Gruppe wem überhaupt etwas senden darf. Im Beispiel soll die Lambda-Funktion auf dem Gerät an den IoT-Core senden. Da der Code als Thema »iot/temperature« verwendet (Listing 9, Zeile 24), ließe sich das noch als Filter einfügen. Im ersten Schritt wäre es aber eher eine Fehlerquelle, weil ein Tippfehler genügt, um schnell zu viel wegzufiltern. Der Entwickler erzeugt das Abonnement mit dem Kommando aus Listing 13.
Listing 13
Abonnement erzeugen
01 aws greengrass create-subscription-definition \
02 --name GG-Abo \
03 --initial-version 'Subscriptions=[{Id=GG-Abo-Id,Target=cloud,Subject=#,Source=arn:aws:lambda:eu-central-1:566776501337:function:LM-FunctionOnCore:LMAlias}]'
Schließlich fehlt noch das letzte Glied in der Kette, das alles zusammenfasst: die Greengrass-Gruppe. Auch hier greift wie bei der Core-Definition wieder das Konzept der Versionen. Die Version enthält die relevanten Daten, das Kommando »aws greengrass create-group« besitzt die Option »–initialVersion«. Das Kommando, das die bisher erzeugten Objekte zu einer Greengrass-Gruppe zusammenschmilzt, lautet:
aws greengrass create-group --name LM1TestGroup --initial-version file://groupsource.json
Auch hier wartet eine Json-Datei, die wie in Listing 14 nun alle »VersionsArn« aller angelegten Komponenten zusammenfasst.
Listing 14
groupsource.json
01 {
02 "CoreDefinitionVersionArn": "arn:aws:greengrass:eu-central-1:566776501337:/greengrass/definition/cores/11251cdf-416e-420b-a964-ecfac6122131/versions/a98201e5-bd6e-44ff-b419-dcd04bb750e6",
03 "FunctionDefinitionVersionArn": "arn:aws:greengrass:eu-central-1:566776501337:/greengrass/definition/functions/420b19f8-5d44-4902-bb5d-87b9188917d5/versions/043383da-f9c1-4498-93bf-18e7dbd5036a",
04 "ResourceDefinitionVersionArn": "arn:aws:greengrass:eu-central-1:566776501337:/greengrass/definition/resources/7a0055d6-9678-4931-bee3-bfe800dda82e/versions/93ee7411-98a4-41b5-a060-db6a5553ad35",
05 "SubscriptionDefinitionVersionArn": "arn:aws:greengrass:eu-central-1:566776501337:/greengrass/definition/subscriptions/6a57b1b4-47b0-4b4b-84ed-d9808345b050/versions/c6a00b5a-cb6c-43b9-895a-64c01cc107ce"
06 }
Damit sind die vorbereitenden Arbeiten abgeschlossen. Ein Klick in die Webconsole ruft die angelegte Gruppe auf den Schirm, wie Abbildung 2 zeigt.
Pi als Greengrass Core
Nun geht es an den Pi als Core-Gerät. Offiziell ist die von AWS bereitgestellte Software für Raspbian Jessie bestimmt, lief im Test aber problemlos auch auf Stretch. Der Link zum Download der Software taucht unter anderem in der Weboberfläche auf, während der Admin eine Gruppe anlegt. Alternativ klickt er in der IoT-Console auf »Software« (unten links) und wählt dann »AWS IoT Greengrass Core Software« aus.
Im nächsten Dialogschritt bietet AWS die Software für verschiedene Architekturen an. Am Ende winkt eine »Tar.gz«-Datei in der aktuellen Version (zum Zeitpunkt dieses Artikels die 1.6.0).
Im Vorfeld ist es nötig, auf dem Pi einen Benutzer »ggc_user« und eine Gruppe »ggc_group« anzulegen, in der »ggc_user« Mitglied ist. Kommt später mal eine andere Programmiersprache als Python zum Einsatz (etwa Java oder Javascript via Node.js), muss der Admin natürlich auch diese zuerst installieren.
Das Auspacken der Software auf dem Pi erfolgt in das Verzeichnis »/greengrass« mit dem Kommando »tar xvpzf greengrass-linux-armv7l-1.6.0.tgz -C /« unter Rootrechten.
Kommen die von AWS bereitgestellten Zertifikate zum Zuge, benötigt der Core noch das Rootzertifikat von Symantec, das die AWS-Zertifikate authentisieren kann. Der Admin holt es auf dem Pi in das Verzeichnis »/greengrass/certs«. Darin gibt er dieses Kommando ein:
wget -O /greengrass/certs/root.ca.pem http://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem
Das Zertifikat und der zuvor erstellte private Schlüssel gehören ebenfalls in das Verzeichnis »/greengrass/certs«.
Jetzt fehlt noch eine Konfigurationsdatei im Json-Format, die unter dem Namen »config.json« im Verzeichnis »/greengrass/config« existieren muss. Listing 15 zeigt ein Beispiel dafür. Die Datei muss der Benutzer nun mit den Ergebnissen seiner Arbeit bis zu diesem Punkt befüllen. Dabei sind die folgenden Felder auszufüllen:
- »caPath«: Hier steht der Name der zuvor von Symantec geholten CA-Datei im »certs«-Verzeichnis.
- »certPath«: An diese Stelle gehört der Name der Zertifikatsdatei im »certs«- Verzeichnis.
- »keyPath«: Der Name der Datei im »certs« Verzeichnis, die den privaten Schlüssel enthält.
- »thingArn«: Der ARN des Core aus dem ersten Arbeitsschritt (»aws iot create-thing«).
- »iotHost«: Gemeint ist der Host im AWS-Universum, mit dem sich der Core verbinden soll. Den Namen liefert das Kommando »aws iot describe-endpoint«.
- »ggHost«: Dies ist der Greengrass-Server der Region, sein Name ergibt sich immer als »greengrass.iot.Name der Region.amazonaws.com«.
Nun startet der Admin den Core. Dazu führt er im Ordner »/greengrass/ggc/core« den Befehl »./greengrassd start« mit Rootrechten aus.
Will er überprüfen, ob alles funktioniert, gibt es jetzt mehrere Möglichkeiten. Das Startkommando gibt eine PID aus, mit der der Admin mittels »ps« und »grep« einfach prüft, ob der Prozess überhaupt läuft. AWS verwendet den MQTT-Port 8883, sodass das Kommando »netstat -tn | grep 8883« Verbindungen in den AWS-Adressbereich zeigen sollte. Diese verwenden, sofern der Pi entsprechend angebunden ist, auch IPv6.
Listing 15
config.json für den Pi
01 {
02 "coreThing": {
03 "caPath": "root.ca.pem",
04 "certPath": "gg.cert.pem",
05 "keyPath": "gg.privatekey.pem",
06 "thingArn": "arn:aws:iot:eu-central-1:566776501337:thing/LM_Core",
07 "iotHost": "a1bjpgs1c5n57h.iot.eu-central-1.amazonaws.com",
08 "ggHost": "greengrass.iot.eu-central-1.amazonaws.com"
09 },
10 "runtime": {
11 "cgroup": {
12 "useSystemd": "yes"
13 }
14 },
15 "managedRespawn": false
16 }
Der Greengrass Core schreibt seine Logdateien in den Ordner »/greengrass/var/log/system«. Die Datei »runtime.log« enthält allgemeine Startmeldungen und Informationen über die MQTT-Kommunikation, falls später mal Funktionen laufen. Bei der ersten Kontaktaufnahme ist die Datei »GGConnmanager.log« interessant, die Informationen über den Kommunikationsaufbau mit AWS enthält.
Auf dem AWS-CLI liefert das Kommando »aws greengrass get-connectivity-info –thing-name LM_core« bei erfolgreicher Verbindung Adressdaten des Geräts.
Kernarbeit
Jetzt steht alles bereit, um die Lambda-Funktion auf alle Cores der Gruppe auszurollen. Dieses Kommando stößt den Lauf an:
aws greengrass create-deployment --group-id ID_der_Gruppe --group-version-id ID_der_letzten_Version --deploymentType NewDeployment
In der AWS-Console findet der Admin in der Gruppe einen Reiter »Bereitstellungen« (Deployments), über den er den Status beobachten kann. Ist alles grün, läuft auf dem Core auch der Prozess.
Im Logverzeichnis existiert neben dem Ordner »system« noch einer namens »user«. In diesem wiederum liegt ein Ordner für jede AWS-Region, in der der Core mal aktiv war, im Beispiel also »eu-central-1«. Darin wartet ein Verzeichnis mit einer vergebenen ID. Hier erzeugt Greengrass für jede Funktion ein eigenes Logfile. Erzeugt das Python-Skript Fehler, tauchen die hier auf.
Der einfache Funktionstest erfolgt jetzt über die Amazon Web Console. Im IoT-Bereich gibt es einen Menü-Eintrag »Test«. Er bietet die Möglichkeit, entweder über den Webbrowser ein MQTT-Thema zu abonnieren oder Nachrichten in einem Thema zu veröffentlichen. Da der Test jetzt auf der empfangenden Seite stattfindet, wählt der Admin beim »Abonnementthema« einen Hashtag (»#«), um alle Nachrichten zu empfangen (Abbildung 3). Sind bereits mehrerer Cores im Einsatz, sollte er hier einen feineren Filter einsetzen. Nach der Wartezeit von einer Minute erscheint dann eine Meldung.
Damit sendet der Pi als Greengrass Core in das IoT-System von AWS. Fehlt nur noch die Alarmierung.
Der Alarm
Es wäre jetzt möglich, sich von außen an das MQTT-System zu klemmen und dann Alarme zu erzeugen. Aber das würde einen eigenen Rechner voraussetzen. Stattdessen kommt hier Lambda Serverless Computing ins Spiel. Der Entwickler lädt Code hoch, andere Funktionen oder ähnliche Events lösen ihn aus. Eine dieser Quellen darf auch der MQTT-Strom aus dem IoT-Bereich sein.
AWS bietet auch einen preisgünstigen Dienst für das Versenden von (unter anderem) SMS-Nachrichten. Damit die Lambda-Funktion später diese Funktionen nutzen kann, muss zunächst wieder eine Rolle existieren, die dies erlaubt (Listing 16). Das Kommando
Listing 16
Rolle zur SMS-Nutzung
01 {
02 "Statement": [
03 {
04 "Action": "sts:AssumeRole",
05 "Effect": "Allow",
06 "Principal": {
07 "Service": "lambda.amazonaws.com"
08 }
09 }
10 ]
11 }
aws iam create-role --role-name mysnsrole --assume-role-policy-document file://snsrolle.json --path /
legt die passende Rolle an. Danach erzeugt der Admin eine neue Lambda-Funktion in der Webconsole. Dabei fragt AWS beim Entwickler eine Rolle ab. Der wählt die »Existing Function« und die gerade erzeugte Rolle »mysnsrole« aus. Abbildung 4 zeigt den Dialog.
Auf der nächsten Seite gibt der Entwickler dann den konkreten Code ein, den Listing 17 zeigt.
Listing 17
SMS-Versender
01 import json
02 import boto3
03
04 ses = boto3.client('sns', region_name= 'eu-west-1')
05 print('Loading function')
06
07
08 def lambda_handler(event, context):
09 # TODO implement
10 temp = event['temperature']
11 if(temp > 30):
12 ret = ses.publish(
13 PhoneNumber="+49162000000",
14 Message="Temperature is " +str(temp))
15 return(ret)
16
17 return {
18 "statusCode": 200,
19 "body": json.dumps('All is well')
20 }
Der letzte Arbeitsschritt besteht im Anlegen eines Triggers. Abbildung 5 dokumentiert den Anfang der Funktionsseite. Auf der linken Seite warten die möglichen Auslöser. Einer davon ist »AWS IoT«. Den wählt der Entwickler aus und muss ihn in Form einer Regel noch konfigurieren. Im Popup wählt er »Create New Rule«, gibt ihr einen Namen und schreibt in das »Rule Query Statement« die Zeile »’select * from “iot/temperature”‘«. Nun führt jede Meldung des Minirechners zu einem Lauf des Skripts.
Der Json-Block, den MQTT losschickt, kommt als Python-Hash »event« in der Funktion an. Ist es beispielsweise zu warm, schickt AWS eine SMS.
Fazit
Damit ist das Projekt fertig. Die Menge des Python-Codes ist dabei sehr überschaubar, das gezeigte Setup sieht komplizierter aus, als es ist. Dennoch steckt einiges an Tüfteln dahinter, da die Dokumentation momentan zwar das Setup über die Web Console sehr gut erklärt, aber nicht die Variante über die Kommandozeile.
Der Autor erzeugte am Ende ein Shellskript von 15 Zeilen und ein Ansible Playbook, das ebenfalls sehr überschaubar war, um den Pi zu provisionieren. Denkt man den Greengrass-Gedanken zu Ende, ließe sich auch beliebiger Code auf den eigenen Geräten (und damit ohne Kosten) betreiben.
Da es bei richtigen Deployments nicht selten um eine große Zahl an Geräten geht, ist das einfache zentrale Verteilen des Codes extrem wichtig, und das erfüllt diese Lösung. Auch ist die gesamte Kommunikation via TLS abgesichert, was bei vielen selbst gestrickten Lösungen nicht der Fall ist. Über einen Over-the-Air-Agenten (OTA) ist es zudem möglich, die eigentliche Greengrass-Software zu aktualisieren.
Infos
-
MQTT: https://mqtt.org
-
Rainer Poisel, “Volks-Schalter”: Linux-Magazin 07/17, S. 30, https://www.linux-magazin.de/ausgaben/2017/07/mqtt/
-
Azure: https://azure.microsoft.com
-
AWS Lambda: https://aws.amazon.com/de/lambda/
-
IoT Core: https://aws.amazon.com/de/iot-core/
-
AWS Greengrass: https://aws.amazon.com/de/greengrass/
-
Greengrass und IoT bei AWS: https://docs.aws.amazon.com/de_de/greengrass/latest/developerguide/
-
Github-Repository der Adafruit-Bibliothek: https://github.com/adafruit/Adafruit_Python_DHT










